Что меня заинтересовало в его реализации -- так это дизайн. Вместо тривиального setAffinityMask/getAffinityMask он ввел абстракцию "блок кода, выполнение которого привязано к ядру". Выглядит это так:
public void run() { final AffinityLock al = AffinityLock.acquireLock(); try { //performance-critical block of code } finally { al.release(); } }С текущей реализацией есть несколько проблем.
Во-первых, реализация-то есть только для unix/linux -- для платформ, поддерживающих sched_setaffinity. Моя попытка портировать на винду сходу не удалась -- SetThreadAffinity я еще нашел, а вот где взять GetThreadAffinity -- так и не разобрался. Поскольку у меня еще и нет винды для тестов -- эту часть я решил отложить.
Попытка портировать на Мак (что для меня очень актуальна) обломалась еще более серьезно -- на Маке вообще нет возможности назначать привязку. Интерфейс взаимодействия с планировщиком потоков, который представляет MacOS X начиная с 10.5+ позволяет только давать рекомендации планировщику, относительно того, что данные потоки неплохо бы поместить "как можно ближе" друг к другу -- в смысле разделяемых кэшей. Это практически обратное тому, что хочется.
Во-вторых, сама по себе идея запрещать relocation для preformance-critical (скорее, latency-critical, если уж быть точным) потоков -- она, конечно, хороша. Но это далеко не все, чего бы хотелось от интерфейса управления привязками. Навскидку, я могу придумать такие пункты:
- Запрещать перемещение потока во время выполнения определенного кода. Это то, что делает библиотека Питера. Т.е. мне пофигу, на каком ядре будет выполняться код, но я хочу, чтобы это ядро было фиксированным все время выполнения данного участка кода. Что я пытаюсь этим выиграть? -- я хочу не тратить время на смену контекста и прогревание кэша при перемещении потока.
- Закрепить ядро за конкретным потоком эксклюзивно. Расширение предыдущего пункта -- мало того, что я хочу запретить перемещение моего потока с текущего ядра, так я еще и хочу запретить перемещение любого другого потока на это ядро (и, разумеется, выпинать с этого ядра те потоки, которые уже на нем сидят сейчас). Что я хочу выиграть здесь? -- в основном, я хочу получить весь кэш ядра только для своего потока.
Сложность здесь в том, что внутри работающей JVM есть хуева туча потоков, выполняющих всякие системные надобности. Начиная от совсем внутренних потоков JVM, типа GC/Finalizer и JIT-compiler, и кончая потоками, запускаемыми внутри стандартной библиотеки -- типа всяких таймеров, чистильщиков очередей слабых ссылок, и прочего мусора. Получить доступ к этим потокам, чтобы можно было явно запретить им использовать определенные ядра -- довольно непросто.
Немного усиленный вариант -- запретить использование не только конкретного логического ядра, а именно физического -- т.е. в случае hyper-threading запретить использование парного аппаратного потока. Смысл такой: "а ну отъебитесь все от моего кэша".
Дополнительная, хоть и сомнительная, опция -- распространить эту эксклюзивную привязку на всю систему. То есть не только в рамках текущего процесса JVM все потоки кроме выделенного обходят данное ядро стороной -- но и глобально во всей системе никто более на это ядро не претендует. Сомнительна эта опция потому, что кажется не очень хорошей идеей позволять конкретному приложению вмешиваться в работу планировщика потоков на уровне всей системы -- не по чину ему. - Связать группу потоков (идея из MacOS X API). Т.е. я хочу, чтобы определенная группа потоков была расположена на таких ядрах, коммуникация между которыми будет максимально дешева. При этом мне все равно, какие именно это ядра.
Вопрос состоит в том, как именно максимально абстрактно соединить в одном API все эти опции...
P.S. Как мне подсказывают, это мой сотый пост за время ведения блога. Ура мне!
Молодец, рад за вас, и рад тому, что поделились своими наработками.
ОтветитьУдалитьУ меня вопрос, на который я так и не смог ответить - и не смог понять даже после ответа Peter'а - а именно дизайн - абстракция "блок кода, выполнение которого привязано к ядру". И как это дело реализовано, какие и откуда идут гарантии из того, что получили AffinitySupport.getAffinity() - откуда гарантии, что affinitySupport.assignedThread привяжет поток к ядру ? или может быть вообще код делает другое ?
Ну, вообще-то меня тоже заинтересовал этот участок кода в AffinityLock. Я-то начал разбираться с библиотекой со стороны реализации взаимодействия с ОС -- эта часть мне казалась более интересной. До верхнего уровня я только сейчас дошел. Оставил Питеру запрос в баг-трекере, посмотрим...
ОтветитьУдалитьfixed. теперь картина сходится и более понятно, как оно работает.
ОтветитьУдалитьСорри за ламерский вопрос...А как можно закрепить процессор (ядро проца) за потоком из user space, в котором работает JVM? Т.е. ты закрепляешь все доступные ядра (не кроме одного, пускай) за своими потоками, и ядро линусковое потом не может их оттуда выкинуть? Это звучит как-то странно.
ОтветитьУдалитьЯ не уверен, что правильно понял вопрос -- как _можно реализовать_ такую привязку? Я пока не знаю. Но в некоторых случаях хотелось бы ее иметь. Лично мне было бы полезно для бенчмарков -- они так дают более стабильные результаты.
ОтветитьУдалить