Low-latency оптимизации на всех уровнях торговой системы

В предыдущей статье я говорил об оптимизации вообще, а теперь приведу несколько советов по low-latency оптимизациях в Java-приложениях на всех уровнях торговой системы:

  • аппаратный уровень: процессор, сетевой интерфейс, коммутация
  • операционная система
  • настройка JVM
  • оптимизации на уровне приложения

На аппаратном уровне

BIOS: Отключить в BIOS энергосберегающий режим. Все ядра процессора должны работать в идеале в Turbo режиме. Изнашивается от такой работы процессор быстрее, но ради прибыли ничего не жалко.

Процессор: чем новее, тем лучше. Чем производительнее, тем лучше. Выбирайте вариант процессора выдающий более высокую частоту и допускающий разгон процессора и каналов памяти до еще более высоких частот. Есть конечно риск перестараться и спалить процессор, но кто не рискует — тот не пьет шампанского. Совеременные процессоры допускают разгон и работают вполне стабильно, если не выжимать из них последнее.

Чаще всего выбирают процессоры Intel семейства Xeon, так как они стандартно ставятся в серверные решения от ведущих поставщиков производительных решений: большое количество ядер, поддержка памяти с коррекцией ошибок, поддержка сопряжения большого количества процессоров, высокие тактовые частоты, минимум лишнего, что на сервере и не нужно. Недавно AMD-процессоры тоже стали предлагать — получается дешевле, но как-то Intel пока еще держит планку.

Начинайте свой выбор минимум с Sandy Bridge и выше. Существует поверие, что каждое новое поколение процессоров Intel прибавляет 15%-17% производительности. Мои измерения с помощью JMH одного и того же кода на разных машинах вроде бы подтверждают это утверждение. Здесь трудно что-то советовать, так как все зависит от бюджета вашего проекта.  Уверяю вас даже из Sandy Bridge 7-летней давности можно выжать рекордные latency, если хорошо постараться. Помните 3 ГГц — это (очень приблизительно) 3 миллиарда инструкций в секунду! На каждом ядре многоядерного процессора! Это очень много, даже в 2019 году. Какие такие сложные вычисления в вашем приложении могут потребовать 3 миллиардов инструкций, чтобы оно выполняло его 1 секунду?

Какую бы архитектуру процессора вы не выбрали, очень советую основательно разузнать по документации все её характеристики, размеры кэша всех уровней, особенности реализации. Это пригодится при написании кода по принципу mechanical sympathy: часто оказывается что код, который работает быстро на, скажем, стареньком Sandy Bridge, начинает тормозить на более свежем Haswell.

Сетевой интерфейс: В современных серверах PIC-слоты, куда ставятся сетевые карты, распределены так, что часть из них находятся «ближе» к CPU0, а другие — к CPU1. Сетевая карта должна располагаться в слоте, который ближе к процессору, на котором будет крутиться приложение.

PCI-слоты могут поддерживать разное количество lane. При установке сетевой карты надо убедиться не только в том, что вы ставите карту в слот, который ближе к нужному вам процессору, но и в слот, количество lane, которого совпадает с lane вашей сетевой карты.

Выбирайте самую быструю сетевую карту. Самым популярным выбором являются сетевые карты Solarflare, которые специально заточены под low-latency. Кроме того, они снабжаются высокоэффективными драйверами, позволяющими ускорять передачу данных между сетью и приложением.

Некоторые Solarflare имеют также FPGA модули, которые позволяют на аппаратном уровне фильтровать пакеты. Но это уже высший пилотаж программирования на уровне VHDL. Главный конкурент Solarflare — компания Mellanox, которая, увы, пока не дотягивает.

На уровне операционной системы

Выбор ОС: Наиболее популярной операционной системой является Linux. Из всех дистрибутивов Linux наиболее популярным являются Red Hat Enterprise Linux (RHEL) и его открытый вариант CentOS. Версия CentOS тоже играет роль, но нет пока никаких доказательств, что свежая версия всегда лучше предыдущей. Скорее бывает даже наоборот, новая версия дистрибутива приносит с собой какие-то новые настройки, которые могут снижать производительность системы, и если вы не догадываетесь о существовании этих настроек, после обновления ОС ваша торговая система будет работать медленнее.

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

Используйте самые свежие драйвера для сетевой карты.

Выбор драйверов: Если используется Solareflare сетевая карта, воспользуйтесь драйверами и функцией Open Onload, которая позволяет обходить TCP/IP стек операционной системы и ее системные вызовы при передаче данных, и позволяет сетевой карте писать данные прямо в user-space вашего приложения, а вашему приложению — напрямую общаться с стевой картой, минуя ядро операционной системы. Этот механизм называется TCP-offload или kernel bypass. Очень эффективно снижает зажержки на копирование данных из буфера в буфер между user-space и kernel-space, переключение контекста и системные вызовы.

