Необходимым условием хорошей масштабируемости многопоточных приложений при увеличении числа процессоров является использование специализированных программных библиотек выделения памяти. В противном случае одновременное выделение памяти несколькими потоками становится узким местом при работе с динамическими структурами данных [1–3]. При этом хорошая библиотека обычно предоставляет возможность профилирования памяти, то есть позволяет выявлять основные характеристики работы с памятью, такие как общий объем выделенной и освобожденной приложением памяти, информация о фрагментации памяти, стеки вызовов, приводящие к выделению наибольшего объема памяти (далее – стеки выделения памяти). Результаты профилирования помогают оптимизировать приложения.
Рассматриваемая в данной работе библиотека выделения памяти jemalloc [4] является одной из наиболее популярных библиотек для многопоточных приложений, разрабатываемых под платформу Linux. Свою известность она получила благодаря реализованным в ней механизмам работы с памятью, обеспечивающим лучшую производительность приложения, и, как следствие, ее активному внедрению в серверные компоненты платформы Facebook [4, 5]. Jemalloc реализует функцию malloc() согласно стандарту POSIX и дает возможность профилирования памяти. Одно из преимуществ библиотеки в том, что при запус- ке приложения профилирование может быть включено (опция opt.prof=true), но неактивно (opt.prof_active=false). Это делает возможным запуск и остановку профилирования прямо во время работы приложения. Стоит отметить, что другие популярные средства профилирования, такие как valgrind [6] и TCMalloc [7], такой возможности не предоставляют. При активном профилировании информация о выделении памяти сохраняется при выделении каждых x Кб, называемых интервалом выборки. Интервал выборки x является настраиваемым параметром, его увеличение уменьшает влияние профилирования на характеристики оперативности приложения, но при этом результаты показывают не все стеки выделения памяти. Согласно документации jemalloc, размер интервала выборки по умолчанию составляет 512 Кб.
При работе приложений в промышленных условиях возникают ситуации, когда объем выделяемой памяти резко возрастает. Опыт показывает, что для систем алгоритмической торговли рост может составлять десятки гигабайт за несколько минут. Подобные случаи легко отслеживать без существенных накладных расходов [8], однако возникает необходимость поиска стеков, приводящих к выделению наибольшего объема памяти. Одним из решений является использование возможностей jemalloc запускать и останавливать профилирование памяти прямо во время работы приложения. При этом важно определить интервал выборки, позволяющий получить необходимую информацию при сохранении предоставляемых приложением гарантий к его временным харак- теристикам, что критично для широкого класса систем, включающих в себя системы алгоритмической торговли [9] и большинство встроенных систем, работающих в условиях дефицита ресурсов [10].
Основные цели данной работы – исследование изменения характеристик оперативности приложения и оценка получаемых результатов при активном профилировании и включенном сборе стеков выделения памяти (opt.prof_accum=true) на различных интервалах выборки. Кроме того, проверяется, оказывает ли влияние включенное, но неактивное профилирование памяти средствами jemalloc на время выполнения многопоточных приложений.
Синтетические тесты
Для анализа влияния активного профилирования и размера интервала выборки на среднее время t выполнения функции malloc() на основе популярного синтетического теста malloc-test [11] реализовано многопоточное приложение. В целях минимизации накладных расходов и повышения точности время t измеряется в тактах центрального процессора посредством чтения аппаратного счетчика TSC (Time Stamp Counter): операция чтения этого счетчика составляет всего одну машинную команду и не требует выполнения системного вызова. Во время выполнения приложения каждый поток в цикле выделяет и освобождает память блоками по 512 байт, глубина стека при этом варьируется в пределах десяти функций, что принято считать средней глубиной [12]. Доступ к выделенной памяти отсутствует, что исключает накладные расходы на операции со страницами виртуальной памяти. Так как функция malloc() вызывается в цикле одновременно несколькими потоками, это соответствует предельному случаю состязания (от англ. contention) за выделение памяти, что позволяет оценить наихудшие значения временных характеристик. Тест выполнен для случаев, когда профилирование отключено и когда оно выполняется с разными интервалами выборки.
В качестве тестового стенда для проведения всех экспериментов в данной работе использован сервер с двумя восьмиядерными процессорами Intel Xeon E5-2690 с тактовой частотой 2,9 GHz. Функция Hyper-Threading процессора отключена в целях повышения точности измерений. Сервер работает под управлением операционной системы Oracle Linux 6.5 с ядром linux-3.8 и библиотекой jemalloc-3.4.1, использующей для сбора стеков библиотеку prof-libgcc.
Результаты синтетического теста приведены на рисунке 1, график «∞» соответствует отключенному профилированию.
Как видно из графиков, при включенном профилировании при уменьшении интервала выборки среднее время t выполнения функции malloc() увеличивается, так как возрастают накладные расходы на сохранение стеков выделения памяти. На интервалах более 128 Кб это увеличение в худшем случае не превышает 25 % вне зависимости от числа потоков n, одновременно осуществляющих выделение памяти. Однако на интервале выборки в 64 Кб t увеличивается до 3,5 раза и сильно зависит от числа потоков, то есть приложение становится плохомасштабируемым. Приведем зависимость значения t (%) для 16 потоков от интервала выборки x относительно случая, когда профилирование выключено:
Интервал выборки x, Кб
|
Увеличение t, %
|
64
|
203
|
128
|
25
|
256
|
21
|
512
|
9
|
1024
|
4
|
2048
|
2
|
Временные характеристики приложения в значительной степени зависят от объема выделенной памяти и характера работы с ней, числа используемых страниц виртуальной памяти и после- довательности доступа к ним, эффективности использования кэша процессора [13]. Поэтому на практике результаты синтетических тестов могут значительно отличаться от результатов, характерных для реальных приложений. В связи с этим дальнейшие эксперименты выполнены на базе промышленной системы.
Профилирование памяти в системе алгоритмической торговли
В экспериментах использовано приложение для системы алгоритмической торговли Tbricks [9], которое при получении с биржи котировок на ценные бумаги выполняет их обработку и отправляет соответствующий заказ на покупку или продажу. Для данного приложения основной характеристикой оперативности является задержка между получением котировок и отправкой заказа. В качестве времени получения котировок и отправки заказа используется время получения/отправки соответствующего сетевого пакета. В отличие от синтетических тестов масштаб времени составляет порядка десяти микросекунд, кроме того, приложение состоит из нескольких процессов, что делает использование счетчика TSC непрактичным для измерений времени. Поэтому отметки времени получаются с помощью метода аппаратного захвата (от англ. hardware capture), позволяющего измерять время получения/отправки сетевых пакетов без влияния на характеристики оперативности системы [14]. Аппаратный захват осуществляется с помощью специальной сетевой карты Emulex Endace DAG 7.5G2 [15]. Тестовая система настроена таким образом, что приложение непрерывно получает котировки, выполняет их обработку и в ответ отсылает заказы. Все измерения выполнены и усреднены для ста тысяч отправленных заказов.
Результаты экспериментов показали, что при запуске приложения с включенным, но неактивным профилированием величина задержки не изменяется. При активном профилировании наблюдается увеличение средней задержки в зависимости от значения интервала выборки. Приведем значения увеличения средней задержки относительно случая, когда профилирование выключено:
Интервал выборки, Кб
|
Увеличение задержки, %
|
64
|
154
|
128
|
103
|
256
|
75
|
512
|
42
|
1024
|
18
|
2048
|
5
|
Согласно полученным результатам, активное профилирование может значительно влиять на быстродействие приложения: так, при выборке на интервале в 64 Кб задержка составляет порядка 150 % от времени, измеренного без профилирования памяти; на интервалах в 1024 Кб и 2048 Кб ухудшение составляет 18 % и 5 % соответственно. Дальнейшее увеличение интервала выборки приводит к уменьшению накладных расходов, связанных с профилированием.
На рисунке 2 приведены графики увеличения задержки t для синтетического теста при выполнении 16 потоков и эксперимента, выполненного на приложении для системы алгоритмической торговли. Как видно из графиков, влияние профилирования на быстродействие приложения при интервалах выборки от 128 Кб до 2048 Кб гораздо выше, чем показали синтетические тесты. Это связано с тем, что в синтетическом тесте увеличение времени выполнения определяется исключительно накладными расходами на сбор стеков, в то время как в реальном приложении сбор стеков вызывает дополнительную задержку в полезных вычислениях приложения, обусловленную снижением эффективности использования кэша процессора и виртуальной памяти. Однако при интервале в 64 Кб ухудшение быстродействия в синтетическом тесте значительно существеннее. Это свидетель- ствует о том, что выделение памяти в реальном приложении в среднем не достигает ситуации предельного состязания. Данные результаты ука- зывают на необходимость проведения подобных экспериментов на системах, где планируется выполнять профилирование.
Анализ исходного кода jemalloc показывает, что наибольшие накладные расходы приходятся на сохранение стеков выделения памяти. При выполнении экспериментов, описанных выше, для получения стеков в jemalloc использовалась библиотека prof-libgcc. Альтернативой prof-libgcc является другая популярная библиотека libunwind [16]. Результаты экспериментов, выполненных с ней, в среднем на 20 % хуже: так, на интервале выборки в 512 Кб ухудшение при использовании libunwind составляет 65 %, что на 23 % больше, чем с prof-libgcc. Поэтому при профилировании памяти рекомендуется использовать библиотеку jemalloc совместно с библиотекой prof-libgcc.
Для оценки качества результатов, полученных при профилировании, использован следующий критерий: общий объем памяти по всем выявленным стекам должен составлять не менее 90 % от всей памяти, выделенной приложению во время профилирования. Согласно полученным результатам, данное условие выполняется на интервалах, меньших или равных 2048 Кб. На интервалах, больших 2048 Кб, качество результатов профилирования резко снижается, что не позволяет эффективно использовать их для оптимизации приложения. Поэтому авторы рекомендуют взять интервал выборки в 2048 Кб.
Таким образом, в данной работе исследовано влияние профилирования памяти средствами библиотеки jemalloc на время выполнения многопоточных приложений. Для этого разработан синтетический тест и выполнены эксперименты с использованием приложения, реализованного для системы алгоритмической торговли Tbricks. При сравнении полученных результатов установлено, что популярные синтетические тесты не обладают достаточной степенью адекватности для анализа влияния профилирования памяти на характеристики оперативности реального приложения.
С помощью приложения алгоритмической торговли в данной работе показано, что использование jemalloc с включенным, но неактивным профилированием памяти позволяет работать с приложением во время промышленной эксплуатации без ухудшения его быстродействия. Это дает возможность в случае необходимости запускать и останавливать профилирование без перезапуска приложения, например, при резком увеличении используемой им памяти. При активном профилировании быстродействие приложения зависит от интервала выборки: уменьшение интервала приводит к снижению быстродействия. Согласно полученным результатам, при использовании рекомендованного авторами значения интервала выборки в 2048 Кб результаты профилирования содержат информацию не менее чем о 90 % всей выделенной памяти, а снижение оперативности приложения составляет порядка 5 % по сравне- нию с отключенным профилированием, благодаря чему можно анализировать выделение памяти в приложениях, работающих в промышленных системах.
Авторы выражают признательность коллегам Святухиной М.А., Осипову Е.В. и Шубину Д.С. за плодотворное сотрудничество по тематике работы и полезные замечания.
Литература
1. Kahan S., Konecny P. "MAMA!": a memory allocator for multithreaded architectures. Proc. 11th ACM SIGPLAN Sym- posium on Principles and Practice of Parallel Programming (PPoPP'06). 2006, pp. 178–186.
2. Gidenstam A., Papatriantafilou M., Tsigas P. Allocating memory in a lock-free manner. Proc. 13th Annual European Conf. on Algorithms (ESA'05). 2005, pp. 329–342.
3. Berger E., Zorn B., McKinley K. Composing high-performance memory allocators. ACM SIGPLAN Notices, 2001, vol. 36, no. 5, pp. 114–124.
4. Evans J. A scalable concurrent malloc (3) implementation for FreeBSD. Proc. of the BSDCan Conf., 2006.
5. Evans J. Scalable memory allocation using jemalloc, Notes by Facebook Engineering, 2011. URL: https://www.facebook.com/ notes/facebook-engineering/scalable-memory-allocation-using-jemalloc/480222803919 (дата обращения: 13.12.2014).
6. Valgrind (home page). URL: http://valgrind.org (дата обращения: 13.12.2014).
7. Google gperftools (home page). URL: https://code.google. com/p/gperftools (дата обращения: 13.12.2014).
8. Gregg B. Systems Performance: Enterprise and the Cloud. Pearson Education, 2013, 792 p.
9. Tbricks Company. URL: https://www.tbricks.com (дата обращения: 13.12.2014).
10. Малиновский А.С., Шмырев Н.В., Костюхин К.А. Организация профилирования сложных систем в условиях дефицита ресурсов // Программные продукты и системы. 2008. № 4. С. 17–20.
11. Lever C., Boreham D. Malloc() performance in a multithreaded Linux environment. Proc. Annual Conf. on USENIX Annual Technical Conf. (ATEC'00). 2005, pp. 56–56.
12. Perks O., Hammond S., Pennycook S., and Jarvis S. WMTrace – A Lightweight Memory Allocation Tracker and Analysis Framework. Proc. of the UK Performance Engineering Workshop (UKPEW’11). Bradford, UK, 2011, pp. 6–24.
13. Molka D. et al. Memory performance and cache coherency effects on an Intel Nehalem multiprocessor system. Parallel Architectures and Compilation Techniques, 2009. PACT'09. Proc. 18th Intern. Conf., IEEE, 2009, pp. 261–270.
14. Schneider F., Wallerich J., Feldmann A. Packet capture in 10-gigabit ethernet environments using contemporary commodity hardware. Passive and Active Network Measurement, Springer Berlin Heidelberg, 2007, pp. 207–217.
15. Emulex (EndaceDAG data capture cards). URL: http://www.emulex.com/products/network-visibility-products-and-services/endacedag-data-capture-cards/specifications (дата обращения: 13.12.2014).
16. Libunwind library (home page). URL: http://www.nongnu. org/libunwind (дата обращения: 13.12.2014).