В преддверии Joker 2016 мы накатали пост про Java Performance, который вызвал бурю эмоций у читателей. Дабы вбросить топлива в вентилятор и попытаться все-таки прийти к какому-то единому решению, мы решили привлечь экспертов из разных «лагерей»:
- Дмитрий Нестерук. Эксперт по .NET, С++ и инструментам разработки, автор курсов по технологиям и математике, квант.
- Андрей Паньгин. Ведущий программист компании Одноклассники, специализирующийся на высоконагруженных бэкендах. Знает JVM как свои пять пальцев, поскольку ранее на протяжении нескольких лет разрабатывал виртуальную машину HotSpot в Sun Microsystems и Oracle. Любит ассемблер и низкоуровневое системное программирование.
- Владимир Ситников. Десять лет работает над производительностью и масштабируемостью NetCracker OS — ПО, используемого операторами связи для автоматизации процессов управления сетью и сетевым оборудованием. Увлекается вопросами производительности Java и Oracle Database.
- Олег Краснов. CTO компании SEMrush и адепт ANSI C.
Андрей Паньгин
— Java и С++, какой сейчас, по-вашему, язык наиболее востребованный? Оба из них уже совершеннолетние, однако кто более зрелый и отточенный?
— Прежде всего, я не считаю, что между этими языками существует какая-либо конкуренция. У каждого из них есть своя ниша, и они прекрасно сосуществуют вместе. Традиционно популярность Java несколько выше. Java платформа привлекает своим мощным инструментарием для отладки и сопровождения приложений. Однако и значимость С++ сложно переоценить. Несмотря на то, что это язык с большущей историей, он и сейчас продолжает активно развиваться: только разработчики успели привыкнуть к C++11, как вышел стандарт С++14 с множеством новых интересных фич.
— Что языки могут дать в мире высоконагруженных серверов? Имеет ли смысл разрабатывать отдельные модули системы на разных языках, затачивая их под конкретные задачи? Если бы могли (захотели или просто была бы возможность), стали бы использовать С++ для решения задач или же делали все только на одном языке?
— Под высоконагруженными серверами каждый понимает что-то своё. Для одних это тысячи сетевых запросов в секунду, для других — параллельные вычисления над большими объёмами данных. Для разных задач лучше подходят разные инструменты. У нас (в Одноклассниках) есть модули, написанные на С++, в частности, связанные с обработкой изображений и видео — там нужны SIMD вычисления и максимально эффективное использование процессора. Впрочем, для большинства наших систем производительности Java хватает. Более того, фрагменты кода, ранее разработанные на C и вызываемые через JNI, мы постепенно переписали на Java, в результате чего даже выиграли в производительности, поскольку избавились от лишних копирований и накладных расходов JNI.
— Использование Unsafe в Java, оправдано или нет? Почему бы тогда не использовать С++?
— У меня есть целый доклад на тему того, почему мы используем Unsafe. Существует ряд сценариев, где без Unsafe пока не обойтись, в частности, для работы с off-heap и взаимодействия с нативным кодом.
Если бы мы захотели написать приложение целиком на C++, нам бы пришлось реализовывать заново все наши общие фреймворки и протоколы: для сбора статистики, для мониторинга, для коммуникации между серверами и т. д. А так — у нас есть лишь маленькая часть кода с Unsafe, отвечающая за низкоуровневые операции, зато всю остальную разработку мы ведём на привычной Java, придерживаясь лучших паттернов написания простого и понятного кода. Гораздо удобнее, когда вся экосистема разрабатывается на единой платформе.
— Какие наиболее частые проблемы производительности бывают при разработке Enterprise-систем и возможные способы решения?
— Редко, когда мы упираемся в производительность самой Java платформы. Обычно проблемы можно решить либо заменой алгоритма, либо масштабированием, то есть наращиванием железа. Чаще всего узким местом оказывается пропускная способность сети или дисковый ввод-вывод. Но если говорить о JVM, то главную или даже единственную проблему производительности нам иногда доставляет сборщик мусора, потому как паузы дольше 500мс в наших случаях зачастую критичны. Поэтому мы стараемся не делать Heap чрезмерно большим: максимум 50 гигабайт, но чаще и того меньше: от 4 до 8 гигабайт на приложение. Большие объёмы стараемся выносить за пределы хипа: сделали даже фреймворк для создания больших высоконагруженных кешей. Дополнительное преимущество такого кеша по сравнению с хиповым — персистентность, то есть возможность перезапуска приложения без потери данных. Это достигается за счёт использования разделяемой памяти: сразу после запуска приложение отображает объект разделяемой памяти в адресное пространство процесса, и кеш со всеми данными становится моментально доступен.
— Что вы можете сказать по поводу скорого выхода JDK 9 и его главной фичи — модульности?
— Про модульность было много разговоров, дата релиза даже несколько раз переносилась, чтобы эту модульность наконец доделать. Но при этом среди моих знакомых я не знаю ни одного Java разработчика, кому эта фича была бы действительно нужна. Я считаю, лучше бы пораньше выпустили JDK 9 — разработчики были бы только благодарны. Нам, например, модульность скорее навредит, чем поможет: ведь один из побочных эффектов — то, что Unsafe теперь будет спрятан глубоко внутри, и без специальных ключиков недоступен. Зато в JDK 9 ожидаются куда более приятные нововведения, ради которых новую версию стоит хотя бы попробовать: улучшения в G1, Compact Strings, VarHandles и др.
— Если очень грубо посмотреть, то разница между C++ и Java — в прослойке runtime, которая, помимо прочего, выполняет всевозможные оптимизации. Что предпочтительнее: использование архитектурных особенностей машины вручную (С++), либо лучше положиться на динамические оптимизации JVM? Если говорить совсем про конкретные вещи, то лучше автоматическая сборка мусора или ручное управление?
— Адаптивная компиляция и автоматическое управление памятью — как раз сильные стороны Java. В этом виртуальная машина преуспела и превзошла статические компиляторы. Но главное даже не это. Мы выбираем JVM за те гарантии безопасности, что она нам даёт. В первую очередь — защиту от фатальных ошибок из-за неправильной работы с памятью. Искать проблемы, связанные с указателями или выходом за границы массива, в неуправляемом коде на порядок сложнее. И стоимость исправления таких ошибок с лихвой перекрывает выгоду от того небольшого преимущества в скорости, что даёт прямой доступ к памяти. Как говорилось выше, мы порой используем Unsafe, и в этих случаях мы автоматически подвергаем себя тем же рискам, что и в C++. Да, нам иногда приходится разбираться в крашдампах JVM, и это занятие не из приятных. Именно поэтому мы всё же предпочитаем чистую Java, а неуправляемый код используем лишь в случаях крайней необходимости.
Также у меня будет доклад на Joker как раз на тему: «Мифы и факты о производительности Java».
Дмитрий Нестерук
— Java и С++, по вашему мнению, какой сейчас язык наиболее востребованный? Оба из них уже совершеннолетние, однако кто более зрелый и отточенный?
— Если говорить о востребованности, то тут все очевидно: Java, конечно, более востребована, чем другие языки. C++ занимает свою нишу в трех основных дисциплинах (game dev, финансы и embedded), ну и плюс является основным языком для HPC и scientific computing. Поэтому если придерживаться шкурных интересов, то Java конечно безопаснее как навык, если только вы не идете целенаправленно в одну из этих областей.
Что касается зрелости, то тут все сложно и нужно сначала разбивать на фичи языка, способности компилятора и особенности стандартных библиотек.
Начнем с первого — с языков. И там и там есть проблемы. С Java проблема в том, что язык не развивается так стремительно, как его ближайший конкурент, в результате чего фичи приходят очень медленно и не так, как хочется. Тут примечательно, что C# моложе, но в нем первом появились лямбды, также технология LINQ (Language Integrated Query — это такие удобные механизмы обхода и выборки наборов данных), да и изначальные решения в основе C# (то есть поддержка свойств и делегатов) тоже были выполнены грамотно и удачно.
Что касается C++, то тут основная проблема — это 100% совместимость С++ с языком С, что автоматически означает огромный багаж никому не нужных языковых фич. С другой стороны, стагнация С++ в 2000-е годы тоже популярности языку не прибавила, т.к. разработчиков нужно постоянно подкармливать новыми фичами. Сейчас ситуация лучше — в С++ есть и лямбды (кстати, более экспрессивны, чем в C#/Java), вывод типов для переменных и даже возвращаемых из функции значений, в общем язык как-то эволюционирует.
Это что касается языков. Теперь насчет компиляторов. Тут, во-первых, сравнение не совсем корректно, т.к. JVM — это JIT, то есть идея о том, что можно взять байт-код и превратить его в идеальное такое представление для текущего процессора, со всеми оптимизациями, которые применимы. Это звучит хорошо в теории — я не знаю как это на Java, но в .NET мире такой подход, по сравнению с оптимизациями С++ компилятора, конечно, он не делает практически ничего. Если вы делаете математику или, я так скажу: если вы, допустим, покупаете математическую .NET библиотеку в сети, то это будет всего лишь обертка вокруг С++.
Да, и касательно С++ компилятора: я для вычислительных задач использую Intel C++, то есть компилятор, который поставляет собственно производитель процессоров. В этом есть огромное количество минусов: языковых фич меньше, чем в MSVC, куча несуразных ошибок по которым приходится обращаться в саппорт, но едим мы этот кактус по одной простой причине: оптимизации. Intel'овский компилятор генерит самый эффективный код. Конечно, не кодом единым: тут у нас вся мощь Intel Parallel Studio идет в ход, это и Threading Building Blocks для паралеллизации (кстати, аналог Microsoft Parallel Patterns Library), и Intel Math Kernel Library, которую даже, если ты не используешь напрямую, ты используешь ее косвенно через MATLAB и прочие. Тут нужно пояснить, что библиотека вроде MKL уже оптимизирована ребятами из Intel: тут и векторизация, и параллелизация, и даже кластерная параллелизация через MPI (например для FFT) сделана «из коробки» — то есть, бери и пользуйся. Ну и конечно стоит упомянуть про средства профиляции, которые тоже часть IPS. Это очень мощный инструментарий, он, по сути, ставит себе цель помочь разработчику оптимизировать код в плане производительности, ну и корректности тоже — там профиляция памяти тоже есть, так что утечки и все подобное легко найти.
Ну и наконец про «либы» — тут все просто, Java выигрывает, в С++ все плохо. Я не буду даже говорить про то что сам интерфейс C++ Standard Library немножко безумен, но проблема мне видится не только в том, что все «легаси», а в том, что просто очень мало фич! У нас вот только-только появились такие вещи как поддержка файловой системы и худо-бедно поддержка потоков какая-то. И то, вот есть у меня строка, хочу ее побить на подстроки по пробелу — этого в стандартной библиотеке нет, то есть я должен брать стороннюю библиотеку (ну благо есть такая штука как Boost, там много полезного). Но развитие действительно тормозит. Многие компании, например Electronic Arts, они пишут свои реализации STL потому что их не устраивает стандартная. Ну и, в кулуарах, многие признаются что нам по сути нужна новая, с нуля, библиотека, некий STL2, хотя корректнее, конечно, было бы назвать ее Standard Library 2 или как-то так.
Есть еще масса проблем, например, отсутствие основного пакетного менеджера, а также, даже если бы он был, как библиотеки-то шарить? В Java или .NET можно просто бинарники раздавать, а в С++ по сути нужно шарить сорцы. Вот эту проблему пока никто толком не решил, и это тоже тормозит развитие т.к. иногда берешь чужую либу, а потом тратишь полчаса просто, чтобы она у тебя заработала.
— Как в целом чувствуют себя языки в Enterprise, например, в банковской сфере? Например, в HFT (High-Frequency-Trading) мире большие нагрузки и высокие требования к надежности. Также финансовая индустрия достаточно консервативна. Как это сказывается на выборе той или иной технологии?
— Enterprise — это одна такая гребенка большая, под которую сейчас идет любая корпоративная разработка. Глобально — это, конечно, C# и Java, и остальные языки где-то на периферии. Вот касательно банковской сферы — тут все несколько более интересно, и особенно интересно, что местами появляется С++, ну и есть некоторые конторы вроде Bloomberg'а, которые вообще на С++ полностью, но это мне кажется аномалия. Вообще, если сейчас получать MFE, то есть магистра по финансовой инженерии, то там в основном С++ и используется, хотя сейчас популярные такие языки как Python и R, ну и MATLAB тоже остается актуален.
Что касается HFT, это тоже такая спорная тема, но да, она в основном тяготеет к С++, а даже к C, использованию всяких FPGA, где и системный С присутствует, или люди на всяких HDL языках пишут. Когда важна скорость, производительность, то тут native code как-то ближе, хотя аргумент, что де «Java тормозит», мне кажется уже неактуален. Тут просто еще ручное управление памятью иногда людям нужно, все боятся большого злостного GC, который придет и остановит все потоки именно в тот момент, когда вам нужно будет совершить какую-то сделку.
В quant finance «плюсы» остались скорее из каких-то консервативных соображений, потому что финмат, в отличие от обычной разработки софта, он рассматривает программирование как навык сродни знанию английского языка, а не как нечто системообразующее. Соответственно, люди просто учат С++ и не мучаются, хотя сейчас для анализа Python и R как-то более популярны даже. Но «плюсов» в инвестбанках вагон.
— Для разработки ПО для встроенных устройств, какой язык лучше подходит, на ваш взгляд? Насколько эти языки позволяют писать портативный код?
— Вообще, тема embedded слишком широка. Для многих embedded — это всякие Rasperry Pi или Arduino, для меня — это FPGA, для кого-то еще что-то. Но если обобщать, то embedded это конечно в основном С или С++, если говорить о прикладном уровне. Конечно, для FPGA разработки я использую или VHDL напрямую или пишу MATLAB, который после конверсии VHDL выбрасывает — суть остается та же.
Конкретно про FPGA, поскольку это единственная тема, в которой я хоть что-то понимаю, могу сказать что языки, да и сам подход разработки — это хорошая иллюстрация того, как вообще вся технология может застрять намного хуже, чем С++ где-то в старых моделях, языках и вообще. С этой технологией очень сложно работать и ты начинаешь по сути или использовать всякие генераторы вроде MATLAB или писать что-то свое. То есть, для людей, которые работают чисто на системном уровне, перекладывание битов в ручную — это нормальное явление, но мне как человеку, который хочет, например, моделировать набор бизнес-правил в «железе», совсем не нравится такой подход, и языка не хватает, чтобы на высоком уровне объяснить, что же мне надо.
А говорить про Java и embedded я просто не квалифицирован.
— Если очень грубо посмотреть, то разница между C++ и Java — в прослойке runtime, которая, помимо прочего, выполняет всевозможные оптимизации. Что предпочтительнее: использование архитектурных особенностей машины вручную (С++), либо лучше положиться на динамические оптимизации JVM?
— Ну я, кажется, уже этот вопрос затронул, но тут все на любителя. Вот взять меня, я на практике использую все уровни параллелизации, то бишь SIMD, OpenMP, MPI и это не говоря про всякую специфику вроде аппаратных ускорителей. SIMD оптимизации есть какие-то в Java, сейчас вон и .NET медленно подтягивается, но по сути С++ все еще рулит в плане автоматических оптимизаций, и не будем забывать что в С++ можно вручную забивать ассемблерные блоки. Я понимаю, что сейчас ассемблер никто не знает и многие-то и С++ в глаза не видели, но суть в том, что когда дело касается чисто вычислений, то есть математики, и хочется побыстрее, то почему бы и нет?
Я не особо верю в динамические оптимизации вот почему: если у вас простой цикл, в нем допустим массив суммируется — это да, это можно распознать, распараллелить там. Проблема в том, если вы, например, втащили в цикл какую-то зависимость извне, что тогда делать? В OpenMP у нас разметка соответствующая, а динамический оптимизатор такие задачи не сможет решать, никогда. Поэтому кто-то, например, посмотрит на CUDA и скажет, что это модель абсолютно нереальная, зачем мне все алгоритмы переписывать, да еще учиться чему-то? А как по мне, то это неизбежно, т.к. оптимизаторы работают очень хорошо на понятных, простых вещах, делают всякие инлайнинги, но все что performance-critical можно написать и руками, написать в native code и не мучаться.
— Насколько динамична экосистема Java и C++? Насколько часто выходят обновления, релизы, стандарты? Насколько живы языки (насколько много языковых фич появляется)?
— Ну, я думаю можно сказать, что «плюсы» бессмертны, в отличии от Java как языка, где появились многие новые языки с интересными фичами — это и Scala и Kotlin и другие. Другое дело, что язык и платформа это разные вещи. Java как язык многих не устраивает, собственно поэтому и новые языки. Но как платформа — там все прекрасно, судя по всему, опять же, есть преимущества даже по сравнению с ближайшим конкурентом (например в плане GC). Но как язык — предостаточно оснований для претензий.
Про С++ я тут должен высказаться, наверное. Конечно, после того, как комьюнити лет этак 13 в свое время вообще ничего не делало, новые стандарты и новые фичи библиотеки — это, конечно, хорошо, прекрасно, я бы даже сказал, по сравнению с полной апатией. В С++11 произошло многое, очень много реально полезных подвижек, я теперь пишу совсем другой С++ нежели ранее. В С++14 все еще немного доулучшали, но вот в С++17 снова разочаровали весь мир — то есть, те фичи которые всех ждали, их не будет. Основная фича, которую все хотели, и хотят в общем-то, это модули. Просто сейчас С++ компилируется очень медленно, точнее первичная компиляция, т.к. инкрементальность-то у него, если судить по MSVC, просто супер, но сборка например либы «с нуля» — это удовольствие ниже среднего. Ну и вот модули должны решить эту проблему, но никто не знает когда.
Опять же, в С++ такая проблема, что самых базовых вещей нет в стандартной библиотеке. И это новичка, которому нужно перевести строку, допустим, в нижний регистр или побить по токенам, просто приведет в ступор. Сторонних библиотек, конечно, много, но сама юзабельность библиотек это тоже вопрос. Языки у которых есть метаданные — ты видишь там функцию и знаешь как ее использовать, даже документация появится в комплишне. А в С++ у тебя может быть шаблонный аргумент типа Func, то бишь функция, а сигнатуру функции ты можешь не понять, даже если залезешь в сорцы. И непонятно что с этим, собственно, делать.
В общем, если резюмировать, я бы сказал, что оба языка как бы живы, и все зависит от того, что вам собственно надо. В целом, можно продуктивно писать и на том и на том. Касательно либ, тут плюсы проигрывают, это всем ясно, причем удивительно, т.к. язык вроде старее, намного, а библиотеки ну, они даже если есть, то из мира С и не особо юзабельны, или их просто нет и нужно искать где-то извне, скачивать, компилировать, и только потом использовать.
Олег Краснов
— Почему С?
— Когда я пришёл в компанию SEMrush, значительных наработок серверной логики на других языках не было. На тот момент я преимущественно программировал на С и решил разрабатывать продукт именно на этом языке. Я верил в свои силы. =)
Для меня С — это простой и удобный язык. При достаточном умении и знании библиотек, он прекрасно подходит для прототипирования разработок на уровне скриптового языка.
У нас, в компании SEMrush, среди языков программирования серверной части распределение примерно следующее: 1/3 — это С и С++, 1/3 — скриптовые, 1/3 — Java.
— Java и С, какой сейчас по-вашему язык наиболее востребованный? Оба из них уже совершеннолетние, однако кто более зрелый и отточенный?
— Мой опыт говорит о том, что на языке С стоит разрабатывать вещи, которые относятся к производительным задачам. Например, это работа с сокетами, мультиплексирование данных, высоконагруженные многопоточные приложения, где можно и нужно максимально полно управлять ресурсами компьютера.
В компании SEMrush нет явного разделения языков программирования по зонам их применения. Если нужно начать новый продукт, то выбор того или иного языка зависит от профессионализма человека, который начинает разработку архитектуры и программирование. А также от того, насколько он коммуникабелен и способен донести до коллег идеи, которые хочет воплотить.
Довольно частые задачи для нас — это сбор и обработка данных. Среди причин, почему, например, мы не используем Java во всех продуктах, то, что у нас в плане сущностей нет дикого наследования. Глубина его, в силу специфики нашей работы, в вертикальной плоскости меньше, чем в горизонтальной. То есть большое количество данных скорее будет передаваться между независимыми сущностями, чем между родителями и потомками.
— Имеет ли смысл разрабатывать отдельные модули системы на разных языках, затачивая их под конкретные задачи?
— Я считаю, что имеет. В этом плане у нас очень хорошо всё устроено. Разработку ведут небольшие группы по 5-6 человек, каждая из которых работает над своим продуктом, а взаимодействие между ними осуществляется через API. И пользовательский интерфейс, и сервисы между собой должны «хорошо» взаимодействовать. Это осуществляется, например, при помощи таких форматов данных, как JSON и Binary-JSON. Поэтому да, можно использовать разные языки для написания целой системы.
У нас есть наша собственная БД, написанная на С, и крупных проблем с ее работой не наблюдалось. Когда я занимался разработкой архитектуры этой базы, не было готовых адекватных средств, которые бы подошли. Данная БД работает на обыкновенных файлах и, с учетом этого, достаточно надежна. Если исключить из рассмотрения все меры по обеспечению её бесперебойной работы (резервирование, кластеризация), то даже «отвалившийся» диск (неполадки в оборудовании или непредвиденные технические причины) не нанесёт большого вреда, будет потеряно не более 8.5% данных. То есть вероятность того, что пользователи пострадают, дополнительно снижена на уровне бизнес-логики. Но в целом система построена таким образом, что данные не теряются. Все очень надежно.
Мы как-то проводили тесты по максимальной утилизации производительности «железа» с 12 дисками. Если использовался RAID5, то скорость чтения/записи была примерно 2.5х скорости одного винчестера. Но наша система использует 12 дисков раздельно на уровне бизнес-логики, это позволяет нам добиться 11-кратного роста скорости за счёт того, что каждый поток работает со своим диском.
— Как в целом чувствуют себя языки в Enterprise?
— У нас есть крупные продукты. Например, один из них обходит сеть Интернет и создает базу ссылающихся друг на друга страниц сайтов. Ядро написано на С, что позволяет утилизировать «железо», практически на все 100%. В этом продукте задействовано более 150 серверов, но такой подход позволяет быть уверенным в том, что мы не переплачиваем за серверный парк, а нас, как вы понимаете, заботит финансовая эффективность. Отдельно отмечу, что благодаря agile процессам разработки, у нас есть время как на доставку новых возможностей пользователю, так и на оттачивание производительности каждого продукта.
— Какие наиболее частые проблемы производительности бывают при разработке Enterprise-систем и возможные способы решения?
— Если честно, особых проблем не могу даже припомнить. Если вдруг не хватает мощности, то использование ассемблера, специальных библиотек и усилий высококлассных программистов позволяет очень быстро решить проблему. Но в 99% случаев у нас с производительными решениями проблем нет.
У меня нет предубеждений по поводу Java, но она более требовательна к ресурсам. Да, с её помощью очень удобно решать задачи со сложной многоуровневой бизнес-логикой, строить взаимосвязанные системы. Однако такого рода задачи, как сетевые взаимодействия, многопоточное программирование или большие бинарные данные, на мой взгляд, больше подходят для С. На Java это тоже можно сделать, однако я бы выбрал С.
— Какая по-вашему должна быть «идеальная» БД? Стоит ли заморачиваться с производительностью приложений, если вся мощь системы нивелируется транзакциями к БД и как это можно исправить?
— Идеальная БД не универсальна. Она делается для определённого приложения и должна хорошо соответствовать его бизнес-логике. Да, ее сложнее спроектировать и проще воспользоваться готовыми решениями, но такой подход позволяет «драматически» увеличить производительность.
— Сейчас очень модно работать с большими данными, для задач обработки больших массивов, что лучше подходит — Java или С++?
— Большие данные — это Святой Грааль для программистов, и для каждого они выглядят по-разному. Для кого-то это петабайты, а для кого-то — экзабайты. На начальном этапе обработки таких данных существуют большие требования к уровню железа. Поэтому наиболее эффективно для их первичной обработки использовать язык С.
— Насколько динамична экосистема Java и C++? Насколько часто выходят обновления, релизы, стандарты? Насколько живы языки (насколько много языковых фич появляется)?
— У нас используется стандарт С99, и с тех пор ничего нового, скорее улучшения компилятора более важны. Усложнение языка не всегда хорошо, на мой взгляд. У последних стандартов С++ — С++14 и С++17 (draft) наблюдается такое количество новых фич, что не ясно, имеет ли смысл использовать большинство из них. Я понимаю, что программистам нравится развитие языка. Однако особенности и удобства нужно правильно использовать. Гонка за фичами как самоцель неэффективна и часто вредит основной идее продукта.
Периодически я наблюдаю использование С++ как С с классами или даже как С без классов с некоторыми контейнерами С++. Это нерационально. Получается каша. Если планируется использование С++, следует внимательно изучить его особенности и использовать их наиболее полноценно.
Владимир Ситников
— Что языки могут дать в мире высоконагруженных серверов? Имеет ли смысл разрабатывать отдельные модули системы на разных языках, затачивая их под конкретные задачи? Если бы могли (захотели или просто была бы возможность), стали бы использовать С++ для решения задач или же делали все только на одном языке?
— В мире энтерпрайз приложений подобное разделение встретишь не часто. Интересный фактор — это поддержка приложения. Если в приложении используются 10 разных языков и, по сути, 10 разных экосистем, то поддерживать такое очень сложно. В итоге, если программу пишет одна компания, а разворачивает, использует и поддерживает другая, то крайне важно сохранять универсальность и простоту сопровождения. Здесь даже использование двух разных языков уже может сильно повышать порог вхождения для поддержки. Второй немаловажный фактор — удобство анализа проблем. Например, если java программа решила прочитать и сохранить многогигабайтный файл в памяти, то без проблем можно узнать какой файл, почему и т.п. В случае C++ подобный анализ сделать гораздо сложнее. В итоге, для энтерпрайз разработки java выступает не только удобной платформой для разработки, но и во многих случаях обеспечивает приемлемую производительность.
— Использование Unsafe в Java, оправдано или нет? Почему бы тогда не использовать С++?
— Наивно считать, что Unsafe в Java нужен (используется) для того, чтобы выделять и освобождать память в обход сборщика мусора. От хорошей жизни unsafe java коде существует много объектов. Например, в OpenJDK java.util.HashMap использует промежуточные Map.Entry объекты, которые не хранят сами данные, а хранят лишь ссылки на ключ и значение. Такая реализация не только увеличивает накладные расходы на хранение, но и замедляет работу, т.к. обращение к памяти в случайном порядке всегда сложнее и медленнее, чем последовательное обращение. В силу семантики языка, во многих случаях javac и JIT-компилятор вынуждены оставлять этот ворох мелких объектов.
Если же посмотреть на C++, то там хэш-таблицы реализованы без лишних entry объектов. Данные хранятся в памяти компактнее, и значение хранится рядом с ключом. С++ позволяет быть ближе к железу.
Что же делать java программистам? Переходить на тёмную сторону Unsafe, off-heap и иже с ним? В первую очередь, нужно провести измерения, и убедиться в том, что проблема для вашего случая реально существует. Большинство типичных java приложений вполне сносно работают с простыми HashMap под капотом.
Если вы «профессионал на закрытом треке», то можете попробовать эдакое (Unsafe, VarHandlers, memory mapped files, и т.д.). Если для вас проблема действительно важна, то стоит участвовать в обсуждениях JEP 169: Value Objects — там ведётся дискуссия о том, как сгладить проблему лишних объектов в Java без ущерба надёжности кода и скорости разработки.
— Какие наиболее частые проблемы производительности бывают при разработке Enterprise-систем и возможные способы решения?
— Типичные проблемы производительности как правило вызваны не самими языком, а то как разные приложения взаимодействуют между собой. Например, это медленные SQL-запросы. Т.е. неэффективная работа с БД. Если система выполняет тысячи запросов, то производительность зависит не от языка, а от используемых алгоритмов. «Правильное» ТЗ и полнота нефункциональных требований позволяют решить многие потенциальные проблемы. Эта проблемы возникает не только при использовании БД, но она не решается сама собой при переходе к микросервисам. И при неправильной гранулярности API этих микросервисов у нас на ровном месте может возникнуть 1000 обращений и скорость работы будет определяться не скоростью языков, а количеством и качеством этих обращений.
— Что вы можете сказать по поводу скорого выхода JDK 9 и его главной фичи — модульности? Это попытка решить определенные сложности, с которыми сталкиваются разработчики на Java?
— При использовании Java одной и неудобных ситуаций является долгий запуск приложения. Например, при запуске серверного приложения контейнеру может потребоваться 30 и более секунд, зависит от железа и количества загружаемых библиотек.
А почему Java-машина каждый раз при запуске заново компилирует все классы приложения? Почему она не может переиспользовать уже недавно сгенерированный машинный код? Состав .class файлов может обновиться и JVM не имеет никаких гарантий по поводу состава классов приложения. Модульность в JDK9, на первый взгляд, программисту ничего не даёт, а на второй взгляд оказывается, что она даёт очень много самой java-машине. Java-машина может использовать более смелые предположения о том, какой код может выполняться, и таким образом модульность открывает возможность ускорения времени запуска.
Более прозаическая польза от модульности – уменьшение размера дистрибутива java-машин. Вряд ли разработчики стройными рядами пойдут создавать свои сборки JVM ради экономии нескольких мегабайт, но в ряде случаев возможность будет весьма кстати.
— Если очень грубо посмотреть, то разница между C++ и Java — в прослойке runtime, которая, помимо прочего, выполняет всевозможные оптимизации?
— Одна из ключевых возможностей Java — возможность раздельной компиляции кода без потери производительности для конечного приложения. Например, java-библиотека может работать с интерфейсом Iterator. Да, при компиляции библиотеки в байткод там будут «виртуальные» вызовы (invokeinterface). Но это не мешает получать хорошую производительность, если по факту используется одна и та же реализация Iterator в конкретном месте нашей программы. JIT-компилятор видит, какие по факту объекты используются, и он генерирует машинный код без лишних поисков «а где у переданного объекта реализован метод hasNext». В результате программист пишет удобный код на интерфейсах, а при выполнении весь итератор превращается в один-единственный регистр процессора, хранящий «текущую позицию».
Если же говорить про C++, то, если библиотеку скомпилировали и библиотека выполняет виртуальный вызов, то всё, у пользователя нет шансов сделать вызов не виртуальным.
В целом, один из самых действенных способов оптимизации — исключать лишние действия. Для того чтобы C++ библиотеки «видели» особенности их использования, программистам C++ приходится делать разнообразные приседания (clang LLVM, всегда компилировать все библиотеки вместе с кодом приложения и т.п.).
На чьей вы стороне?
Автор: JUG.ru Group