openjdk.ruOpenJDK на русском

JEP 285: Spin-Wait Hints

Подсказки для активного ожидания

AuthorsGil Tene, Ivan Krylov
ОтветственныйPaul Sandoz
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск9
Компонентcore-libs / java.lang
Обсуждениеcore dash libs dash dev at openjdk dot java dot net
ТрудоёмкостьS
ДлительностьS
РецензентыDoug Lea, Paul Sandoz
ОдобренBrian Goetz
Создан2016/01/20 14:25
Обновлён2023/01/10 06:21
Задача8147832

Аннотация

Определить API, с помощью которого Java-код может сообщить, что выполняется цикл активного ожидания.

Цели

Определить API, с помощью которого Java-код мог бы сообщить среде выполнения, что находится в цикле активного ожидания. API будет исключительно подсказкой и не будет налагать никаких требований к семантике поведения (например, пустая операция — допустимая реализация). Дать JVM возможность использовать особое поведение для циклов активного ожидания, которое может быть полезно на некоторых аппаратных платформах. Предоставить в JDK как пустую реализацию, так и intrinsic-реализацию и продемонстрировать выигрыш при выполнении хотя бы на одной из основных аппаратных платформ.

Что не является целью

Рассмотрение подсказок для производительности помимо циклов активного ожидания не является целью. Другие подсказки для производительности, например подсказки предварительной выборки (prefetch), выходят за рамки этого JEP.

Мотивация

Некоторым аппаратным платформам полезно, когда программа сообщает, что выполняется цикл активного ожидания. Можно наблюдать некоторые типичные преимущества при выполнении:

  1. Благодаря ряду факторов при использовании подсказки активного ожидания может сократиться время реакции цикла активного ожидания, а значит, и задержки между потоками в ситуациях активного ожидания;

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

Хотя длительное активное ожидание в целом обычно не рекомендуется в программировании в пользовательском режиме, кратковременное активное ожидание перед блокировкой — распространённая практика (как внутри JDK, так и за его пределами). Кроме того, поскольку вычислительные платформы с большим числом ядер широко доступны, многие приложения, чувствительные к производительности и задержкам, например Disruptor, используют шаблон, в котором для функции, критичной к задержкам, выделяется отдельный поток с активным ожиданием, и при этом активное ожидание тоже может быть длительным.

В качестве практического примера и сценария использования: современные процессоры x86 поддерживают инструкцию PAUSE, которой можно обозначить активное ожидание. Использование инструкции PAUSE заметно сокращает время передачи сигнала между потоками туда и обратно. Благодаря своим преимуществам и повсеместным рекомендациям инструкция x86 PAUSE широко применяется в спин-блокировках ядра, в библиотеках POSIX, которые выполняют эвристическое активное ожидание перед блокировкой, и даже в самой JVM. Однако, поскольку нет способа сообщить, что цикл в Java выполняет активное ожидание, обычный Java-код не может воспользоваться её преимуществами.

Приводим конкретные подтверждающие данные: в простых тестах на E5-2697 v2, в которых измерялась задержка передачи сигнала туда и обратно между двумя потоками, взаимодействующими через активное ожидание на volatile-поле, эта задержка заметно снизилась на 18–20 нс во всём широком диапазоне процентилей (от 10-го до 99,9-го процентиля). Это сокращение может означать улучшение до 35–50 % задержки взаимодействия между потоками в лучшем случае, например когда два потока с активным ожиданием выполняются на двух аппаратных потоках, которые делят одно физическое ядро процессора и кэш данных L1. Полный листинг теста можно найти здесь.

На изображении выше показан пример измерения задержки: задержка реакции цикла активного ожидания с intrinsic-вызовом spinLoopHint() (который заменяется инструкцией PAUSE) сравнивается с тем же циклом, выполненным без инструкции PAUSE. Также приведены результаты измерения времени, которое занимает сам вызов System.nanoTime(), используемый для измерения времени.

Описание

Мы предлагаем добавить в JDK метод, сообщающий, что выполняется цикл активного ожидания: java.lang.Thread.onSpinWait().

Пустой метод был бы допустимой реализацией метода java.lang.Thread.onSpinWait(), но для аппаратных платформ, которым это полезно, очевидная цель — intrinsic-реализация. В рамках этого JEP мы намерены сделать intrinsic-реализацию для x86 в JDK. Прототип реализации уже существует, и результаты первоначального тестирования выглядят многообещающе. Ссылки на webrev с предлагаемыми изменениями в библиотеках классов и JVM см. в задаче JBS JDK-8147844.

Альтернативы

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

Можно было бы попытаться научить JIT-компиляторы самостоятельно распознавать циклы активного ожидания и автоматически добавлять инструкции процессора, подсказывающие об активном ожидании, без каких-либо подсказок в Java-коде. Мы предполагаем, что сложность автоматического и надёжного обнаружения активного ожидания, а также вопросы о возможных компромиссах при использовании подсказок на некоторых платформах значительно отсрочили бы появление работоспособных реализаций.

Тестирование

Тестирование «обычной» пустой реализации, очевидно, будет довольно простым.

Мы считаем, что, учитывая очень малый объём этого API, тестирование intrinsic-реализации для x86 тоже будет несложным. Мы ожидаем, что тестирование будет сосредоточено на проверке как корректности генерируемого кода, так и выигрыша в задержке от использования подсказки активного ожидания с intrinsic-реализацией.

Если этот API будет принят как API Java SE (например, для включения в пространство имён java.* в будущем Java SE 9 или Java SE 10), мы планируем разработать для него соответствующие тесты TCK, которые, возможно, войдут в Java SE TCK.

Риски и допущения

«Обычная» пустая реализация, очевидно, несёт довольно низкий риск. Intrinsic-реализация для x86 потребует изменений в нескольких компонентах JVM и поэтому связана с некоторыми рисками, но не бо́льшими, чем другие простые intrinsic-реализации, добавленные в JDK.