5 февр. 2016 г.

Java unsafe: copyMemory aligned vs unaligned

Давеча переводя проект на новые рельсы стала падать сборка - тесты проходят, а вот jvm крешится.

Первое подозрение упало на наш alloc / dealloc поверх Unsafe (да-да, кровь-кишки-камаз) - но в итоге все же стало ясно, что наши руки чистые и вина реально на C2 компиляторе и трова стала тому причина - на эту тему две ошибки в C2 компиляторе: JDK-8081379 и по мотивам этого же JDK-6675699.

Однако, пока я винил наш alloc/dealloc смотрел - а не пытаемся ли мы деаллоцировать чего не нужного, соответственно стал писать адреса выделенной памяти и размеры.
Удивило, что очень много объектов без выравнивания по длине - например, 27 байт или там 253.

Читаем sun.misc.Unsafe#copyMemory и тут ребята заботливо пишут:
    /**
     * Sets all bytes in a given block of memory to a copy of another
     * block.
     *
     * This method determines each block's base address by means of two parameters,
     * and so it provides (in effect) a double-register addressing mode,
     * as discussed in {@link #getInt(Object,long)}.  When the object reference is null,
     * the offset supplies an absolute base address.
     *
     * The transfers are in coherent (atomic) units of a size determined
     * by the address and length parameters.  If the effective addresses and
     * length are all even modulo 8, the transfer takes place in 'long' units.
     * If the effective addresses and length are (resp.) even modulo 4 or 2,
     * the transfer takes place in units of 'int' or 'short'.
     *
     * @since 1.7
     */


Более того, мистер Коваль накинул еще:
мол не только же длина, но и смещение играют роль - н.р если даже читать long, но он будет смещен на 1 байт относительно слова - то в итоге будут прочитаны два long.


обновление 2015-02-13:

Итак, куда же идет Unsafe.copyMemory ?

Это нативный jvm вызов:
копаем в сторону unsafe.cpp: Unsafe_CopyMemory
потом Copy::conjoint_memory_atomic

и вот от момент откровения:

  uintptr_t bits = (uintptr_t) src | (uintptr_t) dst | (uintptr_t) size;

  if (bits % sizeof(jlong) == 0) {
    Copy::conjoint_jlongs_atomic((jlong*) src, (jlong*) dst, size / sizeof(jlong));
  } else if (bits % sizeof(jint) == 0) {
    Copy::conjoint_jints_atomic((jint*) src, (jint*) dst, size / sizeof(jint));
  } else if (bits % sizeof(jshort) == 0) {
    Copy::conjoint_jshorts_atomic((jshort*) src, (jshort*) dst, size / sizeof(jshort));
  } else {
    // Not aligned, so no need to be atomic.
    Copy::conjoint_jbytes((void*) src, (void*) dst, size);
  }
Если кратко, то bits тогда и только тогда будет кратен размеру long, когда и адрес источника, и адрес цели, и длина блока будут кратны размеру long.

benchmark.
проверяет копирование в многопоточном режиме - 4 нитки.

Изначально производилось копирование из одного общего (для всех нитей) куска памяти в другой общий кусок памяти - и от этого получались странные цифры, что копирование 8 байт со смещением 0 такое же, как и копирование 8 байт со смещением 3 байта.

В моем приложении другой шаблон использования - копирование происходит из общего куска памяти в свой (н-р локальный массив байт) локальный кусок памяти.

Результаты для 4х ниток:

Benchmark                    Mode  Samples   Score   Error  Units
copyMemory253Bytes           avgt       15  13.023 ± 0.376  ns/op
copyMemory253BytesOffset3    avgt       15  13.189 ± 0.512  ns/op
copyMemory256Bytes           avgt       15  11.091 ± 0.304  ns/op
copyMemory256BytesOffset7    avgt       15  13.185 ± 0.066  ns/op

copyMemory27Bytes            avgt       15   9.034 ± 0.058  ns/op
copyMemory32Bytes            avgt       15   5.567 ± 0.154  ns/op
copyMemory32BytesOffset3     avgt       15   7.779 ± 0.080  ns/op

copyMemory4Bytes             avgt       15   6.801 ± 0.068  ns/op
copyMemory4BytesOffset3      avgt       15   7.760 ± 0.091  ns/op

copyMemory8Bytes             avgt       15   6.763 ± 0.044  ns/op
copyMemory8BytesOffset1      avgt       15   8.398 ± 0.154  ns/op
copyMemory8BytesOffset3      avgt       15   8.368 ± 0.083  ns/op
copyMemory8BytesOffset5      avgt       15   8.396 ± 0.079  ns/op

readByteOffset0              avgt       15   3.067 ± 0.153  ns/op
readByteOffset3              avgt       15   3.024 ± 0.044  ns/op

readIntOffset0               avgt       15   3.001 ± 0.037  ns/op
readIntOffset3               avgt       15   3.051 ± 0.150  ns/op

readLongOffset0              avgt       15   2.997 ± 0.042  ns/op
readLongOffset3              avgt       15   2.993 ± 0.056  ns/op
и для 1 нитки
Benchmark                    Mode  Samples   Score   Error  Units
copyMemory253Bytes           avgt       15  13.392 ± 0.580  ns/op
copyMemory253BytesOffset3    avgt       15  12.903 ± 0.304  ns/op
copyMemory256Bytes           avgt       15  11.195 ± 0.405  ns/op
copyMemory256BytesOffset7    avgt       15  13.446 ± 0.422  ns/op

copyMemory27Bytes            avgt       15   9.136 ± 0.266  ns/op
copyMemory32Bytes            avgt       15   5.513 ± 0.190  ns/op
copyMemory32BytesOffset3     avgt       15   7.830 ± 0.273  ns/op

copyMemory4Bytes             avgt       15   6.826 ± 0.204  ns/op
copyMemory4BytesOffset3      avgt       15   7.757 ± 0.223  ns/op

copyMemory8Bytes             avgt       15   6.897 ± 0.221  ns/op
copyMemory8BytesOffset1      avgt       15   8.534 ± 0.276  ns/op
copyMemory8BytesOffset3      avgt       15   8.482 ± 0.285  ns/op
copyMemory8BytesOffset5      avgt       15   8.403 ± 0.262  ns/op

readByteOffset0              avgt       15   2.971 ± 0.104  ns/op
readByteOffset3              avgt       15   3.009 ± 0.115  ns/op

readIntOffset0               avgt       15   3.001 ± 0.125  ns/op
readIntOffset3               avgt       15   3.032 ± 0.113  ns/op

readLongOffset0              avgt       15   3.010 ± 0.100  ns/op
readLongOffset3              avgt       15   3.060 ± 0.094  ns/op
Т.е действительно для копирования очень важно, чтобы всё было по восьмерке, тогда как для чтения это не имеет значения. ... meten is weten // голл. поговорка: измерение это знание