8

Как вы думаете, лучший ли способ реализации части приобретения/приобретения пары в Java?Реализация приобретения для выпуска из Unsafe.putOrdered *()?

Я пытаюсь смоделировать некоторые действия в приложении my, используя классическую семантику release/получения (без StoreLoad и без последовательной согласованности по потокам).

Существует несколько способов достижения приблизительного эквивалента хранилища в JDK. java.util.concurrent.Atomic*.lazySet() и базовые sun.misc.Unsafe.putOrdered*() являются наиболее часто цитируемыми подходами для этого. Однако нет очевидного способа реализовать загрузку.

  • В JDK API, которые позволяют lazySet() в основном используют volatile переменные внутри, так что их магазин-релизы спариваются с летучими нагрузками. Теоретически энергозависимые нагрузки должны быть более дорогими, чем загрузка, и не должны обеспечивать ничего большего, чем чистая загрузка в контексте предыдущего хранилища.

  • sun.misc.Unsafe не обеспечивает getAcquire()* эквивалентов putOrdered*() методов, даже если такие методы приобретают запланированы на предстоящий VarHandles API.

  • Похоже, что это будет работать, это простая загрузка, а затем sun.misc.Unsafe.loadFence(). Это несколько обескураживает, что я не видел этого нигде. Это может быть связано с тем, что это довольно уродливый взлом.

P.S. Я хорошо понимаю, что эти механизмы не охвачены JMM, что их недостаточно для обеспечения последовательной согласованности и что созданные ими действия не являются действиями синхронизации (например, я понимаю, что они, например, прерывают IRIW). Я также понимаю, что хранилища-релизы, предоставленные Atomic*/Unsafe, чаще всего используются либо для того, чтобы с легкостью обнулить ссылки или сценарии производителя/потребителя, как оптимизированный механизм передачи сообщений для некоторого важного индекса.

ответ

3

Неустойчивое считывание - это именно то, что вы ищете.

Фактически, соответствующие летучие операции уже имеют семантику освобождения/получения (в противном случае это случается раньше, чем для парного волатильного чтения), но парные изменчивые операции должны быть не только последовательно последовательными (~ бывает-до), но также они должны быть в total synchronization order, вот почему StoreLoad барьер вставлен после volatile write: чтобы гарантировать общий порядок волатильной записи в разные местоположения, так что все потоки будут видеть эти значения в том же порядке.

Летучих считывающим приобретают семантику: proof от точки доступа кодового, также есть прямая рекомендация Дуга Lea в JSR-133 cookbook (LoadLoad и LoadStore барьеров после каждого летучего чтения).

