5 октября 2012 г.

Thread affinity binding

По следам выступления на jug.ru:

Меня спрашивали, как я задаю affinity потокам в яве в бенчмарках. Начнем с того, что из чистой явы это сделать нельзя. Но если нельзя, а очень хочется — то таки можно. Долгое время я пользовался на скорую руку сварганенным методом, подсмотренным где-то на stackoverflow. Потом какое-то время участвовал в разработке библиотеки Java-Thread-Affinity, от Питера Лоурея. В принципе, она довольно неплоха, и я могу ее вполне рекомендовать.

Однако, на мой взгляд, она предлагает слишком высокоуровневый подход. Мне самому кажется более привлекательной идея сначала разработать достаточно универсальное низкоуровневое API — просто удобным образом оттранслировать в яву стандартную функциональность ОС. А уж потом на этом фундаменте экспериментировать с различными вариантами высокоуровневого API. Потому что мне видится, что высокоуровневых API может быть несколько вариантов, под разные задачи, а вот низкоуровневое определяется системными возможностями, и разнообразие здесь существенно меньше, и легче найти такой вариант, который будет подходить для всех. Так что еще какое-то время назад я озадачился разработкой low-level affinity API.

Текущий (по-правде говоря — довольно черновой) вариант можно посмотреть здесь.

Как подключить?
Через maven:
<repository>
   <id>java-affinity-binding.googlecode.com</id>
   <name>AB Repository for Maven</name>
   <url>http://java-affinity-binding.googlecode.com/svn/repo/</url>
</repository>
...
<dependency>
   <groupId>org.affinity</groupId>
   <artifactId>affinity</artifactId>
   <version>0.1.1-SNAPSHOT</version>
</dependency>
Как пользоваться?
Стартовая точка — статическая фабрика ThreadAffinityUtils. Через интерфейс CPULayoutService можно получить список доступных вычислительных блоков (CPU), а ThreadAffinityService позволяет задать список ядер, на которых текущий поток может выполняться.
final ThreadAffinityService affinityService = ThreadAffinityUtils.defaultAffinityService();
if( affinityService.isActuallyAvailable() ) {
   final CPULayoutService layout =ThreadAffinityUtils.defaultLayoutService();
   final CPU cpu = layout.cpu( 0 );
   affinityService.restrictCurrentThreadTo( cpu );
} else {
   System.out.println( "Affinity binding is not supported by current OS" );
}
Как это работает?
Да просто дергаем sched_setaffinity на POSIX-системах, и SetThreadAffinityMask на Windows. Дергаем через JNA, это позволяет не связываться с JNI напрямую, и не возиться самим с поддержкой различных платформ и компиляцией бинарников под каждую (этот гемморой берут на себя авторы JNA, за что им большое человеческое спасибо, искренняя уважуха, лучи добра и дай бог жену хорошую)
Но ведь JNA медленнее JNI?
Да, медленнее. Но мне кажется, здесь это не принципиально — часто перепривязывать потоки с ядра на ядро не очень хорошая идея, скорее всего эти операции вообще будут делаться всего несколько раз за все время жизни потока. Так что несколько дополнительных миллисекунд погоды не делают
Где это работает?
На Windows и Linux точно. По-идее, будет работать так же на любых POSIX системах, но есть свои нюансы. Например, на Mac OS не работает — несмотря на то, что sched_setaffinity там есть, но семантика у него иная, нам не подходящая.
Ограничения
В текущей версии задавать привязку можно только для текущего потока (того, который вызывает .restrictCurrentThreadTo()). Для вмешательства в другой поток нужно узнать его native thread id, а как это сделать "снаружи" этого потока я пока не нашел. Thread.getId() возвращает просто порядковый номер потока в JVM, он не имеет ничего общего с native tid. Конечно, узнать список tid-ов текущего процесса можно — но ведь надо как-то отобразить их на джавовские потоки... В общем, привязать, скажем, GC к какому-то ядру пока не получится, получится только те потоки, в код которых вы можете вставить соответствующие вызовы

Код использовался в бенчмарках, но еще ни разу не использовался в рабочих приложениях. Если что — похороны за ваш счет.

2 комментария:

  1. Еще одна JavaAffinity библиотека. Здорово.

    По-поводу native thread ID лучшее, что я нашел, это идти по стопам jstack. Вот тут хорошее описание:

    https://gist.github.com/843622

    ОтветитьУдалить
  2. Вот это "еще одна" меня самого смущало. Куча людей делают свои костыли, но такого, чтобы взять и пользоваться всем -- нет. Жалко

    Ссылка очень полезная, спасибо. Я не нашел такой информации.

    ОтветитьУдалить