Все разработчики знают, что есть два способа сделать дело: первый — вручную, медленно, нервно, сложно, либо второй – автоматизировано, быстро и еще сложнее.
Например, я мог бы продолжить писать эту статью о том, почему стоит использовать Java вместо C++ при программировании систем с низкой задержкой. Либо мог бы обучить ИИ, чтобы он сделал это за меня. Второй подход, в конце концов, сэкономил бы мне массу времени – искусственный интеллект генерировал бы за меня тысячи статей в секунду – но редактор вряд ли обрадовался бы услышать, что на подготовку первой статьи мне нужно два года.
Аналогичная ситуация возникает при необходимости разработать систему с низкой задержкой.
Когда речь заходит о разработке программных систем с низкой задержкой, принято считать, что было бы безумием делать это на каком-либо языке кроме С++. Но я попытаюсь убедить вас в противоположном, алогичном и даже в чем-то еретическом тезисе: для достижения низкой задержки Java пойдет лучше.
В этой статье я рассмотрю в качестве примера конкретный класс программ, в которых высоко ценится низкая задержка: трейдинговые системы. Но те аргументы, что я привожу здесь, применимы почти в любых обстоятельствах, где низкая задержка желательна или необходима. Мне просто удобнее рассмотреть эту тему в той предметной области, где у меня есть опыт. Правда в том, что измерить задержку бывает очень непросто.
Все сводится к тому, как вы понимаете «низкую задержку». Сейчас объясню…
Народная мудрость
Сначала рассмотрим, по каким причинам C++ предпочтителен для создания высокоскоростных систем с низкой задержкой.
Поскольку C++ очень близок к металлу, большинство разработчиков скажут вам, что вы по определению выигрываете в скорости, когда пишете на этом языке. В ситуациях, не терпящих задержек, например, при высокоскоростном трейдинге, когда на уровне микросекунд решается, что перед нами – ценный софт или бесполезная куча мусора на диске, C++ считается золотым стандартом.
По крайней мере, когда-то так было. В реальности же сегодня множество крупных банков и брокеров используют системы, написанные на Java. Я имею в виду, именно написанные на Java, а не написанные на Java и затем интерпретированные на С++ в целях снижения задержки. Такие системы превращаются в стандарт даже для инвестиционных банков высшего уровня, несмотря на то, что они (казалось бы) медленнее.
Итак, в чем же дело?
Пожалуй, C++ действительно дает «низкую задержку» на уровне выполнения кода, но ни о какой экономии времени не идет речи при выкатывании новых возможностей или даже поиске разработчиков, способных их написать.
(Реальные) отличия между Java и C++
Но такая проблема с длительностью разработки – это еще цветочки по сравнению с реальными отличиями между Java и C++ в боевых системах. Итак, чтобы понять истинную ценность каждого из языков в этом контексте, давайте немного развернем этот момент.
Во-первых, важно помнить, по какой именно причине С++ действительно быстрее Java в большинстве ситуаций: указатель C++ - это адрес переменной в памяти. То есть, программа может напрямую обращаться к конкретным переменным, а не просматривать вычислительно затратные таблицы, чтобы найти их. Либо может, если точно ей указать, где находятся нужные переменные — так как при работе с C++ зачастую приходится явно управлять временем жизни и принадлежностью объектов.
Суть всего этого такова: если вы в самом деле не владеете С++ в совершенстве (а это навык, на овладение которым уходят десятки лет), то код на C++ потребуется отлаживать часами, если не неделями. Любой, кто пытался отлаживать движок Монте-Карло или решатель для уравнений в частных производных (PDE), расскажет вам, что отладка памяти на фундаментальном уровне может занимать невообразимое количество времени. Всего один дефектный указатель может легко обвалить всю систему, так что сдача новой версии программы, написанной на C++ - это иногда просто ужас.
Это, конечно же, еще не все. Те, кто наслаждается программированием на C++ (все трое) укажут вам, что сборщик мусора в Java страдает от нелинейных всплесков задержки. Это, в частности, актуально при работе с унаследованными системами. Поэтому добавление обновлений в код Java так, чтобы не сломать клиенту всю систему, может оказаться медленным вплоть до неприменимости.
На что я возражу, что за последнее десятилетие проделана огромная работа, чтобы снизить задержку, возникающую из-за сборщика мусора Java. Например, есть LMAX Disruptor, трейдинговая платформа с низкой задержкой, написанная на Java, которая строилась как каркас для «сродства с железом» (mechanical sympathy), на котором она работает – причем, без блокировок.
Проблемы можно дополнительно сгладить, если вы пишете систему, в которой используется процесс непрерывной интеграции и доставки (CI/CD), потому что CI/CD позволяет автоматически развертывать протестированные изменения кода. Дело в том, что CI/CD позволяет пошагово подходить к снижению задержки на уровне сборщика мусора, благодаря чему Java можно постепенно улучшать и подгонять к конкретным аппаратным окружениям без ресурсозатратной подготовки кода в соответствии с разными аппаратными спецификациями, что пришлось бы заблаговременно делать перед его сдачей.
Поскольку IDE в Java поддерживаются гораздо полнее, чем в C++, в большинстве сред (Eclipse, IntelliJ, IDEA) вы сможете рефакторить Java. Это значит, что большинство IDE позволят вам оптимизировать код для снижения задержек – а при работе с C++ такие возможности до сих пор ограничены.
Пусть Java и не сравнится с C++ по чистой производительности, большинство разработчиков могут достичь приемлемой производительности на Java гораздо легче, чем достигли бы на C++. Реально задержка убивается на отрезке «у меня есть идея – я выразил ее в коде».
Что такое «быстрее»?
На самом деле, есть немало причин задуматься, на самом ли деле С++ «быстрее» Java, и «меньше» ли в самом деле задержки на нем. Я сознаю, что здесь уже углубляюсь в дебри, и многие разработчики хотят спросить, а не брежу ли я. Но разрешите я доскажу.
Во-первых, есть (слегка абсурдная) точка зрения, что, если у вас есть два разработчика, один из которых пишет на C++, а другой на Java, и обоим вы дадите задачу написать платформу для высокоскоростного трейдинга с нуля, то Java-разработчик перейдет к трейдингу гораздо раньше, чем C++-разработчик. Те, кто программировал на обоих этих языках, понимают, почему: просто в Java гораздо реже встречается неопределенное поведение, чем в C++. Ограничусь всего одним примером: выход индекса за пределы массива – это ошибка как в Java, так и в C++. Если случайно сделать такое в C++, то можно получить ошибку сегментации или (чаще) какое-то случайное число, которое даже опытному разработчику ничего не скажет. В Java при выходе индекса за пределы массива всегда выбрасывается ArrayIndexOutOfBoundsException
. Поэтому отладка в Java гораздо проще, так как все ошибки обычно вскрываются немедленно, и местоположение бага легче отследить.
Кроме того, как минимум, по моему опыту, Java (в большинстве окружений) просто лучше распознает, какие фрагменты кода выполнять не обязательно, а какие критичны для функционирования программы. Разумеется, можно днями отлаживать ваш код на C++, пока в нем не останется ровно ничего лишнего, но в реальной жизни любая программа немного разбухшая, и Java лучше распознает такие излишки автоматически.
То есть, в реальном мире Java зачастую быстрее С++, даже при стандартном подходе к изменению задержки. А там, где С++ быстрее Java, разница в задержке между языками зачастую поглощается другими факторами и становится совершенно несущественной, даже в такой области, как высокочастотный трейдинг. Например, проделана большая работа по снижению задержки в 5G-сетях — по данным некоторых аналитиков, она уменьшилась до 1 мс, но в программировании под низкие задержки и такие цифры заметно сказываются на производительности.
Преимущества Java в системах с низкой задержкой
Считаю, что в совокупности все эти факторы вполне неоспоримо свидетельствуют в пользу удобства Java для создания платформ высокоскоростного трейдинга (а, в самом деле – и для любых систем с низкой задержкой, подробнее об этом чуть ниже).
Но, чтобы еще немножечко поколебать энтузиастов C++, давайте пробежимся еще по нескольким доводам в пользу Java:
Во-первых, как уже было показано выше, любые излишние задержки, которые Java привносит в вашу программу, наверняка гораздо ниже, чем уже имеющиеся – например, задержки при сетевой коммуникации в (как минимум) такой системе, где сделка должна быть полностью проведена, прежде, чем завершиться. Это значит, что любой (качественный) код на Java в большинстве трейдинговых ситуаций может работать так же быстро, как и код на C++.
Разработка на Java требует меньше времени, и это также означает, что программы, написанные на Java, можно быстрее приспосабливать к аппаратным изменениям (или даже новаторским трейдинговым стратегиям), чем программы на C++.
Развивая эту мысль, увидим, что даже оптимизация программы на Java может пойти быстрее — если рассматривать ее в контексте всей программы — чем при решении эквивалентной задачи на C++. Как рассказал InfoQ Питер Лоури, Java-консультант, интересующийся низкими задержками и системами с высокой пропускной способностью, “если ваше приложение тратит 90% времени на выполнение 10% вашего кода, то на Java оптимизировать эти 10% сложнее, зато легче написать и поддерживать 90% остального кода, особенно если способности специалистов в вашей команде разнятся.”
Иными словами, можно писать на Java от машинного уровня и выше так, чтобы задержки были низкими. Просто нужно писать на Java как на C++, на каждом этапе разработки не забывая об управлении памятью. Преимущество отказа от C++ как такового в том, что отладка, гибкая разработка и приспосабливание к множеству сред в Java происходит быстрее и удобнее.
Ну так что?
Если вы дочитали до этого абзаца и разрабатываете системы для трейдинга с низкой задержкой, то, вероятно, задумались, а касается ли вас все вышесказанное. Ответ (за немногочисленными исключениями) – да.
Спор о том, как добиться низкой задержки, не нов, и актуален не только в мире финансов. Поэтому здесь можно извлечь ценные уроки и для других ситуаций. В частности, приведенный выше аргумент, что Java «лучше» по причине большей гибкости, надежности, быстроты разработки и поддержки – применим во многих областях программирования.
Причины, по которым (лично я) предпочитаю писать системы с низкой задержкой на Java – те же самые, что обеспечивают успех этого языка на протяжении последних 25 лет. Код на Java легко писать, компилировать, отлаживать и изучать, а значит, вы потратите меньше времени на написание кода, и у вас останется больше времени на оптимизацию задержек. В конечном итоге, это надежно позволит создавать более быстрые трейдинговые системы. А это как раз важнее всего.