Некоторое время назад ко мне в руки попал компьютер на базе Intel XEON D-1540 с поддержкой расширения TSX. Я давно уже хотел устроить хотя бы небольшое тестирование этого расширения, т.к. оно выглядит крайне интересно. Причём интерес вызывает как “бесплатная оптимизация” многопоточных приложения через HLE-префиксы (тем более что она не ломает обратную совместимость кода), так и полноценное использование транзакционной памяти в сложных сценариях.
Так уж получилось, что объём свободного времени на проведение тестирования TSX у меня был крайне ограниченным, да к тому же пришлось ещё и самостоятельно добавлять поддержку соотв. инструкций в компилятор. Из-за этого пришлось ограничиться простым тестированием производительности двусвязного списка со случайным добавлением/удалением элементов. В процессе тестирования проверялась зависимость общей скорости выполнения работы в операциях (удаление+добавление) в зависимости от следующих параметров:
- Алгоритм обеспечения потоко-безопасности двусвязного списка
- Кол-во потоков, осуществляющее параллельный доступ к списку
- Размер тестового массива (влияет на процент конфликтов при доступе к списку)
Рассмотрим используемые алгоритмы обеспечения потоко-безопасности двусвязного списка. Во первых, это мой любимый FIFO-spinlock, который основывается на получении очереди доступа с помощью LOCK XADD, дальнейшем ожидании своей очереди в цикле с PAUSE и LOCK INC по завершению доступа, на доступ ко всему списку. Во вторых, это обычный spinlock на основе LOCK BTS/BTR на доступ ко всему списку. В третьих это более сложный алгоритм на основе LOCK XADD, способный блокировать отдельные элементы (реально блокируются ещё и оба соседа и тут ещё возможны доработки, хотя принесут ли они пользу пока не ясно). Также для второго и третьего метода имеется вариант с использованием HLE. И наконец, последний, шестой алгоритм – защита доступа с помощью инструкций XBEGIN/XEND.
Перед тем как приступить к рассмотрению результатов многопоточного доступа давайте взглянем на скорость работы каждого из этих алгоритмов в однопоточном режиме для оценки объёма накладных расходов:
Всё вполне ожидаемо – чем проще метод, тем меньше накладные расходы на него. Перед тем как начать рассматривать основные данные экспериментов хочу отметить одну особенность. Методы LocalLock-HLE и TSX демонстрировали очень низкие результаты (раза в 3 ниже нем при 3-х потоках) при работе в два потока, а механизм подсчёта процента конфликтов давал очень большой разброс. Я склонен полагать, что эти проблемы связаны с тем, что тест абсолютно синтетический, а потому исключил данные по работе в два потока из финальных результатов, т.к. они полностью выбиваются из общей картины.
Уже можно сделать кое-какие выводы. Во первых FIFO-spinlock явно проигрывает обычному (что лично для меня явилось полной неожиданностью), несмотря на то, что в нём всегда две операции с префиксом LOCK на каждый доступ к защищаемому ресурсу. Во вторых можно положительно оценить Hardware Lock Epsilon, особенно для общей блокировки (от 31 до 135% прироста). Для локальной блокировки эффект конечно меньше, но тоже заметен (от 15.5 до 40% прироста).
Что же касается использования TSX, то тут всё не так однозначно. Использовать его при высокой вероятности конфликтов строжайше противопоказано, т.к. происходит критическое падение производительности (некоторые эксперименты вообще “зависали”). С другой стороны при проценте конфликтов 10 и менее он вполне конкурентоспособен, а при 5% и меньшем кол-ве конфликтов демонстрирует просто таки феноменальные результаты (сложно спорить с эффективность механизма поддержания когерентности кешей процессора). Ну и нельзя списывать со счётов тот факт, что TSX позволяет строить значительно более сложные алгоритмы, чем могут себе позволить механизмы вроде локальных блокировок.