Продуктивність Java: сьогодення та майбутнє

Вже два десятиліття активно плодяться міфи про те, що програм на Java властиві проблеми з продуктивністю. Одночасно з цим на Java створюються по-справжньому високонавантажених системи. Хто ж у кінцевому підсумку прав? Щоб скласти думку про те, як зараз йдуть справи з продуктивністю Java, ми звернулися до двох зацікавленим сторонам: творцям самої Java і клієнтам, які використовують Java в своїх системах. На наші запитання люб'язно погодилися відповісти Олексій Шипилев (Oracle) і Олег Анастасьєв (Однокласники).

Java Performance очима творців JDK

Jug.Ru: Розкажіть, будь ласка, про себе і про свою роботу?
Олексій Шипилев: Мене звати Олексій Шипилев. Я працюю над продуктивністю Java вже більше 10 років. За цей час я встиг попрацювати над різними JVM — спочатку над Apache Harmony в Intel, потім перейшов в Sun Microsystems, де займався OpenJDK. На поточний момент моя робота здебільшого полягає в тому, щоб знаходити проблеми продуктивності в продукті і визначати шляхи їх вирішення або ж виправляти власними руками, якщо проблеми прості. Сюди входить і оптимізація під стандартні бенчмарки, тестуючі продуктивність віртуальної машини, і рішення клієнтських проблем (оптимізація їх програм), і поліпшення глобальних речей, які необхідні багатомільйонної екосистемі Java.
Jug.Ru: Як ви вважаєте, чи коректно взагалі на сьогоднішній день говорити про те, що продуктивність — проблема в цілому Java, як технології, а не окремих додатків?
Олексій Шипилев: Про це складно говорити, оскільки продуктивність визначається частіше все ж кодом кінцевого програми, а не задіяним для його створення мовою. У мови програмування взагалі немає продуктивності, вона може бути тільки у реалізації цієї мови. Причому, різних реалізацій може бути багато. Однак у світі Java сталося так, що найчастіше мова йде про реалізацію від Sun JDK/Oracle, яка займає більше 95% ринку. Її і будемо мати на увазі.
На самому початку, в 1995 — 2000 роках, Java, як і всякий молодий продукт, дійсно була не дуже ефективно реалізована. Але за останнє десятиліття в реалізаціях Java зроблено настільки багато, що проблеми, які раніше вважалися типовими, перестали так сильно і так боляче бити розробників по голові.
Звичайно, багато складнощі, пов'язані з розробкою високопродуктивних додатків, що збереглися, так що говорити, що ця проблема остаточно вирішена, по-моєму, необачно. Є ще багато речей, які необхідно докручувати.
Але важливо пам'ятати, що далеко не всім потрібні високопродуктивні рішення: у багатьох розробників продуктивність не входить у список критеріїв успіху — "працює за прийнятний час" і добре. Якщо ж розробникам дійсно потрібна кожна остання крапля продуктивності, їм доведеться обходити підводні камені, які присутні в кожному достатньо складному продукті (а віртуальна машина HotSpot — і OpenJDK в цілому — дуже складний продукт).
Jug.Ru: Іншими словами, продуктивність вже не є глобальною проблемою Java?
Олексій Шипилев: Думаю, так.
Чесно кажучи, мені здається, що проблеми продуктивності будь-якої платформи — перебільшені. Як я говорив, більшість програм не вимагають великої продуктивності. Але в программистском співтоваристві існують стійкі легенди і прості рецепти, які дуже зручно повторювати (коли ти повторюєш їх, здається, що приєднуєшся до групи присвячених!). І одна з таких легенд полягає в тому, що "Java гальмує". Особисто я впевнений, що вона давно не релевантна. Для неї були об'єктивні підтвердження років 10-15 тому, але зараз ситуація змінилася. Безумовно, і зараз можна написати програми, які наступлять на проблеми з продуктивністю рантайме. Але ці проблеми в основному відомі, для них є обхідні шляхи, а ті, кому такі шляхи не підходять, створюють власні "милиці".
Jug.Ru: Наскільки активно йде розвиток JDK і, відповідно, усунення відомих проблем продуктивності (якщо говорити про Oracle JDK)?
Олексій Шипилев: Досить активно, і цьому є причина — екосистема Java дуже велика, навіть в перерахунку на одну компанію. У того ж Oracle ентерпрайз-стеки написані на Java. Відповідно, будь-яке поліпшення, яке робиться в платформі, поширюється по всьому стеку і полегшує життя в тому числі і розробників Oracle. Але це історія, чому Oracle розвиває OpenJDK. Ця історія в різних якостях повторюється і для інших вендорів, і для інших проектів з відкритим кодом.
Jug.Ru: Які останні нововведення в Java вам здаються найбільш значущими з точки зору підвищення продуктивності?
Олексій Шипилев: По-перше, мені подобається, що історія зі складальником сміття Garbage-First (G1) потихеньку йде до логічного кінця. Garbage-First був анонсований давно, але тільки в Java 8 і 9 він став себе досить пристойно вести, так що його можна використовувати в промислових масштабах — настільки, що в Java 9 він включений за умовчанням. Цей багаторічний проект нарешті вистрілює і робить речі, які планувались з самого початку.
По-друге, мені близька історія з високопродуктивними додатками, які вимагають Unsafe, на заміну частини якого розробляється VarHandles. Є легітимні випадки, коли хочеться вичавити останні краплі продуктивності за допомогою низькорівневих хаків. Але Unsafe, як відомо, — це приватний API, толком не стандартизований, тобто його використання — це біг з гострими ножицями з тліючим вугіллям палаючого будинку. А VarHandles — це один із шляхів, за допомогою якого ми можемо надати публічний API для таких рідкісних, але важливих випадків, коли потрібна максимальна продуктивність або якась функціональність, яка інакше не доступна.
Ще одне цікаве нововведення — Compact Strings. Я особисто брав участь у цьому проекті та інших "строкових" оптимізацію. Подібного роду зміни платформи, серйозно поліпшують загальновживані класи, істотно підвищують продуктивність взагалі всіх програм, які написані на Java, і тим самим ще більше знижують необхідність милиць.
І в даному випадку ми отримали дуже хороший результат не тільки на синтетиці, але і на реальних додатках. Досягнуте поліпшення пам'яті і продуктивності на 10% на великих програмах — це дуже хороші прирости для такої здорової дорослої платформи, як Java.
Jug.Ru: Раз вже ми говоримо про продуктивності, чи існують якісь "канонічні" методи її вимірювання? Зводиться для бізнесу все до грошей?
Олексій Шипилев: Продуктивність не завжди переводиться в гроші. На практиці досить складно оцінити, як приріст продуктивності впливає на економічну сторону питання. Часто це непрямі ефекти — час, який витрачає програміст на написання коду, укладывающегося в цілі щодо продуктивності; час, який користувачі витрачають на очікування результату, і т. п. Але з розміщенням серверів в хмарах і щільних датацентрах продуктивність стала ближче до фінансової сторони питання: чим швидше працює твоє додаток, тим менше воно споживає ресурсів, тим менше ти платиш за оренду і обслуговування серверів. Причому ця залежність добре масштабуються додатках може бути просто лінійна, тобто розігнав на 50% свого додаток — тобі потрібно в 2 рази менше заліза, платиш за інфраструктуру в 2 рази менше.
Крім того, питання грошей виникає, коли потрібно виправдати тимчасові витрати на оптимізацію. Організації, які займаються комерційною розробкою — не богадільні. Вони платять інженерам, щоб ті допомагали вирішувати бізнес-задачі, тому організації намагаються зрозуміти, чи варто фінансувати конкретний напрямок розробки; скільки бізнес-профіту дадуть ці результати. Так що оптимізація — це не просто "ми тут подлубалися отверточкой, тому що нам дуже цікаво копирсатися саме тут". Так можуть мислити окремі розробники, іноді вдало співвідносячи ці бажання з бізнес-цілями, але бізнес сам по собі в оптимізації задля оптимізації не зацікавлений.
Jug.Ru: Які майбутні нововведення в JDK, на ваш погляд, найбільш очікуваним з точки зору управління продуктивністю?
Олексій Шипилев: Value Types — дуже очікуване нововведення, яке на даний момент планується реалізувати до виходу Java 10. Це дуже складний проект, який потребує докладного розбору, як він стикується з усією іншою платформою. "Священна корова" Java — це зворотна сумісність. Можна зробити фічу, яка її зламає (точніше, можна зламати її в якихось дрібних речей, але потрібно дуже гарне обґрунтування, чому ви це ламаєте, і які у користувача є шляхи обходу).
Value Types вирішує дуже просту проблему. Одним із стовпів Java як мови програмування є негласне властивість, що (майже) все — об'єкт. В цьому криється цікава грабля: вона виникає з того, що у об'єктів Java є індивідуальні властивості. Наприклад, ідентичність (identity): якщо ви зробили об'єкт, у якого записано число 42 в якомусь його полі, і другий об'єкт, в якому лежить "таке ж" число 42, то ці 2 об'єкти — різні з точки зору мови, та відрізняються як раз за рахунок identity. З точки зору реалізації, це означає, що і зберігати потрібно дві окремі копії цих практично однакових об'єктів — наприклад, щоб було де зберегти метаінформацію про них. І коли у додатку з'являються великі графи об'єктів, накладні витрати для кожного об'єкта пожирають істотну частину корисної пам'яті. Було б непогано, якби була в мові були б сутності без identity, для яких цього можна було б уникнути. І такі сутності є: примітиви! Але їх список жорстко зафіксований. Природне розширення — дати можливість декларувати сутності, які записуються як класи, а працюють як примітиви — це і є value types.
Value types істотно відрізняються від звичних reference-типів. Наприклад, чи є Object супертипом для всіх value-типів? Логічно, що ні, і тоді з'являються тонкі моменти взаємодії з дженериками, зі спеціалізацією і т. п. Є бібліотеки, які роблять подібного роду спеціалізацію руками (той же GNU Скарб), але всім хочеться, щоб це було реалізовано в самій мові. Так що ця дуже очікувана фіча: відомо, які бонуси вона принесе; відомо зараз, які виникнуть проблеми. Проте в ході розробки ми подивимося ще, скільки там реально бонусів, а скільки проблем.
Jug.Ru: Враховуючи, що проблеми продуктивності — скоріше приватні, ніж глобальні, можна говорити про якийсь типовою схемою при оптимізації додатків?
Олексій Шипилев: Існують цілком конкретні методології, які наказують, куди в першу чергу варто дивитися на підставі тих чи інших симптомів. Ми з Сергієм Куксенко та іншими робили доповіді на цю тему.
Наприклад, можна говорити, що у нас дуже хороші збирачі сміття, але, як не крутись, якщо будеш дуже багато смітити, ти в підсумку збірка сміття буде займати значну частину часу. Який рантайм ти не напиши, а якщо програміст руками написав сортування бульбашкою або лінійний пошук по масиву в 100 мільйонів елементів, швидко не буде. Тут немає ніякої магії — один дурень може таку задачку загадати, на яку семеро мудреців не дадуть.
З мого досвіду можу сказати, що якщо продуктивністю конкретного додатка ніхто ніколи не займався або займався погано, то там майже напевно (на 99%) є безліч ідіотських або очевидних неэффективностей, які можна швидко виявити і швидко виправити, піднявши продуктивність в рази.
Jug.Ru: А крім сміття, які є типові проблеми, що легко піддаються оптимізації?
Олексій Шипилев: Мої улюблені — проблеми з багатопоточністю. Відомо, що найпростіший спосіб написати коректне багатопотокове додаток — це щедро використовувати синхронізацію. Я не кажу, що ця практика порочна, але часто зустрічаються проблеми з тим, що апаратні ресурси використовуються не повністю із-за постійних блокувань. Це дуже легко діагностується, і часто легко виправляється (часто, щоправда, вимагає правок в архітектурі).
Дуже часто зустрічаються алгоритмічні проблеми, коли переписуванням поганих шматочків коду на хороші моменти, які або мають кращу алгоритмічну складність в принципі, або яким-небудь способом використовують специфічні знання про дані у додатку, виходять гігантські прирости, які ніяким рантаймным оптимізацій і не снилися.
JDK/JVM-специфічні проблеми зустрічаються, але рідко. Сюди падають і проблеми з щільністю даних в пам'яті (звідки нам знову махають рукою value types), і проблеми з високорівневими оптимизациями (escape-аналіз і автовекторизация, привіт!), і проблеми з кодогенерацией. І тут слизьке питання — проблема в тому, що рантайм поганий і не працює "правильно", або у тому, що ми не хочемо в даному випадку якось змінити рішення, щоб у нас була продуктивність краще (наприклад, використовувати додаткову бібліотеку). Різні люди і різні організації дивляться на це по-різному.
Взагалі з моєї точки зору оптимізація продуктивності Java-додатків принципово не відрізняється від оптимізації якогось нативного додатка, в якому JVM не бере участь. JVM — це, звичайно, окремий рівень в цій ієрархії, але багато проблем, які там існують, притаманні розробці взагалі, а не розробки конкретно на Java.
Jug.Ru: Враховуючи, що певні проблеми все ж існують, є сенс використовувати Java для високопродуктивних додатків?
Олексій Шипилев: Знаєте, коли я був школярем, один з моїх викладачів у відповідь на єхидний питання когось із моїх друзів, чому ж ми не пишемо на такому-швидкому, сказав таку річ: "Я буду писати мій промисловий код на Pascal (популярному в ті далекі часи), тому що він скрізь мені підкладе підстилки, скрізь все перевірить, чи не дасть мені вистрілити собі в ногу. А в тому місці, де мені важлива швидкість, я вже обману його так, щоб було швидко". І ця історія повторюється з різними дійовими особами і з різними мовами: Pascal проти C, Java проти C++, C проти асемблера і т. п. На ділі продуктивність великої програми на горизонті осудних приростів, як правило, визначається продуктивністю досить маленького шматка в цьому додатку. Тому може бути простіше не бешкетувати та не писати мовою, що вас змушує писати низькорівневий код, тому що ви з розуму зійдете. Варто писати на высокоуровневом мовою, а там, де треба, обдурити його: зробити так, щоб у конкретних місцях було швидше, перейшовши або до менш идиоматическому кодом, повторює кривизну бібліотек і рантайма, або віддавши важке на рівень нижче. Практика промислової розробки на Java і історія її продуктивності в чому цей підхід уособлює.
Найближчий доповідь Олексія відбудеться на конференції Joker 2016 у форматі кейноута і, звичайно ж, він буде присвячений продуктивності платформи і способів підвищення продуктивності вашого коду.
Java Performance очима розробника