Отключите у операционной системы все некритические и ненужные сервисы.

Отключите у операционной системы заплатки от Spectre и Meltdown. Заплатки предназначены для защиты систем, где работают несколько пользователей. Вы своей торговой системой владеете безраздельно. Защищите ее от доступа извне другими способами, а в самой системе снимите все барьеры. Заплатки от Spectre и Meltdown легко могут сьесть 20-30% производительности вашего приложения так как препятствуют процессору использовать многие техники, ускорявшие исполнение кода.

Изоляция процессов: Современные сервера имеют несколько многоядерных процессоров — чаще два. Идеально — запускать приложение только на одном процессоре, отдавая второй CPU под задачи и сервисы операционной системы. Изоляция процесса осуществляется с помощью утилит cgroups и taskset.

Оптимальный доступ к памяти: Двухпроцессорная архитектура наверняка будет организована как NUMA: вся память поделена на два банка, и у каждого процессора есть свой банк памяти, который ближе к нему. Очень важно запретить приложению аллоцировать память, которая расположена ближе ко второму процессору. Доступ к такой памяти будет медленнее и требует доступа к шине между процессорами. Делается это несколькими способами:

  • с помощью cgroups
  • с помощью утилиты numactl
  • с помощью параметра JVM -XX:UseNUMA, сообщающего JVM, что процесс работает на NUMA-машине

На уровне JVM

Настройки JVM с помощью стартовых опций я описываю в отдельной статье.

  • настройки heap
  • настройки сборщика мусора
  • настройки JIT-компилятора
  • прочие настройки

На уровне самого Java-приложения

Количество потоков: При проектировании приложения, сопоставляйте количество активных потоков с количеством ядер процессоров системы. Какие-то потоки у вас будут CPU-bound, какие-то — IO-bound. IO-bound потоки часто блокируются в ожидании данных из сети, или при отправке данных в сеть. Большую часть своей «жизни» они проводят в ожидании и не занимают сильно процессор, а вот CPU-bound потоки — это потоки делающие большие сложные вычисления, и им, конечно, нужны вычислительные мощности процессора в полной мере. Если таких CPU-bound потоков в вашем приложении больше, чем ядер процессора, какой-то поток будет постоянно «голодать«, так как его постоянно будет вытеснять другой CPU-bound поток.

Учитываться должны не только потоки самого приложения, но и сервисные потоки самой JVM. Поэтому очень желательно иметь на своем сервере процессоры с большим количеством ядер.

Thread affinity: важно прикрепить критически важные потоки к определенному ядру процессора, запретить операционной системе переносить этот поток на другие ядра, а также запретить операционной системе переносить на это ядро другие потоки. То есть  на данном конкретном ядре выполняется только критически важный поток и только он. В приложении таких потоков может быть несколько, поэтому опять-таки важно чтобы числа ядер процессора хватило на все критически важные потоки и плюс осталось еще ядер на некритически важные потоки. Affinity делается с помощью сторонних библиотек прямо в коде приложения, либо пишете код самостоятельно через JNI/C/C++ или Unsafe. Или прикрепляете поток к ядрам процессора уже после старта вручную с помощью numactl.

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

Lock-free: Использовать lock-free алгоритмы и структуры данных. Блокировки существенно повышают latency системы, вызывают переключения контекста ОС.

В случае lock-free структур данных часть кода будет загружать ядро процессора на 100%. Это нормально. Убедитесь только, что поток надежно привязан к этому ядру, иначе ОС будет пытаться его остановить, запарковать и исполнить на ядре другой поток (см. про Affinity выше).

Обращения к диску: Снизить до минимума записи в логи. Писать в логи только то, что совершенно необходимо для отслеживания работы системы. В low-latency программах записи на диск — это самые дорогие операции. Запись в лог должна осуществляться асинхронно: программа не должна ждать в коде, когда закончится запись на диск, прежде чем выполнить следующую команду: это приводит к блокировке потока, переключению контекста и затратам на запуск потока после того, как запись на диск завершена.

Сбор мусора: Снизить до минимума количество мусора, которое производит ваше приложение. Чем быстрее вы забьете heap мусором, тем скорее вступит в игру сборщик мусора, а вступить в игру он может в самый неподходящий момент.

Выйти за пределы heap: Если есть навыки и умения, храните данные и объекты за пределами JVM heap в нативной памяти JVM процесса. Это делается либо с помощью JNI и C/C++ кода, либо с помощью недокументированного класса Unsafe. Помните, что за пределами heap вы сами должны управлять памятью. Любая ошибка в управлении памятью может привести к неожиданному краху программы в самый неподходящий момент.

 

Реклама

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Google photo

Для комментария используется ваша учётная запись Google. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s