Діалоги про Java Performance

Кожен рік на JPoint експерти виступають з хардкорными доповідями про продуктивність Java. І жодного разу не було нудно — питання зберігає актуальність на протязі багатьох років. Про те, звідки ростуть ноги у міфів, що робить JVM, як вимірювати продуктивність, при чому тут бізнес-вимоги замовника та як обійти частину граблів ми поговорили з експертами, для яких Java performance — не проблема, а робота.


Java Performance і всемогутня JVM
Що відбувається на стороні Java Virtual Machine (JVM), як вона впливає на продуктивність? Про це та трохи про Java performance в цілому ми поговорили з JVM Engineer в SAP Фолькером Симонисом (Volker Simonis). Фолькер відповідав на питання по-англійськи, тут ми публікуємо переклад.

— Чому кажуть, що Java повільна і проблеми продуктивності критичні?


— Я думаю, що сприйняття Java як чогось вайлуватого залишилося в минулому. В даний час віртуальні машини Java мають реально наворочені JIT-компілятори та алгоритми збирання сміття, які забезпечують дуже хорошу продуктивність для звичайних додатків. Звичайно, ви завжди можете знайти приклади, де Java повільніше, ніж нативні C/C++ програми. Але такі системи, як Hadoop, Neo4j або H20 — приклади того, що навіть великі, складні високонавантажених програми можуть бути написані на Java. Тобто продуктивність Java дуже висока і як технологія Java — вельми конкурентоздатна.

З мого досвіду люди сьогодні більше скаржаться на продуктивність «альтернативних» мов для JVM, таких як Ruby (JRuby), Clojure або Scala. Я думаю, що JVM зможе оптимізувати їх так само добре, як і чистий Java-код. Це лише питання часу.

Як оцінювати та вимірювати продуктивність Java?

— Вимірювання продуктивності Java — це справжня наука. Немає нічого дивного в тому, що такі компанії, як Oracle, Google, Twitter або Однокласники мають виділені групи по роботі з продуктивністю, що складаються з дуже досвідчених і кваліфікованих співробітників.
Звичні підходи профілювання продуктивності Java-додатків, швидше за все, завжди будуть терпіти крах.

В даний час Java Micro Harness або, для стислості, JMH Олексія Шипилева (зверніть увагу, саме через «е», він наполягає на цьому, принаймні, в російськомовних публікаціях), який є частиною OpenJDK, зарекомендував себе як стандарт вимірювання та аналізу продуктивності невеликих і середніх Java-додатків. Складність і винахідливість реалізації JMH дасть вам уявлення про те, що JMH робить для того, щоб точно виміряти продуктивність програми на Java. Його блог на http://shipilev.net/ — незамінне джерело інформації з питань продуктивності та оптимізації Java.

І поки JMH, в основному, відповідає за вимір і оптимізацію продуктивності Java-коду, продовжує існувати проблема поліпшення продуктивності GC. Та це вже інша історія, яка вимагає свій, інший набір інструментів і експертизу.

— Де найчастіше бувають проблеми з продуктивністю?

— Це, звичайно, значною мірою залежить від типу вашого Java-додатки. Якщо мова про програми, прив'язаним до даних (тобто, якщо виконується простий код на величезних даних), ймовірно, більш важливо правильно вибрати і правильно налаштувати ваш GC-алгоритм. І навіть в цьому випадку вам спочатку доведеться вибрати, чи хочете ви оптимізувати додаток для максимальної пропускної здатності або ж ви вважаєте більш важливим latency програми.

З іншого боку, якщо ваш додаток залежить від обчислень, ви можете використовувати інструменти типу JMH та/або нативних профайлеров, щоб перевірити, що віртуальна машина дійсно повністю вбудовує і оптимізує всі навантажені виконувані гілки.
Для жорстко розпаралелених додатків краще вибрати правильну політику синхронізації/блокування (яка може змінюватись в залежності від заліза/платформи, на яких працює ваш додаток) та для вводу/виводу пов'язаних додатків важливо вибрати правильну абстракцію (тобто блокуючий vs неблокуючий) і API.