Jug.Ru: Розкажіть, будь ласка, про себе і про свою роботу.
Олег Анастасьєв: Я працюю в команді платформи в компанії Однокласники. Команда платформи розробляє програми для того, щоб Однокласники працювали швидко, тобто розробляє і підтримує різні сховища даних, фреймворки для комунікації серверів один з одним і т.п. Крім того, якщо що-то з швидкодією трапляється на продакшені, саме команда платформи шукає рішення, як це лікувати. Наша відповідальність — зробити так, щоб Однокласники працювали швидко, тому що якщо вони не будуть працювати швидко, вони просто працювати не будуть — дуже швидко заваляться під навантаженням.
Jug.Ru: Як ви вважаєте, чи є сенс використовувати Java для високонавантажених додатків? Або просто немає альтернативи?
Олег Анастасьєв: Можливо, Java — не найшвидший мова, існують і більш швидкі мови. Але якщо розглядати Java, як мова для розробки великих навантажених проектів, то тут їй в принципі альтернатив поки що немає. Можна написати більш швидкий код на мовах C або C++, але при цьому цей код буде більш дорогий — його написання, налагодження та подальша підтримка будуть коштувати значно дорожче, ніж аналогічний код на Java. Крім того, до коду на C, який стирчить в інтернет, виникає дуже багато питань безпеки. Як відомо, у мові З можливі всякі небезпечні конструкції, через які потім нехороші люди будуть вас зламувати. На Java таких небезпечних конструкцій менше, тому в частині безпеки програма на Java вимагає менших зусиль.
У підсумку Java має дуже хороше співвідношення ціна / продуктивність.
У Java є ряд проблем, зокрема, нам довелося окремо працювати над управлінням пам'яттю і підтримкою високого трафіку, але вони можуть бути вирішені за допомогою невеликої кількості коду — ми для цього створили окрему бібліотеку one-nio (посилання на https://github.com/odnoklassniki/one-nio). Вся інша маса коду володіє тими позитивними рисами Java, які у неї є — швидка розробка, безпека, хороший інструментарій діагностування проблем, вбудований в JVM, захист від помилок і т. д.
Jug.Ru: Ви згадали, що довелося вирішувати певні проблеми продуктивності. Розкажіть про них детальніше?
Олег Анастасьєв: Для нас швидкодія — це не тільки швидкість виконання коду на Java. Ми розглядаємо його і з ракурсу ефективного використання ресурсів є обсяг оброблюваних даних, і пропускна здатність, і споживання пам'яті. І тут в Java, дійсно, багато чого не вистачає: колекцій примітивів, struct-ів, роботи з оффхипом, прозорого використання нативних API, управління affinity, файловими кешами і т. д., тому нам доводиться розробляти рішення, які дозволяють обходити вузькі місця.
Для нас швидкодії Java у такому трактуванні не вистачає, більше того, це питання не може чекати кілька років наступного релізу Java — проблеми повинні вирішуватися прямо зараз, тому ми активно шукаємо їх вирішення самі. Давайте зупинимося на цьому докладніше.
Наприклад, одна з больових точок Java — це швидкодію введення-виведення, як блокуючого, так і не блокуючого (зокрема, мережевого).
Це добре видно на прикладі роздачі відео. Загальний вихідний трафік відео зараз досягає 500 гігабіт. Для того, щоб обслуговувати такий потік ми повинні роздавати відео як можна швидше, щоб як можна більше трафіку припадало на один сервер. Наше залізо здатне віддавати 40 Гбіт з машини, але написати на Java за допомогою стандартних рішень сервер, який буде використовувати всі 40 Гбіт у вас не вийде — буде занадто багато втрат в продуктивності всередині самої Java. Це одна з проблем, які ми вирішували в складі нашої open source бібліотеки.
Приклад з 40 Гбитным трафіком — це свого роду екстремум. Є і менш навантажені сервера, але там теж є свої проблеми. Наприклад, ще одна больова точка Java — це зберігання великої кількості об'єктів у пам'яті. У Java є garbage collector. З одного боку, це добре, оскільки він дозволяє автоматично прибирати сміття. Але з іншого боку, коли вам потрібно кешувати в пам'яті дуже багато інформації, він скоріше заважає, ніж допомагає. Більш того, якщо масив даних на сотню гігабайт зберігається в пам'яті, то ви захочете, щоб він не загубився при перезапуску програми — його завантаження займе значний час. Такі масиви хочеться зберігати в поділюваної пам'яті, а вбудованих засобів в Java для цього теж немає. У таких місцях хочеться мати ручне керування пам'яттю. Добре, що в Java є Unsafe, через який ми і зробили власне рішення.
Jug.Ru: чи Розвивається JDK в напрямку вирішення специфічних для ваших завдань проблем? З'являються нові опції, які ви можете використовувати?
Олег Анастасьєв: Остання випущена в світ версія Java — 8. У ній рішень згаданих проблем немає. Є тільки намір вирішити деякі з цих проблем в Java 9, частина з них — в 10 і більш пізніх. Але вийде чи не вийде; наскільки запропоновані рішення будуть краще, ніж є зараз, говорити поки рано, оскільки, наприклад Java 9 ще не вийшла. Звичайно, бета-версію вже можна брати, але що зміниться, поки вона дійде до релізу, не відомо. Тому вийде Java — подивимося.
Jug.Ru: А чи є якісь очікувані нововведення, які могли б вам допомогти? Приміром, VarHandles?
Олег Анастасьєв: Допоможе чи ні нам VarHandles залежить від того, як вони будуть реалізовані в кінцевій версії, і як швидко вони будуть працювати.
VarHandles — досить складний спосіб навіть з точки зору API зробити те, що зараз можна зробити просто і зрозуміло через Unsafe. Це можливість оголосити масив в пам'яті одного типу, а потім читати його, як масив пам'яті іншого типу. Для людини, знайомого з принципами асемблера або C, це виглядає як звернення за адресою пам'яті і зчитування комірки пам'яті, як Long або як Byte, в залежності від ситуації. Грубо кажучи, VarHandles дозволяє зробити те ж саме, але більш складно (це технічно більш складне рішення на рівні JDK), але зате він має більше захисту від програмістів, які періодично "стріляють собі в ноги".
До того ж, VarHandles вирішують тільки один з сценаріїв використання Unsafe. Але ми використовуємо Unsafe також і зовсім для інших сценаріїв (наприклад, для кастомних серіалізації або роботи з пам'яттю, що розділяється), і альтернатив цьому раніше Java 10 точно не очікується.
Jug.Ru: На підставі чого у вашій компанії приймається рішення про необхідність роботи над продуктивністю додатків?
Олег Анастасьєв: Ми не займаємося швидкодією заради швидкодії; завжди оцінюємо економічний ефект від оптимізації.
Для нас продуктивність вимірюється в кількості грошей, які потрібно витратити на залізо: спочатку на закупівлю серверів, а потім кожен рік на їх підтримку в дата-центрі. Чим більше швидкодію програм — тим менше заліза необхідно на ту ж задачу, тобто менше грошей піде на його підтримку.
Оптимізація продуктивності відштовхується від поставлених завдань. Чи можемо ми дозволити собі ось це кількість техніки для цієї конкретної задачі?
наприклад, витратити, умовно кажучи, рік роботи висококваліфікованого програміста, щоб поліпшити швидкодію на 0,5%, економічно не ефективно до тих пір, поки у вас не порушені якоюсь проблемою, припустимо, всі сервери в дата-центрі. Тоді це буде економічно ефективно, і ми будемо цим займатися. Якщо ні, нам простіше купити нових серверів, тобто вирішити проблему залізом.
Для нас швидкодія — це бізнес-метрика; і вона має чітке економічне обґрунтування. Швидкодія пиляється до тих пір, поки воно економічно ефективно.
Незважаючи на всі питання до продуктивності рішень на Java, відрадно бачити, що над цими проблемами йде безперервна робота, а сама Java, незважаючи на всі нападки, давно стала промисловим стандартом для масштабних високонавантажених корпоративних проектів.


Більше цікавих доповідей, технічних, хардкорних, ви знайдете в програмі Joker 2016. Пропонуємо вашій увазі декілька прикладів:
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.