blob: 12a158a8da4d05590abf10aba1a5b229eb1c904a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cassandra.spark.bulkwriter.util;
import java.lang.reflect.Field;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import com.google.common.primitives.Longs;
import com.google.common.primitives.UnsignedBytes;
import com.google.common.primitives.UnsignedLongs;
import sun.misc.Unsafe; // CHECKSTYLE IGNORE: Used with care to speed byte operations up
/**
* Utility code to do optimized byte-array comparison.
* This is borrowed and slightly modified from Guava's {@link UnsignedBytes}
* class to be able to compare arrays that start at non-zero offsets.
* NOTE: This was lifted from Cassandra 2.1 and reduced to the minimum number of
* methods necessary for the Spark Bulk Writer. Tests in Cassandra, not ported here
* as more copy/paste didn't seem to be valuable. Also tested via DecoratedKeyTest.
*/
public final class FastByteOperations
{
private FastByteOperations()
{
throw new IllegalStateException(getClass() + " is static utility class and shall not be instantiated");
}
/**
* Lexicographically compare two byte arrays
*
* @param buffer1 the first bytebuffer
* @param buffer2 the second byteburrer
* @return standard java comparison return value (-1 for <, 0 for ==, and 1 for >)
*/
public static int compareUnsigned(ByteBuffer buffer1, ByteBuffer buffer2)
{
return BestHolder.BEST.compare(buffer1, buffer2);
}
public interface ByteOperations
{
int compare(ByteBuffer buffer1, ByteBuffer buffer2);
}
/**
* Provides a lexicographical comparer implementation; either a Java
* implementation or a faster implementation based on {@link Unsafe}.
*
* Uses reflection to gracefully fall back to the Java implementation if
* {@code Unsafe} isn't available.
*/
private static class BestHolder
{
static final String UNSAFE_COMPARER_NAME = FastByteOperations.class.getName() + "$UnsafeOperations";
static final ByteOperations BEST = getBest();
/**
* Returns the Unsafe-using Comparer, or falls back to the pure-Java implementation if unable to do so
*/
static ByteOperations getBest()
{
String arch = System.getProperty("os.arch");
boolean unaligned = arch.equals("i386") || arch.equals("x86") || arch.equals("x86_64") || arch.equals("amd64");
if (!unaligned)
{
return new PureJavaOperations();
}
try
{
Class<?> theClass = Class.forName(UNSAFE_COMPARER_NAME);
// Yes, UnsafeComparer does implement Comparer<byte[]>
@SuppressWarnings("unchecked")
ByteOperations comparer = (ByteOperations) theClass.getConstructor().newInstance();
return comparer;
}
catch (Throwable throwable)
{
// Ensure we really catch *everything*
return new PureJavaOperations();
}
}
}
public static final class UnsafeOperations implements ByteOperations
{
static final Unsafe UNSAFE;
/**
* The offset to the first element in a byte array
*/
static final long BYTE_ARRAY_BASE_OFFSET;
static final long DIRECT_BUFFER_ADDRESS_OFFSET;
static
{
UNSAFE = (Unsafe) AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
try
{
Field field = Unsafe.class.getDeclaredField("UNSAFE");
field.setAccessible(true);
return field.get(null);
}
catch (NoSuchFieldException | IllegalAccessException exception)
{
// It doesn't matter what we throw; it's swallowed in getBest()
throw new Error();
}
});
try
{
BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
DIRECT_BUFFER_ADDRESS_OFFSET = UNSAFE.objectFieldOffset(Buffer.class.getDeclaredField("address"));
}
catch (Exception exception)
{
throw new AssertionError(exception);
}
// Sanity check - this should never fail
if (UNSAFE.arrayIndexScale(byte[].class) != 1)
{
throw new AssertionError();
}
}
static final boolean BIG_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN);
public int compare(ByteBuffer buffer1, ByteBuffer buffer2)
{
return compareTo(buffer1, buffer2);
}
static int compareTo(ByteBuffer buffer1, ByteBuffer buffer2)
{
Object object1;
long offset1;
int length1;
if (buffer1.hasArray())
{
object1 = buffer1.array();
offset1 = BYTE_ARRAY_BASE_OFFSET + buffer1.arrayOffset();
}
else
{
object1 = null;
offset1 = UNSAFE.getLong(buffer1, DIRECT_BUFFER_ADDRESS_OFFSET);
}
offset1 += buffer1.position();
length1 = buffer1.remaining();
return compareTo(object1, offset1, length1, buffer2);
}
static int compareTo(Object buffer1, long offset1, int length1, ByteBuffer buffer2)
{
Object object2;
long offset2;
int position = buffer2.position();
int limit = buffer2.limit();
if (buffer2.hasArray())
{
object2 = buffer2.array();
offset2 = BYTE_ARRAY_BASE_OFFSET + buffer2.arrayOffset();
}
else
{
object2 = null;
offset2 = UNSAFE.getLong(buffer2, DIRECT_BUFFER_ADDRESS_OFFSET);
}
int length2 = limit - position;
offset2 += position;
return compareTo(buffer1, offset1, length1, object2, offset2, length2);
}
/**
* Lexicographically compare two arrays
*
* @param buffer1 left operand: a byte[] or null
* @param buffer2 right operand: a byte[] or null
* @param offset1 Where to start comparing in the left buffer
* (pure memory address if buffer1 is null, or relative otherwise)
* @param offset2 Where to start comparing in the right buffer
* (pure memory address if buffer1 is null, or relative otherwise)
* @param length1 How much to compare from the left buffer
* @param length2 How much to compare from the right buffer
* @return 0 if equal, < 0 if left is less than right, etc.
*/
static int compareTo(Object buffer1, long offset1, int length1, Object buffer2, long offset2, int length2)
{
int minLength = Math.min(length1, length2);
/*
* Compare 8 bytes at a time. Benchmarking shows comparing 8 bytes at a
* time is no slower than comparing 4 bytes at a time even on 32-bit.
* On the other hand, it is substantially faster on 64-bit.
*/
int wordComparisons = minLength & ~7;
for (int index = 0; index < wordComparisons; index += Longs.BYTES)
{
long long1 = UNSAFE.getLong(buffer1, offset1 + index);
long long2 = UNSAFE.getLong(buffer2, offset2 + index);
if (long1 != long2)
{
return BIG_ENDIAN ? UnsignedLongs.compare(long1, long2)
: UnsignedLongs.compare(Long.reverseBytes(long1), Long.reverseBytes(long2));
}
}
for (int index = wordComparisons; index < minLength; index++)
{
int byte1 = UNSAFE.getByte(buffer1, offset1 + index) & 0xFF;
int byte2 = UNSAFE.getByte(buffer2, offset2 + index) & 0xFF;
if (byte1 != byte2)
{
return byte1 - byte2;
}
}
return length1 - length2;
}
}
public static final class PureJavaOperations implements ByteOperations
{
public int compare(ByteBuffer buffer1, ByteBuffer buffer2)
{
int end1 = buffer1.limit();
int end2 = buffer2.limit();
for (int index1 = buffer1.position(), index2 = buffer2.position(); index1 < end1 && index2 < end2; index1++, index2++)
{
int byte1 = buffer1.get(index1) & 0xFF;
int byte2 = buffer2.get(index2) & 0xFF;
if (byte1 != byte2)
{
return byte1 - byte2;
}
}
return buffer1.remaining() - buffer2.remaining();
}
}
}