Але головне золоте правило: велика частина зусиль повинна бути спрямована на правильність алгоритмів, тому що ні одна віртуальна машина ніколи не зможе перетворити погано написаний код у високопродуктивну програму. Тонка і точна настройка на рівні JVM, як пояснено вище, завжди повинна бути тільки останнім кроком циклу розробки.

— Ви — експерт з JVM. Як часто JVM стає причиною низької продуктивності? У чому основні проблеми і як з ними боротися?

— В ідеалі, після певної кількості ітерацій Java-додаток повинен досягти стану, при якому щонайменше 95% часу працює скомпільований JIT-му код. При цьому відбуваються регулярні, але досить короткі GC-паузи. Це можна легко перевірити за допомогою таких інструментів, як JConsole, JITwatch або Flight Recorder. Якщо це не так, то потрібно з'ясувати, яка частина віртуальної машини викликає проблему і затюнить (або заоптимизировать) відповідний компонент VM.

Ви, можливо, знаєте, що продуктова версія HotSpot JVM пропонує близько 600 так званих додаткових опцій (це ті опції, які починаються з -ХХ), а debug-збірка включає понад 1200 таких опцій (щоб отримати повний список, ви можете використовувати ключ -XX:+PrintFlagsFinal). З допомогою цих опцій ви можете отримати детальну інформацію про те, що відбувається всередині віртуальної машини. Але найголовніше, що ці опції можуть використовуватися для тонкої настройки практично будь-якої підсистеми віртуальної машини. Погана новина полягає в тому, що ці опції не дуже добре документовані, так що, ймовірно, вам доведеться заглянути в исходники, щоб зрозуміти, що відбувається насправді.

Деякі проблеми зі збиранням сміття можуть вирішитися шляхом вибору правильного Garbage Collector (у поточній версії HotSpot їх там повно) і правильної конфігурації heap-а. Проблеми продуктивності у створеному коді часто спричиняються неправильним вибором того, що потрібно инлайнить. Знову-таки, це впливає певною мірою, але, на жаль, це дуже складна тема і важко домогтися поліпшення в одній частині коду без зниження продуктивності інших частин.

Блокування, очікування потоків і різні ефекти з кэшами – ще одне джерело частих проблем. А ось для їх вирішення потрібно глибоке знання віртуальної машини, так само як і знання нутрощів апаратної платформи і операційної системи.

