Используя Unsafe.putXXX, можно поместить примитивный тип в массив или поле объекта.
Но код, подобный следующему, генерирует ошибки.
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.arrayElementVarHandle(long[].class);
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
}
Exception in thread "main" java.lang.ClassCastException: Cannot cast [B to [J
at java.base/java.lang.Class.cast(Class.java:3780)
at Main.main(Main.java:15)
Также bytes может быть записан как:
byte[] array = new byte[32];
long v = 5,
int i = 8;
int high = (int) (v >>> 32);
int low = (int) v;
array[i + 0] = (byte) (high >>> 24);
array[i + 1] = (byte) (high >>> 16);
array[i + 2] = (byte) (high >>> 8);
array[i + 3] = (byte) high;
array[i + 4] = (byte) (low >>> 24);
array[i + 5] = (byte) (low >>> 16);
array[i + 6] = (byte) (low >>> 8);
array[i + 7] = (byte) low;
Есть ли эффективный способ переинтерпретировать разные типы и записать их в поля и массивы, возможно, избегая Unsafe, но столь же эффективно.
Любые особые случаи, когда компилятор или JIT распознают намерение и соответствующим образом оптимизируют.




В частности, для byte[] вы можете использовать MethodHandles::byteArrayViewVarHandle:
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
Есть несколько обручей, через которые вы должны пройти с VarHandles, чтобы сделать их такими же быстрыми, как Unsafe;
VarHandle::withInvokeExactBehavior, чтобы обеспечить это).Это можно упростить, обернув набор VarHandle и получив вызовы вспомогательных методов, выполняющих приведение:
private static final VarHandle LONG_ARR_HANDLE
= MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
public static void setLong(byte[] bytes, int index, long value) {
LONG_ARR_HANDLE.set(bytes, index, value);
}
public static long getLong(byte[] bytes, int index) {
return (long) LONG_ARR_HANDLE.get(bytes, index);
}
Есть некоторые препятствия, через которые вам нужно пройти с VarHandles, чтобы сделать их такими же эффективными, как и Unsafe; 1) Убедитесь, что сам VarHandle постоянен, это можно сделать, поместив его в поле static final и обратившись к нему оттуда. 2) Убедитесь, что вызов VarHandle точен. Здесь это будет означать приведение второго аргумента к long, поскольку VarHandle также исключает long. Это можно упростить, заключив вызов set VarHandle во вспомогательный метод, выполняющий приведение типов.
Чем массив не является byte[]? Скажи long[] array и я хочу написать byte.
Если массив не является byte[], вам придется использовать API доступа к памяти, который инкубируется в последней версии JDK (16). Там вы можете преобразовать long[] в MemorySegment, а затем получить к нему доступ с помощью byte доступа к памяти VarHandle.
Канонический способ с использованием ByteBuffer также является самым быстрым способом, начиная с Java 17. Измерьте сами (и/или рассмотрите другие варианты), если вы еще не используете Java 17.
ByteBuffer byteBuffer = ByteBuffer.allocate(bytesLength);
byteBuffer.asLongBuffer().put(longs);
return byteBuffer.array();
Я провел эксперимент с сериализацией float[] в byte[] и в зависимости от требуемого порядка байтов и версии Java разные методы выходили по-разному, но ByteBuffer и MemorySegment и Unsafe или библиотеки, использующие их, были на высоте. VarHandles не сильно отстают, но они все еще зациклены.
На OpenJDK 17 с использованием G1 на моем MBP 16 "2019 результаты были следующими:
Benchmark (size) Mode Cnt Score Error Units
beByteBuffer 512 avgt 5 193.351 ± 21.366 ns/op
beByteBufferWrap 512 avgt 5 197.477 ± 43.743 ns/op
beDataOutputStream 512 avgt 5 1590.887 ± 60.744 ns/op
beKryo 512 avgt 5 861.624 ± 11.927 ns/op
beManualUnpacking 512 avgt 5 861.573 ± 10.108 ns/op
beObjectOutputStream 512 avgt 5 2168.386 ± 12.950 ns/op
beVarHandle 512 avgt 5 225.611 ± 1.839 ns/op
leByteBuffer 512 avgt 5 155.345 ± 1.229 ns/op
leByteBufferWrap 512 avgt 5 154.523 ± 0.853 ns/op
leDataOutputStream 512 avgt 5 55414.147 ± 9390.669 ns/op
leKryoUnsafe 512 avgt 5 156.019 ± 18.235 ns/op
leManualUnpacking 512 avgt 5 880.687 ± 12.856 ns/op
leMemorySegment 512 avgt 5 148.483 ± 0.897 ns/op
leUnsafeCopyMemory 512 avgt 5 149.107 ± 2.011 ns/op
leVarHandle 512 avgt 5 225.615 ± 1.357 ns/op
Обратите внимание: если вы еще не используете Java 17, ваши результаты могут сильно отличаться! ObjectOutputStream был намного медленнее до Java 17, и два из четырех вариантов *byteBuffer были в 2* медленнее тоже. Поэтому измерьте для вашей конкретной JVM и конфигурации JVM. В целом, однако, ByteBuffer или MemorySegment — это способ двигаться вперед.
@Holger Ага, извините, в таком случае это действительно вводило в заблуждение. Я ДЕЙСТВИТЕЛЬНО измерил более старые версии, сообщил об ошибке в коде ByteBuffer, и она была исправлена в Java 17. Я немного переформулировал ответ прямо сейчас и добавил ссылку на ошибку.
Отличное дополнение. Очень хорошо знать.
Но если я правильно прочитал, установка одного значения типа byteBuffer.putLong(…) не повлияла?
Описание ошибки немного сумбурно, извините за это. На самом деле я сообщил об этом как о двух разных ошибках, и инженер, обрабатывающий их, смешал их вместе, так что это немного сбивает с толку. Я не измерял отдельные вызовы putLong(), так как меня особенно интересовала переинтерпретация целых массивов. Ошибка заключалась в том, что JVM не использовала внутренний механизм copyMemory для пакетного метода, но я не знаю, как работает метод put-single. Не стесняйтесь взглянуть на код бенчмаркинга и попробовать добавить дополнительные параметры.
+1 Спасибо за ответ. Какова производительность по отношению к использованию
Unsafe? Это так же эффективно или это самый эффективный способ сделать это?