diff --git a/src/java/org/apache/cassandra/utils/FastByteOperations.java b/src/java/org/apache/cassandra/utils/FastByteOperations.java index 5efba76456..9cf8b863d5 100644 --- a/src/java/org/apache/cassandra/utils/FastByteOperations.java +++ b/src/java/org/apache/cassandra/utils/FastByteOperations.java @@ -186,6 +186,8 @@ public class FastByteOperations */ static final long BYTE_ARRAY_BASE_OFFSET; static final long DIRECT_BUFFER_ADDRESS_OFFSET; + static final long HEAP_HB_FIELD_OFFSET; + static final long HEAP_ARRAY_FIELD_OFFSET; static { @@ -218,6 +220,8 @@ public class FastByteOperations { BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); DIRECT_BUFFER_ADDRESS_OFFSET = theUnsafe.objectFieldOffset(Buffer.class.getDeclaredField("address")); + HEAP_HB_FIELD_OFFSET = theUnsafe.objectFieldOffset(ByteBuffer.class.getDeclaredField("hb")); + HEAP_ARRAY_FIELD_OFFSET = theUnsafe.objectFieldOffset(ByteBuffer.class.getDeclaredField("offset")); } catch (Exception e) { @@ -286,15 +290,24 @@ public class FastByteOperations { Object obj1; long offset1; - if (buffer1.hasArray()) + + // Direct ByteBuffer + if (buffer1.isDirect()) + { + obj1 = null; + offset1 = theUnsafe.getLong(buffer1, DIRECT_BUFFER_ADDRESS_OFFSET) + position1; + } + // Heap ByteBuffer (Mutable) + else if (buffer1.hasArray()) { obj1 = buffer1.array(); offset1 = BYTE_ARRAY_BASE_OFFSET + buffer1.arrayOffset() + position1; } + // Read-Only Heap ByteBuffer (Still has hb but read-only) else { - obj1 = null; - offset1 = theUnsafe.getLong(buffer1, DIRECT_BUFFER_ADDRESS_OFFSET) + position1; + obj1 = theUnsafe.getObject(buffer1, HEAP_HB_FIELD_OFFSET); + offset1 = BYTE_ARRAY_BASE_OFFSET + position1; } return compareTo(obj1, offset1, length1, buffer2, BYTE_ARRAY_BASE_OFFSET + offset2, length2); @@ -330,16 +343,28 @@ public class FastByteOperations { Object src; long srcOffset; - if (srcBuf.hasArray()) - { - src = srcBuf.array(); - srcOffset = BYTE_ARRAY_BASE_OFFSET + srcBuf.arrayOffset(); - } - else + + // Direct ByteBuffer + if (srcBuf.isDirect()) { src = null; srcOffset = theUnsafe.getLong(srcBuf, DIRECT_BUFFER_ADDRESS_OFFSET); } + // Heap ByteBuffer (Mutable) + else if (srcBuf.hasArray()) + { + src = srcBuf.array(); + srcOffset = BYTE_ARRAY_BASE_OFFSET + srcBuf.arrayOffset(); + } + // Read-Only Heap ByteBuffer (Still has hb but read-only) + else + { + src = theUnsafe.getObject(srcBuf, HEAP_HB_FIELD_OFFSET); + int arrayOffset = theUnsafe.getInt(srcBuf, HEAP_ARRAY_FIELD_OFFSET); + srcOffset = BYTE_ARRAY_BASE_OFFSET + arrayOffset ; + } + // Direct ByteBuffer + copy(src, srcOffset + srcPosition, trgBuf, trgPosition, length); } @@ -387,16 +412,26 @@ public class FastByteOperations Object obj1; long offset1; int length1; - if (buffer1.hasArray()) - { - obj1 = buffer1.array(); - offset1 = BYTE_ARRAY_BASE_OFFSET + buffer1.arrayOffset(); - } - else + + // Direct ByteBuffer + if (buffer1.isDirect()) { obj1 = null; offset1 = theUnsafe.getLong(buffer1, DIRECT_BUFFER_ADDRESS_OFFSET); } + // Heap ByteBuffer (Mutable) + else if (buffer1.hasArray()) + { + obj1 = buffer1.array(); + offset1 = BYTE_ARRAY_BASE_OFFSET + buffer1.arrayOffset(); + } + // Read-Only Heap ByteBuffer (Still has hb but read-only) + else + { + obj1 = theUnsafe.getObject(buffer1, HEAP_HB_FIELD_OFFSET); + offset1 = BYTE_ARRAY_BASE_OFFSET; + } + offset1 += buffer1.position(); length1 = buffer1.remaining(); return compareTo(obj1, offset1, length1, buffer2); @@ -410,16 +445,26 @@ public class FastByteOperations int position = buffer.position(); int limit = buffer.limit(); - if (buffer.hasArray()) - { - obj2 = buffer.array(); - offset2 = BYTE_ARRAY_BASE_OFFSET + buffer.arrayOffset(); - } - else + + // Direct ByteBuffer + if (buffer.isDirect()) { obj2 = null; offset2 = theUnsafe.getLong(buffer, DIRECT_BUFFER_ADDRESS_OFFSET); } + // Heap ByteBuffer (Mutable) + else if (buffer.hasArray()) + { + obj2 = buffer.array(); + offset2 = BYTE_ARRAY_BASE_OFFSET + buffer.arrayOffset(); + } + // Read-Only Heap ByteBuffer (Still has hb but read-only) + else + { + obj2 = theUnsafe.getObject(buffer, HEAP_HB_FIELD_OFFSET); + offset2 = BYTE_ARRAY_BASE_OFFSET; + } + int length2 = limit - position; offset2 += position; diff --git a/test/unit/org/apache/cassandra/utils/FastByteOperationsTest.java b/test/unit/org/apache/cassandra/utils/FastByteOperationsTest.java index 4f6d6523f8..5776dbcc82 100644 --- a/test/unit/org/apache/cassandra/utils/FastByteOperationsTest.java +++ b/test/unit/org/apache/cassandra/utils/FastByteOperationsTest.java @@ -20,6 +20,7 @@ package org.apache.cassandra.utils; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import org.junit.Test; @@ -50,6 +51,60 @@ public class FastByteOperationsTest testCopy(bytes1, wrap1(bytes1, false), wrap2(empty, false), UO); } + @Test + public void testFastByteCopyReadOnly() + { + byte[] bytes1 = new byte[128]; + byte[] empty = new byte[128]; + rand.nextBytes(bytes1); + testCopy(bytes1, wrap1(bytes1, true).asReadOnlyBuffer(), wrap2(empty, true), PJO); + testCopy(bytes1, wrap1(bytes1, true).asReadOnlyBuffer(), wrap2(empty, false), PJO); + testCopy(bytes1, wrap1(bytes1, false).asReadOnlyBuffer(), wrap2(empty, true), PJO); + testCopy(bytes1, wrap1(bytes1, false).asReadOnlyBuffer(), wrap2(empty, false), PJO); + testCopy(bytes1, wrap1(bytes1, true).asReadOnlyBuffer(), wrap2(empty, true), UO); + testCopy(bytes1, wrap1(bytes1, true).asReadOnlyBuffer(), wrap2(empty, false), UO); + testCopy(bytes1, wrap1(bytes1, false).asReadOnlyBuffer(), wrap2(empty, true), UO); + testCopy(bytes1, wrap1(bytes1, false).asReadOnlyBuffer(), wrap2(empty, false), UO); + } + + @Test + public void testFastByteCopyWithPoistion() + { + byte[] bytes1 = new byte[128]; + byte[] empty = new byte[128]; + rand.nextBytes(bytes1); + + int randomPos = ThreadLocalRandom.current().nextInt(0, 101); + + testCopyWithPosition(bytes1, wrapR(bytes1, true, randomPos).asReadOnlyBuffer(), wrapR1(empty, true, randomPos), PJO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, true, randomPos).asReadOnlyBuffer(), wrapR1(empty, false, randomPos), PJO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, false, randomPos).asReadOnlyBuffer(), wrapR1(empty, true, randomPos), PJO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, false, randomPos).asReadOnlyBuffer(), wrapR1(empty, false, randomPos), PJO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, true, randomPos).asReadOnlyBuffer(), wrapR1(empty, true, randomPos), UO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, true, randomPos).asReadOnlyBuffer(), wrapR1(empty, false, randomPos), UO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, false, randomPos).asReadOnlyBuffer(), wrapR1(empty, true, randomPos), UO, randomPos); + randomPos = ThreadLocalRandom.current().nextInt(0, 101); + testCopyWithPosition(bytes1, wrapR(bytes1, false, randomPos).asReadOnlyBuffer(), wrapR1(empty, false, randomPos), UO, randomPos); + + } + + + private void testCopyWithPosition(byte[] canon, ByteBuffer src, ByteBuffer trg, FastByteOperations.ByteOperations ops, int position) + { + byte[] result = new byte[src.remaining()]; + byte[] canonSubArray = Arrays.copyOfRange(canon, position, canon.length); + ops.copy(src, src.position(), trg, trg.position(), src.remaining()); + ops.copy(trg, trg.position(), result, 0, trg.remaining()); + assert firstdiff(canonSubArray, result) < 0; + } + + private void testCopy(byte[] canon, ByteBuffer src, ByteBuffer trg, FastByteOperations.ByteOperations ops) { byte[] result = new byte[src.remaining()]; @@ -86,6 +141,38 @@ public class FastByteOperationsTest } } + @Test + public void testFastByteCompareRandomRead() + { + byte[] bytes1 = new byte[128]; + for (int i = 0 ; i < 1000 ; i++) + { + rand.nextBytes(bytes1); + for (int j = 0 ; j < 16 ; j++) + { + byte[] bytes2 = Arrays.copyOf(bytes1, bytes1.length - j); + testTwiddleOneByteComparisonsReadOnly(bytes1, bytes2, 16, true, 1); + testTwiddleOneByteComparisonsReadOnly(bytes1, bytes2, 16, true, -1); + testTwiddleOneByteComparisonsReadOnly(bytes1, bytes2, 16, false, 1); + testTwiddleOneByteComparisonsReadOnly(bytes1, bytes2, 16, false, -1); + testTwiddleOneByteComparisonsReadOnly(bytes1, bytes2, 16, true, 128); + testTwiddleOneByteComparisonsReadOnly(bytes1, bytes2, 16, false, 128); + } + } + } + + + private void testTwiddleOneByteComparisonsReadOnly(byte[] bytes1, byte[] bytes2, int count, boolean start, int inc) + { + for (int j = 0 ; j < count ; j++) + { + int index = start ? j : bytes2.length - (j + 1); + bytes2[index] += inc; + testComparisonsReadOnly(bytes1, bytes2); + bytes2[index] -= inc; + } + } + private void testTwiddleOneByteComparisons(byte[] bytes1, byte[] bytes2, int count, boolean start, int inc) { for (int j = 0 ; j < count ; j++) @@ -116,6 +203,45 @@ public class FastByteOperationsTest return buf; } + private static ByteBuffer wrapR(byte[] bytes, boolean direct, int position) + { + return sliceR(bytes, direct ? dbuf1 : hbuf1, position); + } + + private static ByteBuffer wrapR1(byte[] bytes, boolean direct, int position) + { + return sliceR(bytes, direct ? dbuf2 : hbuf2, position); + } + + private static ByteBuffer sliceR(byte[] bytes, ByteBuffer buf, int position) + { + buf = buf.duplicate(); + buf.put(bytes); + buf.position(position); + buf = buf.slice(); + + return buf; + + } + + private void testComparisonsReadOnly(byte[] bytes1, byte[] bytes2) + { + testComparison(bytes1, bytes2); + testComparison(bytes2, bytes1); + testComparison(wrap1(bytes1, false).asReadOnlyBuffer(), bytes2); + testComparison(wrap2(bytes2, false).asReadOnlyBuffer(), bytes1); + testComparison(wrap1(bytes1, false).asReadOnlyBuffer(), wrap2(bytes2, false)); + testComparison(wrap2(bytes2, false).asReadOnlyBuffer(), wrap1(bytes1, false)); + testComparison(wrap1(bytes1, true).asReadOnlyBuffer(), bytes2); + testComparison(wrap2(bytes2, true).asReadOnlyBuffer(), bytes1); + testComparison(wrap1(bytes1, true).asReadOnlyBuffer(), wrap2(bytes2, true)); + testComparison(wrap2(bytes2, true).asReadOnlyBuffer(), wrap1(bytes1, true)); + testComparison(wrap1(bytes1, true).asReadOnlyBuffer(), wrap2(bytes2, false)); + testComparison(wrap1(bytes1, false).asReadOnlyBuffer(), wrap2(bytes2, true)); + testComparison(wrap2(bytes2, true).asReadOnlyBuffer(), wrap1(bytes1, false)); + testComparison(wrap2(bytes2, false).asReadOnlyBuffer(), wrap1(bytes1, true)); + } + private void testComparisons(byte[] bytes1, byte[] bytes2) { testComparison(bytes1, bytes2); diff --git a/test/unit/org/apache/cassandra/utils/Generators.java b/test/unit/org/apache/cassandra/utils/Generators.java index 0f92b9ea90..a4b2d0118b 100644 --- a/test/unit/org/apache/cassandra/utils/Generators.java +++ b/test/unit/org/apache/cassandra/utils/Generators.java @@ -370,7 +370,7 @@ public final class Generators public static Gen bytes(int min, int max) { - return bytes(min, max, SourceDSL.arbitrary().constant(BBCases.HEAP)); + return bytes(min, max, SourceDSL.arbitrary().pick(BBCases.HEAP, BBCases.READ_ONLY_HEAP)); } public static Gen directBytes(int min, int max)