Нарешті, я хочу зазначити, що останнім часом ми все частіше бачимо проблеми з роботою Java у віртуальних середовищах. JVM здійснює велику кількість «самоконфигураций» при запуску, щоб оптимально адаптуватися до навколишнього середовища. Але от якщо інформація про реальних ресурсах (таких як кількість CPU або обсяг доступної пам'яті), не відповідають тому, що отримала JVM, це може призвести до дуже поганий продуктивності.

Наприклад, якщо операційна система рапортує про великій кількості доступних логічних CPU, віртуальна машина створить безліч потоків для збирання сміття та для JIT-а. Але якщо хостова операційна система буде розподіляти ці потоки на зовсім невелику кількість фізичних CPU, то користувальницькі потоки Java-додатки можуть бути повністю витіснені системними потоками віртуальної машини. Схожа проблема виникає, якщо гостьова операційна система виділяє для якоїсь JVM великий обсяг пам'яті, але ця пам'ять порпається (ділиться) між іншими гостьовими ОС.

— Як зараз реалізуються алгоритми керування пам'яттю в JVM, що робиться для того, щоб розробники отримували менше проблем з продуктивністю?

— Насправді, віртуальна машина HotSpot використовує різні системи управління пам'яттю і стратегії. Найбільша і сама, напевно, відома для звичайного програміста на Java — це, звичайно, heap плюс працюють на цьому heap-е алгоритми збирання сміття.

Однак JVM працює ще з безліччю інших пулів пам'яті. Існує metaspace, який зберігає завантажені класи і кеш для зберігання згенерованого віртуальною машиною коду (самий, мабуть, відомий вид коду – це скомпільовані JIT-му методи, а також згенеровані шматки інтерпретатора і різні заглушки коду).
Ну і нарешті, різні підсистеми віртуальної машини, такі як GC або JIT-компілятор, можуть на деякий час зажадати істотну частину нативної пам'яті для того, щоб виконати свою роботу. Ця пам'ять зазвичай виділяється на льоту і підтримується в різних сегментах, ресурсних зонах або кеші підсистеми. Наприклад, JIT-компіляції великого методу може тимчасово зажадати більше гігабайти нативної пам'яті. Всі ці пули пам'яті можуть бути налаштовані під вимоги конкретного додатка.

— Розкажіть, будь ласка, цікаву історію з вашої практики, коли була проблема продуктивності Java. Як вона була вирішена?

— Наша SAP JVM працює на безлічі різних платформ, серед яких є HP-UX на PARISC і Itanium CPU. HP-UX на Itanium забезпечує програмну емуляцію для бінарних файлів PARISC, що робить можливим легко й прозоро запускати додатки PARISC. Правда, на порядок повільніше, ніж рідні додатки.

Після того, як ми отримали дуже багато скарг клієнтів на дуже погану продуктивність нашої JVM на HP-UX/Itanium, ми стали розбиратися, в чому справа. Виявилося, що клієнти використовували двійкові файли PARISC для запуску на HP-UX/Itanium. Оскільки пояснювати кожному клієнту складності емуляції ми не могли, ми просто додали в чергову версію нашої JVM перевірку, яка забороняє виконання коду для PARISC в режимі програмної емуляції.

Java Enterprise Performance для
Погодьтеся, неприємний момент, коли додаток вийшло в продакшен і повернулося з обуреними зауваженнями клієнта з приводу гальмування і зависання. Проблеми продуктивності Java критичні, насамперед, для Enterprise рішень. Саме тому за хардкорными практичними порадами ми вирушили до експерта, Performance Architect в NetCracker (telecom solutions company), Володимиру Ситникову.

«Про можливі проблеми продуктивності Java доводиться чути як в навчальній, так і професійного середовища. У кожному жарті є частка жарту. Частково повільність Java — це міф, тим більше, що продуктивність платформи змінюється з часом і те, що викликало критику років 10 тому, зараз працює зовсім по-іншому. Міркувати про Java performance на рівні «гальмує — не гальмує» необачно.

На практиці, основні проблеми в промислових додатках пов'язані з наявністю зайвого коду, коли дій виконується більше, ніж потрібно для вирішення задачі. Десь розробник перемудрував, а де-то бізнес-вимоги так заплутані, що з першого разу швидко не напишеш. Іноді продуктивності заліза вистачає, а іноді рішення працює повільніше, ніж очікувалося. Основна рекомендація тут одна — не виконувати непотрібний код або хоча б його частину.

Ще одна причина зниження продуктивності криється в алгоритмах; коли, наприклад, використовують повний перебір замість того, щоб знайти запис з алгоритмічного ознакою. Але на практиці хитромудрі (більш розумні, ніж HashMap) структури даних рідко використовуються для підвищення продуктивності.

Можуть впливати на продуктивність і сторонні інструменти розробки, наприклад, бібліотеки і фреймворки. На практиці проблеми можуть бути на всіх рівнях — і вони трапляються. Розробники зазвичай виявляють їх на тестах, проводиться аналіз за допомогою різних технік профілювання (на рівні браузера, сервера додатків, бази даних). Проблеми зі стороннім кодом виникають часто, і єдиний спосіб відловити їх — робити заміри і ретельно стежити за розвитком цього самого стороннього коду. В моїй практиці, що не місяць, то знахідка. Буває, заміна одного рядка коду прискорює вставку даних у базу в 10 разів.

Взагалі, головне в вимірі і роботі з продуктивністю Java — не забувати про навантажувальному тестуванні. Відповідно, наскільки великі і разлаписты бізнес-вимоги до enterprise-розробці, настільки величезні тести, які їх перевіряють і допомагають помітити неприємні моменти, пов'язані з performance. Наприклад, потрібно враховувати, що навантаження в корпоративному середовищі нерівномірна: вранці всі прийшли, ввійшли в систему, пік навантаження, потім до обіду йде спад, в обід навантаження найменша, а після обіду користувачі з новою силою підвищують навантаження до високих значень. Звичайно, при першому аналізі такі нюанси можуть бути упущені. А по факту вони можуть дуже сильно впливати на те, де очікувати масової навантаження і як писати код, щоб уникнути неприємностей з продуктивністю.

Ви ось мене питаєте про найбільш типові помилки та граблі. У кожного вони свої. Наприклад, вибір конкретної моделі процесора або пам'яті рідко що змінює. Звичайно, якщо порівнюються зразки 2006 та 2016 років, то модель має значення. Багато питань лежить у площині масштабованості. Багато хто перебуває у пошуках святого Грааля enterprise-розробки: всі хочуть, зробивши єдиний вимір, передбачити, скільки потрібно серверів і якої потужності для розгортання програми. Тут єдиний вихід — тестувати на живому сервері, враховувати запаси ресурсів для відмовостійкості.

Взагалі, напевно, є універсальний інструмент оптимізації — невідворотність тестування. У нас в компанії є фахівці по навантаженню тестування та аналізу результатів, але основний ефект досягається тоді, коли розробник сам відповідає за швидкість роботи коду. Продуктивність — це не те, що можна взяти і додати постфактум. Передбачити, де буде проблема, важко, — підозрювати кожну рядок вихідного коду не вистачить ніякого часу. Загальна проблема в тому, що тестування було проведено або не так, або не на тому обсязі даних, або не на тій навантаженні і конфігурації. Наприклад, при тестуванні бібліотеки не оновили, а замовник взяв і оновив — вважайте, проблема пропущена.

На моїй пам'яті були проекти, які пробували обійти питання продуктивності стороною, але чи це були маленькі демо-проекти, або ті, які дуже швидко поверталися зі скаргами клієнтів на повільну роботу програми.

Часто до зниження продуктивності або падіння всього додатки призводить не якась одна глобальна помилка, а комбінація двох-трьох проблем, кожна з яких, на перший погляд, не представляє загрози. І найстрашніше, що розробник може і не дізнатися про проблеми, що виникли в його коді. Якщо проблему вдається вирішити силами інженерів технічної підтримки, то рідко шукають автора коду, щоб «нагородити» його.

До речі, про скарги. Якщо клієнт обурюється, щоб зрозуміти, де шукати проблему, є простий алгоритм. Потрібно запитати представника замовника, а як повинно бути. Якщо все відпрацьовує повільно і вам здається, що справа в суб'єктивних відчуттях, уточніть, за який час повинно працювати. Такі вимоги, звичайно ж, краще збирати теж заздалегідь (під час розробки, або до її початку). Ми вимоги розбиваємо по компонентів: наприклад, якщо користувач хоче, щоб сторінка відкривалася за 5 з, ми розподіляємо бюджет на браузер — 1 з, на серверну частину — 3,5 с, мережа — 0,5 с і так далі. Найчастіше розбивка йде аж до компонентів самої системи, які, до речі, логируют час роботи, і розробники, відкривши профайлер, можуть перевірити вкладається їх код в допустимі межі.

Отже, проблеми є. Куди дивитися, звідки ноги ростуть?

  • Подивитися на завантаженість CPU і вміст GC логів. Якщо система завантажена, і при цьому навантаження викликана роботою GC, то або пам'яті мало (потрібно підвищувати -Xmx), або «хтось занадто багато їсть».

  • Сама по собі 50-100% завантаженість вже говорить про те, що часи відгуку можуть утворюватись самими неймовірними.

  • Буває, вина лежить на сторонніх додатках. Невинне оновлення може привезти пачку гальм «в подарунок». Канонічний приклад — історія з RedHat Linux: механізм transparent huge pages викликає проблеми з продуктивністю. Це «фіча» Red Hat Linux, з-за якої «раптом» починають гальмувати як java-додатки, так і бази даних. Проблемі схильні Red Hat Linux 6+ (CentOS 6+). посилання можно почитати докладніше. THP режим потрібно обов'язково відключати. Якщо подібна налаштування заліза і програм виконується вручну, то не виключений людський фактор, і в підсумку — гальма.

  • Задавати класичні запитання «працювало воно раніше» і «що міняли». Питати, що робили на стороні клієнта, накочували оновлення на компоненти системи. Якщо накочували — вектор пошуку стає зрозуміліше. Якщо ясності немає, пам'ять вільна, тоді дивимося в профайлер, яка активність займає ресурси. Ну а далі у кожного своя історія зі своїм кодом.
Тепер подивимося на проблему продуктивності ближче до реальних умов, до клієнта. Нерідко додаток абсолютно несподівано веде себе в продакшені, хоча на тестах все було ідеально. І цьому є пояснення. Якщо ситуація відтворюється тільки у клієнта, то це може говорити про проблеми з вихідними даними. Наприклад, ми знаємо, що додатком належить здійснювати операції з 1'000'000 клієнтами. Ми генеруємо 1'000'000 випадкових імен і прізвищ, тести проходять добре, пошук клієнта по ПІБ літає. Виходимо в продакшн — користувачі обурюються. Дивимося уважніше на проблемний пошук — і виявляється, що Іванових Іванів Івановичів тисяча. Якщо в навантажувальних тестах не врахувати таку кількість повторень, то не дивно, що на тестах пара знайдених рядків відображаються миттєво, а в експлуатації пошук працює через раз.

Згенерувати коректний набір даних для тестів — ціла наука. Потрібно створити не просто осмислені, припустимо, 100 ГБ даних, але і врахувати взаємодії між даними, повинно бути максимально схоже на те, що буде в експлуатації.

  • Ідеальний випадок — наявність дампу бази клієнта. Його можна використовувати безпосередньо або попередньо зашифрувати. Причому шифрування має враховувати існуючі співвідношення і посилання між об'єктами і забезпечувати безпеку даних одночасно. Наприклад, проста заміна всіх значень на MD5, не є достатньо безпечною. Навіть пошук у Google за MD5 може знайти «початкове значення. Правильніше використовувати HMAC, щоб з одного боку однакові дані хешировались в однакові суми, але при цьому дешифрация буде неможлива.

  • Буває і так, що дампа немає. В такому випадку вивчаються параметри майбутньої системи і пишуться сценарії, які генерують тестові дані на основі цих параметрів. При цьому варто уникати рівномірних розподілів, адже, в реальності рідко коли розподілу рівномірні.
Що ще можна сказати про Java performance для enterprise? Збирайте нефункціональні вимоги замовника: розбивку за сценаріями роботи, частоту взаємодій користувача з тим чи іншим модулем програми, кількість користувачів і очікуваний час роботи. Виправлення проблем продуктивності на пізніх етапах може коштувати дуже дорого, а зайва секунда поки чекає оператор колл-центру призводить до серйозних втрат».



Якщо у вас виникли питання по висловлювань експертів або ви хочете розповісти про свою позицію з питань продуктивності Java, ласкаво просимо коментарі на конференцію JPoint 2016 22-23 квітня. Всі там будемо!

Джерело: Хабрахабр

0 коментарів

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