Каков наиболее эффективный способ переинтерпретировать базовые битовые шаблоны и записать их в массив или поле?

Используя 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 распознают намерение и соответствующим образом оптимизируют.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
575
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В частности, для 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;

  1. Убедитесь, что сам VarHandle постоянен, это можно сделать, поместив его в статическое конечное поле и обратившись к нему оттуда.
  2. Убедитесь, что вызов VarHandle точен. Здесь это будет означать приведение второго аргумента к длинному, так как VarHandle исключает также долго. (в последнем JDK вы можете использовать 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);
}    

+1 Спасибо за ответ. Какова производительность по отношению к использованию Unsafe? Это так же эффективно или это самый эффективный способ сделать это?

Suminda Sirinath S. Dharmasena 13.12.2020 15:37

Есть некоторые препятствия, через которые вам нужно пройти с VarHandles, чтобы сделать их такими же эффективными, как и Unsafe; 1) Убедитесь, что сам VarHandle постоянен, это можно сделать, поместив его в поле static final и обратившись к нему оттуда. 2) Убедитесь, что вызов VarHandle точен. Здесь это будет означать приведение второго аргумента к long, поскольку VarHandle также исключает long. Это можно упростить, заключив вызов set VarHandle во вспомогательный метод, выполняющий приведение типов.

Jorn Vernee 13.12.2020 15:43

Чем массив не является byte[]? Скажи long[] array и я хочу написать byte.

Suminda Sirinath S. Dharmasena 13.12.2020 15:47

Если массив не является byte[], вам придется использовать API доступа к памяти, который инкубируется в последней версии JDK (16). Там вы можете преобразовать long[] в MemorySegment, а затем получить к нему доступ с помощью byte доступа к памяти VarHandle.

Jorn Vernee 13.12.2020 15:53

Канонический способ с использованием 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. Я немного переформулировал ответ прямо сейчас и добавил ссылку на ошибку.

Petr Janeček 11.04.2022 14:01

Отличное дополнение. Очень хорошо знать.

Holger 11.04.2022 14:04

Но если я правильно прочитал, установка одного значения типа byteBuffer.putLong(…) не повлияла?

Holger 11.04.2022 14:06

Описание ошибки немного сумбурно, извините за это. На самом деле я сообщил об этом как о двух разных ошибках, и инженер, обрабатывающий их, смешал их вместе, так что это немного сбивает с толку. Я не измерял отдельные вызовы putLong(), так как меня особенно интересовала переинтерпретация целых массивов. Ошибка заключалась в том, что JVM не использовала внутренний механизм copyMemory для пакетного метода, но я не знаю, как работает метод put-single. Не стесняйтесь взглянуть на код бенчмаркинга и попробовать добавить дополнительные параметры.

Petr Janeček 11.04.2022 14:11

Другие вопросы по теме