также имеет семантику приобретения (proof), но используется для того, чтобы не читать значение (вы можете сделать то же самое с простым изменчивым чтением), но для предотвращения повторного чтения простых записей с последующим изменчивым чтением. Это используется в StampedLock для оптимистического чтения (см. StampedLock#validate реализация метода и использование).

Обновление после обсуждения в комментариях.

Давайте проверим, являются ли Unsafe#loadStore() и волатильными считаются одинаковыми и имеют семантику приобретения.

Я ищу точку доступа C1 compiler source code, чтобы избежать прочтения всех оптимизаций в C2. Он преобразует байт-код (фактически, а не байт-код, но его представление интерпретатора) в LIR (промежуточное представление низкого уровня), а затем переводит граф в фактические коды операций, зависит от целевой микроархитектуры.

Unsafe#loadFence является intrinsic который имеет _loadFence псевдоним. В C1 LIR generator он генерирует следующее:

case vmIntrinsics::_loadFence : 
if (os::is_MP()) __ membar_acquire(); 

где __ это макросы для генерации LIR.

Теперь давайте посмотрим на volatile, прочитав implementation в том же генераторе LIR. Он пытается вставить нулевые проверки, проверяет IRIW, проверяет, находится ли мы на x32 и пытается прочитать 64-битное значение (чтобы сделать некоторые магии с SSE/FPU) и, наконец, приводит нас к тому же коду:

if (is_volatile && os::is_MP()) { 
    __ membar_acquire(); 
} 

Ассемблерный генератор затем вставляет инструкции (-ы) для определения конкретной платформы here.

не глядя на конкретных реализаций (нет ссылки здесь, но все это можно найти в SRC/CPU/{$ cpu_model}/VM/c1_LIRAssembler _ {$ cpu_model} .cpp)

  • SPARC

    void LIR_Assembler::membar_acquire() { 
        // no-op on TSO 
    } 
    
  • x86

    void LIR_Assembler::membar_acquire() { 
        // No x86 machines currently require load fences 
    } 
    
  • Aarch64 (слабая модель памяти, барьеры должны быть предварительно послал)

    void LIR_Assembler::membar_acquire() { 
        __ membar(Assembler::LoadLoad|Assembler::LoadStore); 
    } 
    

    Согласно aarch architecture description такой membar будет скомпилирован как dmb ishld инструкции после загрузки.

  • PowerPC (также слабая модель памяти)

    void LIR_Assembler::membar_acquire() { 
        __ acquire(); 
    } 
    

    , который затем преобразуется в конкретные инструкции PowerPC lwsync.Согласно commentslwsync семантически эквивалентно

    lwsync заказов Магазин | Store, Load | Store, Load | Load, но не хранить | Load

    Но до тех пор, как PowerPC hasn» t любые более слабые барьеры, это единственный выбор для реализации семантики приобретения PowerPC.

Выводы

Летучие читает и Unsafe#loadFence() равны с точки зрения упорядочения памяти (но, возможно, не с точки зрения возможных оптимизаций компилятора), на наиболее популярных x86 это не-оп, и PowerPC является только поддерживаемая архитектура не имеет точных ограничений.

+0

Спасибо, что нашли время, чтобы написать ответ! Он заполнен приятной общей информацией, но, хотя это полезно для некоторых людей, оно не предоставляет каких-либо новых идей, не присутствующих в вопросе, или каких-либо оправданий, почему использовать специально изменчивую нагрузку (в отличие от 'loadFence()' или что-то еще). –

+0

Возможно, я не понимаю, что именно нужно. Почему он не отвечает? Не использовать ничего, кроме vread/'loadFence()'.Используйте volatile read, если вам нужна семантика/r для определенной переменной и 'loadFence()', если вам нужны только барьеры, например. для набора переменных или * до * чтения переменной, чтобы избежать определенного переупорядочения. Я отредактирую свой ответ, как только я пойму вас. Угадайте, что я могу удалить часть о no-ops и VH и добавить сравнение 'loadFence()' и vread, если вы этого хотите. – qwwdfsad

+0

Вы получаете семантику получения из нестабильного чтения и ' loadFence() ', и теоретически энергозависимая нагрузка может быть более дорогостоящей на некоторых архитектурах, поскольку она должна быть частью действий синхронизации. Так что это не так просто, как «просто использовать волатильное чтение», по крайней мере, не без более существенных аргументов. Я предположил, что volatile read и 'loadFence()' являются единственными жизнеспособными опциями, но я все еще надеюсь, что кто-то откроет еще один интересный вариант. –

1

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

Вы можете сделать это с помощью комбинации

int permits = theUnsafe.getInt(object, offset); 
if (!enough(permits)) 
    permits = theUnsafe.getVolatileInt(object, offset); 

Эта модель может быть использована в кольцевых буферов, чтобы минимизировать отток строк кэша.

+1

Этот фрагмент не очень полезен: летучая нагрузка и простая загрузка буквально одинаковы, так как x86 имеет общий порядок хранения, а большинство барьеров - нет-ops, единственное различие заключается в барьерах компилятора (не для этого фрагмента), так что на самом деле это всего лишь два простых чтения :) – qwwdfsad

+0

@qwwdfsad Вы предполагаете, что будет использоваться только x86 :) Это может быть безопасная ставка в большинстве случаев, но здесь это не так (я бегу на Aarch64, Raspberry Pi 3, чтобы быть конкретным). Кроме того, пока я/мы уже находимся в опасных водах с помощью 'sun.misc.Unsafe' и underspecified release/приобретаем, приятно рассуждать в общих чертах, когда это возможно. @PeterLawrey не может, чтобы две нагрузки были спекулятивно рухнуты в одну летучую нагрузку? Наверное, это не будет большой проблемой, мне просто интересно. –

+0

@DimitarDimitrov они могли бы быть объединены, но мы видели преимущества производительности при использовании энергонезависимых нагрузок, чтобы избежать владения линией кэша. –