Статьи

Аппаратная транзакционная память на Java или почему синхронизация снова будет крутой.

Аппаратная память транзакций может позволить нескольким потокам одновременно осуществлять спекулятивный доступ к одной и той же структуре данных и позволить протоколу когерентности кэша определять наличие конфликта. HTM стремится обеспечить вам масштабируемость точной зернистой блокировки, простоту конечно же зернистой блокировки и производительность, практически не зависящую от блокировки. Если ваша программа или библиотека была поддержана JVM, она была написана с блокировкой курса, это может означать, что ваше приложение может масштабироваться до гораздо большего числа ядер с небольшими изменениями.

Хотя добавить поддержку этого в C и C ++ нетривиально, добавление поддержки к собственному коду, сгенерированному JVM, может быть выполнено без изменения байтового кода.

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

Что такое аппаратная память транзакций и сколько она будет стоить? 

Аппаратная транзакционная память существует уже некоторое время, но до недавнего времени она не была основным потоком. Благодаря поддержке Intel в процессорах 4-го поколения i3 / i5 / i7 (Haswell) и процессорах семейства E5-2600 v2 HTM широко доступен для новых машин на базе процессоров Intel. Уже сейчас некоторые производители имеют больше моделей систем, использующих новые процессоры, чем предыдущие поколения. AFAIK, AMD планируют добавить эту функцию в ближайшее время.

Оборудование, которое вы купите и, возможно, уже сделаете, сделает это. Многие новые модели ноутбуков имеют процессоры Haswell, поскольку они значительно улучшили энергопотребление.

Как это может работать? 

Синхронизированные блоки часто используются в Java на индивидуальной основе. Чтобы упростить код, эти блокировки часто гораздо более грубые, чем оптимальные, например, Hashtable блокирует весь объект / карту для любой операции по сравнению с ConcurrentHashMap, который имеет точную зернистую блокировку. Писать точную зернистую блокировку гораздо сложнее, и поэтому она более подвержена ошибкам. Цель аппаратной памяти транзакций состоит в том, чтобы поддерживать детализированную блокировку курса, но получить преимущество точной блокировки. Это особенно полезно для кода, где повторная оптимизация кода нецелесообразна.

пример

private final Map map = new HashMap<>(); 
public synchronized PooledObject acquireObject(String key) {
    PooledObjectobject = map.get(key);
    if (object == null)
        map.put(key, object = new PooledObject());
    return map;
}

Вы можете ожидать общего случая

  • только читает карту
  • обновляет карту, но в разных местах например разные ключи.
  • редко пытается обновить один и тот же ключ в двух потоках одновременно.

Что бы вы хотели

  • параллельное выполнение между потоками.
  • очень маленький по сравнению с кодом без блокировки.
  • процессор или JVM, чтобы выполнить всю работу по оптимизации этого, то есть вам не нужно менять код.

Без HTM синхронизированный блок должен получить блокировку и обеспечить сериализованный доступ, даже если в большинстве случаев это операция чтения.

С HTM байт-код может быть превращен в псевдокод, как это

public PooledObject acquireObject(String key) {
    int code;
    do {
        xbegin();
        PooledObjectobject = map.get(key);
        if (object == null)
            map.put(key, object = new PooledObject());
        return map;
    } while((code = xend()) == RETRYABLE);
    if (code != DONE) {
        // take corrective action such as
        // obtain a normal lock and repeat
    }
}

Инструкция XEND в ЦП проверяет, была ли изменена какая-либо строка кеша, к которой был получен доступ, другим ЦП / потоком. Если нет, внесенные изменения фиксируются. В противном случае любые изменения сбрасываются, и цикл можно повторить.

Примечание. Откат транзакции означает отмену изменений и может даже привести к откату создания объекта, если он не имеет значительных побочных эффектов. Если он имеет побочные эффекты, существует инструкция XABORT, которая может инициировать прерывание транзакции, и потребуется выполнить код возврата.

Compare And Swap ограничен 64-битными, каков предел этих транзакций? 

The limit is the number of lines you can store in your L1 cache.  This is up to 32 KB.  If you have hyper threading this might be half i.e. 16 KB. Also, the L1 cache is 8 way associative so in the worst case 9 cache lines which hash to the same bucket could cause the transaction to fail. (less with hyper threading)  Never the less, it is much higher and far more flexible than CAS 64-bit or 2CAS which is 128-bit.

Writing this transaction locking structure with fall back add boilerplate and duplicate code in a language like C.

Conclusion 

The beauty of this pattern is it can be applied to Java code which has already been compiled and available as open source libraries.  Unlike C code which would need significant rework to exploit this functionality, Java programs could take advantage of HTM without re-compilation.  What we would need is a change in the JVM.

References

Benchmarks : Haswell’s TSX and Memory Transaction Throughput (HLE and RTM) from SiSoftware
Fun with Intel® Transactional Synchronization Extensions from Intel
Transactional memory support: the speculative_spin_mutex from Intel
Making Sense of the Intel Haswell Transactional Synchronization eXtensions by Johan De Gelas