Чи набагато важче Java "налаштувати" продуктивність порівняно з C / C ++? [зачинено]


11

Чи заважає "магія" JVM впливати програмісту на мікрооптимізацію на Java? Нещодавно я читав на C ++, іноді впорядкування членів даних може забезпечити оптимізацію (надано, в мікросекундному середовищі), і я припускаю, що руки програміста пов'язані, коли справа доходить до стискання роботи Java?

Я вдячний, що гідний алгоритм забезпечує більшу швидкість, але коли у вас є правильний алгоритм, Java важче налаштувати за рахунок управління JVM?

Якщо ні, чи могли б люди навести приклади, які трюки можна використовувати на Java (окрім простих прапорів компілятора).


14
Основний принцип усієї оптимізації Java полягає в наступному: JVM, мабуть, вже зробив це краще, ніж ви можете. Оптимізація здебільшого передбачає дотримання розумної практики програмування та уникання звичних речей, таких як об'єднання рядків у циклі.
Роберт Харві

3
Принцип мікрооптимізації на всіх мовах полягає в тому, що компілятор вже зробив це краще, ніж ви можете. Інший принцип мікрооптимізації на всіх мовах полягає в тому, що кидати на нього більше апаратних засобів дешевше, ніж мікрооптимізація часу програміста. Програміст повинен схилятися до масштабування проблем (субоптимальних алгоритмів), але мікрооптимізація - це марна трата часу. Іноді мікрооптимізація має сенс у вбудованих системах, де ви не можете кинути на неї більше обладнання, але Android, що використовує Java, і досить погана його реалізація показує, що більшість з них вже має достатньо обладнання.
Ян Худек

1
для «трюкам продуктивності Java», заслуговує вивчення є: Effective Java , Angelika Langer Links - Java Performance і пов'язаних з продуктивністю статті Брайана Гетца в теорії і практиці Java і Threading легковажно серії перераховане тут
комар

2
Будьте вкрай обережні щодо порад та рекомендацій - JVM, операційні системи та апаратне забезпечення рухається - вам найкраще вивчити методологію настройки продуктивності та застосувати удосконалення для вашого конкретного середовища :-)
Martijn Verburg,

У деяких випадках VM може робити оптимізацію під час виконання, що недоцільно робити під час компіляції. Використання керованої пам’яті може підвищити продуктивність, хоча вона також часто матиме більший слід пам’яті. Невикористана пам'ять звільняється, коли зручно, а не якнайшвидше.
Брайан

Відповіді:


5

Впевнений, що на рівні мікрооптимізації JVM зробить деякі речі, над якими ви будете мало контролювати порівняно з C та C ++.

З іншого боку, різноманітність поведінки компілятора з C і C ++, особливо, матиме набагато більший негативний вплив на вашу здатність робити мікрооптимізацію будь-яким невиразним портативним способом (навіть через версії компілятора).

Це залежить від того, який проект ви налаштовуєте, на які середовища ви орієнтуєтесь тощо. Зрештою, це насправді не має значення, оскільки ви отримуєте на кілька порядків кращі результати від оптимізації алгоритмічної / структури даних / програм.


Це може мати велике значення, коли ви знайдете, що ваш додаток не змінюється в масштабах ядер
Джеймс

@james - дбайливо розробити?
Теластин

1
Дивіться тут для початку: механічна-
Джеймс

1
@James, масштабування по ядрах має дуже мало спільного з мовою реалізації (крім Python!), І більше стосується архітектури додатків.
Джеймс Андерсон

29

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

Однак є одна важлива область оптимізації, де C ++ та Java принципово відрізняються, і це масовий доступ до пам'яті. C ++ має керування пам'яттю вручну, а це означає, що ви можете оптимізувати компонування даних програми та шаблони доступу, щоб повністю використовувати кеші. Це досить важко, дещо специфічно для апаратного забезпечення, на якому ви працюєте (тому підвищення продуктивності може зникнути на різних апаратних засобах), але якщо зробити все правильно, це може призвести до абсолютно захоплюючої продуктивності. Звичайно, ви платите за це з потенціалом для всіх видів жахливих помилок.

З мовою, зібраною зі сміттям, як Java, таких оптимізацій неможливо виконати в коді. Деякі з них можна виконати під час виконання (автоматично або через конфігурацію, див. Нижче), а деякі просто неможливо (ціна, яку ви платите за захист від помилок управління пам’яттю).

Якщо ні, чи могли б люди навести приклади, які трюки можна використовувати на Java (окрім простих прапорів компілятора).

Прапорці компілятора в Java не мають значення, оскільки компілятор Java майже не оптимізується; час виконання.

Дійсно, у програмах Java є безліч параметрів, які можна змінити, особливо це стосується сміттєзбірника. У цих варіантах немає нічого простого - типові параметри корисні для більшості програм, а для кращої продуктивності потрібно зрозуміти, що саме роблять варіанти та як поводиться ваша програма.


1
+1: в основному те, що я писав у своїй відповіді, можливо, краще формулювання.
Клаїм

1
+1: Дуже хороші моменти, пояснені дуже стисло: "Це досить важко ... але якщо зробити все правильно, це може призвести до абсолютно захоплюючої продуктивності. Звичайно, ви платите за це з потенціалом для всіх видів жахливих помилок . "
Джорджіо

1
@MartinBa: Ви більше платите за оптимізацію управління пам’яттю. Якщо ви не намагаєтеся оптимізувати управління пам'яттю, керування пам'яттю C ++ не так вже й складно (уникайте її повністю через STL або зробіть це порівняно просто за допомогою RAII). Звичайно, реалізація RAII в C ++ займає більше рядків коду, ніж нічого не робити в Java (тобто тому, що Java обробляє це за вас).
Брайан

3
@Martin Ba: В основному так. Данглінг-покажчики, переповнення буфера, неініціалізовані покажчики, помилки в арифметиці вказівника, все те, що просто не існує без ручного управління пам'яттю. А оптимізація доступу до пам'яті в значній мірі вимагає зробити багато ручного управління пам'яттю.
Майкл Боргвардт

1
Є кілька речей, які можна зробити в Java. Один - об'єднання об'єктів, що збільшує шанси на пам'ять об'єктів (на відміну від C ++, де це може гарантувати локальність пам'яті).
RokL

5

[...] (надано, у мікросекундному середовищі) [...]

Мікросекунди додаються, якщо ми перебираємо мільйони на мільярди речей. Особистий сеанс vtune / мікро-оптимізації з C ++ (без алгоритмічних удосконалень):

T-Rex (12.3 million facets):
Initial Time: 32.2372797 seconds
Multithreading: 7.4896073 seconds
4.9201039 seconds
4.6946372 seconds
3.261677 seconds
2.6988536 seconds
SIMD: 1.7831 seconds
4-valence patch optimization: 1.25007 seconds
0.978046 seconds
0.970057 seconds
0.911041 seconds

Все, окрім "багатопотокової", "SIMD" (написаної від руки до компілятора), та оптимізації 4-валентного патча були оптимізаціями пам'яті на рівні мікрорівні. Також оригінальний код, починаючи з початкового часу 32 секунди, вже був досить оптимізований (теоретично оптимальна алгоритмічна складність), і це нещодавній сеанс. Оригінальна версія задовго до цієї останньої сесії потребувала 5 хвилин на обробку.

Оптимізація ефективності пам’яті може допомогти часто в будь-якому місці від декількох разів до порядків величин в однопотоковому контексті та більше в багатопотокових контекстах (переваги ефективної повторної пам’яті часто множиться з кількох потоків у суміші).

Про важливість мікрооптимізації

Мене трохи засмучує думка, що мікрооптимізація - це марна трата часу. Я погоджуюсь, що це гарна загальна порада, але не всі роблять це неправильно на основі переслідувань і забобонів, а не замірів. Зроблено правильно, це не обов'язково дає мікро вплив. Якщо ми візьмемо власну Embree (ядро Raytracing) від Intel і перевіримо лише написаний ними простий скалярний BVH (а не пакет променів, який важко перемогти), а потім спробуємо перемогти продуктивність цієї структури даних, це може бути найбільше досвід буття навіть для ветерана, який десятиліттями звик до профілювання та налаштування коду. І все це завдяки застосованим мікрооптимізаціям. Їх рішення може обробляти понад сто мільйонів променів в секунду, коли я бачив промислових спеціалістів, які працюють в режимі проміння, хто може "

Немає можливості здійснити просту реалізацію BVH лише з алгоритмічним фокусом і отримати понад сто мільйонів первинних перетинів променів в секунду проти будь-якого оптимізуючого компілятора (навіть власного ICC IC). Безпосередньо один не отримує навіть мільйона променів в секунду. Потрібно професійно якісні рішення, щоб часто навіть отримати кілька мільйонів променів в секунду. Потрібна мікрооптимізація на рівні Intel, щоб отримати понад сто мільйонів променів в секунду.

Алгоритми

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

Різниця між місяцями та хвилинами, ймовірно, змусить більшість людей, навіть тих, хто не працює в критичних для продуктивності полів, вважати час виконання неприйнятним, якщо для отримання результату потрібні користувачі, які чекають місяцями.

Тим часом, якщо порівняти немікрооптимізований, прямого сортування злиття з швидким сортуванням (що зовсім не алгоритмічно перевершує сортування злиття, а пропонує лише поліпшення на мікрорівні для місцевості відліку), мікрооптимізований кваксорт може закінчитися 15 секунд на відміну від 12 хвилин. Змусити користувачів чекати 12 хвилин може бути цілком прийнятним (час перерви на каву).

Я думаю, що ця різниця, мабуть, незначна для більшості людей між, скажімо, 12 хвилинами та 15 секундами, і тому мікрооптимізація часто вважається марною, оскільки часто подобається лише різниця між хвилинами та секундами, а не хвилинами та місяцями. Інша причина, яку я вважаю марною - це те, що вона часто застосовується до неважливих областей: невелика площа, яка не є навіть петельною і критичною, що дає певну різницю в 1% (що може бути просто шумом). Але людям, які піклуються про такі типи різниць у часі і готові вимірювати та робити це правильно, я думаю, що варто звернути увагу принаймні на основні поняття ієрархії пам’яті (зокрема верхні рівні, що стосуються помилок сторінки та пропуску кешу) .

Java залишає багато місця для хорошої мікрооптимізації

Фу, вибачте - з таким виглядом гнів:

Чи заважає "магія" JVM впливати програмісту на мікрооптимізацію на Java?

Трохи, але не настільки, як люди можуть подумати, якщо ви зробите це правильно. Наприклад, якщо ви займаєтесь обробкою зображень, в натурному коді з рукописними оптимізаціями SIMD, багатопотоковості та пам’яті (шаблони доступу та, можливо, навіть представлення залежно від алгоритму обробки зображень), легко розчавити сотні мільйонів пікселів в секунду за 32- бітові пікселі RGBA (8-бітові кольорові канали), а іноді навіть мільярди в секунду.

Наблизитись до Яви неможливо, якщо, скажімо, зробив Pixelоб’єкт (один лише надув би розмір пікселя від 4 байтів до 16 на 64-розрядному).

Але ви, можливо, зможете наблизитись набагато більше, якби уникнути Pixelоб'єкта, використали масив байтів та змоделювали Imageоб’єкт. Java все ще досить компетентна, якщо ви починаєте використовувати масиви простих старих даних. Я раніше пробував подібні речі на Java і був дуже вражений, якщо ви не створюєте кучу маленьких підліткових об'єктів скрізь, що в 4 рази більше, ніж зазвичай (наприклад: використовувати intзамість Integer), і починати моделювати об'ємні інтерфейси, як Imageінтерфейс, а не Pixelінтерфейс. Я б навіть зважився сказати, що Java може конкурувати з програмою C ++, якщо ви перебираєте звичайні старі дані, а не об'єкти (величезні масиви float, наприклад, ні Float).

Можливо, навіть важливіше, ніж розміри пам'яті, - це те, що масив intгарантій суцільного подання. Масив Integerне робить. Близькість часто є важливою для місцевості відліку, оскільки це означає, що декілька елементів (наприклад: 16 ints) можуть усі вписатись в одну лінію кешу і, можливо, отримати доступ до них разом перед тим, як виселити ефективні схеми доступу до пам'яті. Між тим, одне Integerможе бути розташоване десь у пам’яті, коли оточуюча пам’ять не має значення, тільки щоб ця область пам’яті була завантажена в кеш-рядок лише для використання одного цілого числа до виселення на відміну від 16 цілих чисел. Навіть якби нам надзвичайно пощастило та оточилиIntegersу пам’яті все було поряд, ми можемо вмістити лише 4 у кеш-рядок, до якого можна отримати доступ до виселення, оскільки це в Integer4 рази більше, і це в найкращому випадку.

І є багато мікрооптимізацій, оскільки ми об’єднані в одній архітектурі / ієрархії пам'яті. Шаблони доступу до пам’яті не залежать від того, якою мовою ви користуєтесь, такі поняття, як нав'язування циклу чи блокування циклу, зазвичай, можна застосовувати набагато частіше на C або C ++, але вони так само корисні Java.

Я нещодавно читав на C ++, іноді впорядкування членів даних може забезпечити оптимізацію [...]

Порядок членів даних, як правило, не має значення в Java, але в основному це добре. У C і C ++ збереження порядку членів даних часто важливо з причин ABI, тому компілятори з цим не псуються. Людські розробники, які працюють там, повинні бути обережними, щоб робити такі дії, як упорядкувати своїх членів даних у порядку зменшення (найбільший до найменшого), щоб уникнути втрати пам'яті на прокладку. З Java, очевидно, JIT може впорядкувати членів для вас на ходу, щоб забезпечити правильне вирівнювання, мінімізуючи підкладку, тому за умови, що це так, він автоматизує щось, що середні програмісти C і C ++ часто можуть погано робити і в кінцевому підсумку витрачають пам'ять таким чином ( що не просто витрачає пам'ять, але часто витрачає швидкість, збільшуючи крок між структурами AoS без потреби і спричиняючи більше пропусків кешу). Це ' дуже робототехнічна річ, щоб переставляти поля, щоб мінімізувати забивання, тому в ідеалі люди не займаються цим. Єдиний час, коли розташування полів може мати значення таким чином, що вимагає від людини знання оптимального розташування, якщо об'єкт більший за 64 байти, і ми організовуємо поля на основі шаблону доступу (а не оптимального набивання) - у такому випадку це може бути більш людським завданням (вимагає розуміння критичних шляхів, частина яких - це інформація, яку компілятор неможливо передбачити, не знаючи, що робитимуть користувачі із цим програмним забезпеченням).

Якщо ні, чи могли б люди навести приклади, які трюки можна використовувати на Java (окрім простих прапорів компілятора).

Найбільша різниця для мене з точки зору оптимізації ментальності між Java та C ++ полягає в тому, що C ++ може дозволити вам використовувати об'єкти трохи (підлітковий) трохи більше, ніж Java у критичному для продуктивного сценарію. Наприклад, C ++ може перенести ціле число до класу без накладних витрат (орієнтир у всьому місці). Java має мати метадані вказівника + вирівнювання накладних накладних витрат на об'єкт, тому Booleanбільше boolean(але в обмін, що забезпечує однакові переваги відображення та можливість змінити будь-яку функцію, не позначену як finalдля кожної окремої УДТ).

У C ++ трохи легше керувати безперервністю макетів пам’яті через неоднорідні поля (наприклад: перемежування плаває і вводиться в один масив через структуру / клас), оскільки просторова локальність часто втрачається (або принаймні втрачається контроль) в Java при розподілі об'єктів через GC.

... але найчастіше рішення з найвищою ефективністю часто все-таки розділять їх і використовуватимуть шаблон доступу SoA над суміжними масивами простих старих даних. Тож для областей, які потребують пікової продуктивності, стратегії оптимізації компонування пам’яті між Java та C ++ часто однакові, і часто вам доведеться знести ці маленькі об’єктно-орієнтовані інтерфейси на користь інтерфейсів стилю колекції, які можуть робити такі речі, як гаряче / розщеплення холодного поля, повторення SoA і т. д. Неоднорідні повтори AoSoA здаються неможливими на Java (якщо ви просто не використовували необроблений масив байтів чи щось подібне), але це для рідкісних випадків, коли обидвашаблони послідовного та випадкового доступу повинні бути швидкими, одночасно маючи суміш типів полів для гарячих полів. Для мене основна частина різниці в стратегії оптимізації (на загальному рівні) між цими двома є суперечливою, якщо ви досягаєте пікових показників.

Відмінності трохи розрізняються більше , якщо ви просто дістаючи «хорошу» продуктивність - не в змозі зробити так само з невеликими об'єктами , як IntegerVS. intможе бути трохи більше PITA, особливо з тим , як він взаємодіє з узагальненнями . Трохи складніше просто створити одну загальну структуру даних як центральну ціль оптимізації в Java, яка працює для int, floatтощо., Уникаючи тих великих і дорогих UDT, але часто найважливіші області роботи вимагають ручної прокатки власних структур даних. налаштований на дуже конкретну мету, так що це тільки дратує код, який прагне до хорошої продуктивності, але не пікової продуктивності.

Об'єкт накладні

Зауважте, що накладні об'єкти Java (метадані та втрата просторової локальності та тимчасова втрата тимчасової локальності після початкового циклу GC) часто є великими для речей, які насправді є невеликими (наприклад, intпорівняно з Integer), які зберігаються мільйонами в деякій структурі даних, значною мірою суміжні та мають доступ у дуже тугих петлях. Здається, що з цього приводу є багато чутливості, тому я повинен уточнити, що ви не хочете турбуватися про накладні об'єкти для великих об’єктів, таких як зображення, просто насправді мізерні об'єкти, такі як один піксель.

Якщо хтось відчуває сумніви щодо цієї частини, я б запропонував зробити орієнтир між підсумовуванням мільйона випадкових intsпорівняно з мільйоном випадкових випадків Integersі робити це повторно ( Integersвольові зміни перейдуть в пам'ять після початкового циклу GC).

Ultimate Trick: Дизайн інтерфейсу, який залишає місце для оптимізації

Отже, найвищий фокус Java, як я бачу, якщо ви маєте справу з місцем, яке обробляє велике навантаження над дрібними предметами (наприклад: a Pixel, 4-вектор, матриця 4x4, a Particle, можливо, навіть Accountякщо у ньому є лише кілька малих полів) - це уникати використання об'єктів для цих маленьких речей та використання масивів (можливо, пов'язаних між собою) простих старих даних. Об'єкти стають інтерфейсами збору , як Image, ParticleSystem, Accounts, колекція матриць або векторів і т.д. Окремих з них можна отримати за індексом, наприклад , це також один з кінцевих трюків дизайну в C і C ++, оскільки навіть без цього основних накладних об'єкта і роз'єднана пам'ять, моделювання інтерфейсу на рівні однієї частинки перешкоджає найбільш ефективним рішенням.


1
Враховуючи, що погані показники в основному можуть мати пристойні шанси перемогти пікові показники в найважливіших областях, я не думаю, що можна повністю знехтувати перевагою легкої хорошої роботи. І хитрість перетворення масиву структур у структуру масивів дещо руйнується, коли всі (або майже всі) значення, що містять одну з оригінальних структур, будуть одночасно отримані. BTW: Я бачу, що ви знайдете багато старих дописів і додаєте власну добру відповідь, іноді навіть гарну відповідь ;-)
Дедуплікатор

1
@Deduplicator Сподіваюся, що я не дратую людей, надто сильно натикаючись! Цей отримав трохи підлітковий рівень, - можливо, я мушу трохи його покращити. SoA проти AoS часто є важким для мене (послідовний проти випадкового доступу). Я рідко знаю заздалегідь, який саме я повинен використовувати, оскільки в моєму випадку часто є суміш послідовного та випадкового доступу. Цінний урок, який я часто засвоював, - це розробити інтерфейси, які залишають достатньо місця для гри з представленням даних - свого роду більш об'ємними інтерфейсами, які мають великі алгоритми перетворення, коли це можливо (іноді це неможливо з підлітковими бітами, доступними випадковим чином тут і там).

1
Ну, я помітив лише тому, що справи справді повільні. І я брав свій час з кожним.
Дедупликатор

Мені дуже цікаво, чому user204677пішов геть. Така чудова відповідь.
олігофрен

3

Існує середня область між мікрооптимізацією, з одного боку, і хорошим вибором алгоритму, з іншого.

Це область прискорень з постійним коефіцієнтом, і вона може давати порядки величини.
Це робиться так, що виключаєте цілі дроби часу виконання, як-от спочатку 30%, потім 20% того, що залишилося, потім 50%, і так далі протягом декількох ітерацій, поки майже нічого не залишиться.

Цього ви не бачите в невеликих демо-програмах. Там, де ви бачите, це у великих серйозних програмах з великою кількістю структур даних класу, де стек викликів, як правило, глибокий на багато шарів. Хороший спосіб знайти можливості прискорення - це дослідити вибірки випадкових станів стану програми.

Зазвичай скорочення складаються з таких речей, як:

  • мінімізація дзвінків newшляхом об’єднання та повторного використання старих об’єктів,

  • визнаючи, що робиться щось подібне до загальності, а не до необхідності,

  • перегляд структури даних за допомогою різних класів збору, які мають однакове поведінка великих виходів, але користуються перевагою фактично використовуваних шаблонів доступу,

  • збереження даних, отриманих за допомогою викликів функцій замість повторного виклику функції (Це природна і забавна тенденція програмістів вважати, що функції, що мають короткі назви, виконуються швидше.)

  • допущення певної суперечливості між зайвими структурами даних, на відміну від спроб їх повністю відповідати подіям сповіщень,

  • тощо.

Але, звичайно, нічого з цього не слід робити без попереднього виявлення проблем із взяттям зразків.


2

Java (наскільки я знаю) не дає вам контролю над змінними місцями в пам'яті, тому вам важче уникнути таких речей, як помилковий обмін та вирівнювання змінних (ви можете викласти з класу декілька невикористаних членів). Інша річ, я не думаю, що ви можете скористатися такими інструкціями mmpause, але такі речі є специфічними для процесора, тому, якщо ви вважаєте, що вам це потрібно, Java може не використовувати мову.

Існує клас Unsafe, який дає вам гнучкість C / C ++, але також і небезпека C / C ++.

Це може допомогти вам переглянути код складання, який створює JVM для вашого коду

Щоб ознайомитись з додатком Java, який розглядає подібні деталі, див . Код Disruptor, випущений LMAX


2

На це питання дуже важко відповісти, оскільки це залежить від мовної реалізації.

Загалом, місця для таких "мікрооптимізацій" в наші дні дуже мало. Основна причина полягає в тому, що компілятори користуються такими оптимізаціями під час компіляції. Наприклад, немає різниці в продуктивності між до-інкрементними та після-прирістними операторами в ситуаціях, коли їх семантика однакова. Іншим прикладом може бути, наприклад, цикл на зразок цього, for(int i=0; i<vec.size(); i++)де можна стверджувати, що замість викликуsize()Функція члена під час кожної ітерації було б краще отримати розмір вектора перед циклом, а потім порівнювати з цією єдиною змінною і таким чином уникати функції виклику за ітерацію. Однак є випадки, коли компілятор виявить цей нерозумний випадок і кешуватиме результат. Однак це можливо лише тоді, коли функція не має побічних ефектів, і компілятор може бути впевнений, що розмір вектора залишається постійним протягом циклу, тому він застосовується лише до досить тривіальних випадків.


Що стосується другого випадку, я не думаю, що компілятор може його оптимізувати в осяжному майбутньому. Виявлення того, що безпечно оптимізувати vec.size (), залежить від доведення того, що розмір, якщо вектор / загублений не змінюється всередині циклу, що, на мою думку, не можна визначити через проблему зупинки.
Лягати Райан

@LieRyan Я бачив безліч (простих) випадків, коли компілятор генерував абсолютно однаковий бінарний файл, якщо результат був "кешований" вручну і якщо розмір () викликався. Я написав деякий код, і, виявляється, поведінка сильно залежить від того, як працює програма. Є випадки, коли компілятор може гарантувати, що немає можливості зміни розміру вектора під час циклу, і тоді є випадки, коли він не може цього гарантувати, дуже схожий на проблему зупинки, як ви згадали. Наразі я не можу перевірити свою претензію (розбирання на C ++ - це біль), тому я відредагував відповідь
zxcdw

2
@Lie Ryan: багато речей, які не можна визначити в загальному випадку, цілком можна вирішити для конкретних, але звичайних випадків, і це дійсно все, що вам тут потрібно.
Майкл Боргвардт

@LieRyan Якщо ви запускаєте лише constметоди цього вектора, я впевнений, що багато оптимізуючих компіляторів розберуться з цим.
К.Штефф

в C #, і я думаю, що я читаю також на Java, якщо ви не кешуєте розмір, компілятор знає, що він може зняти чеки, щоб побачити, чи виходите за межі масиву, і якщо ви розміру кешу, він повинен робити перевірки , яка, як правило, коштує дорожче, ніж ви економите за допомогою кешування. Спроба перехитрити оптимізатори рідко є вдалим планом.
Кейт Григорій

1

Чи могли б люди навести приклади, які трюки можна використовувати на Java (крім простих прапорів компілятора).

Окрім удосконалення алгоритмів, обов'язково враховуйте ієрархію пам’яті та те, як процесор використовує її. Існує велика перевага у скороченні затримок доступу до пам’яті, як тільки ви зрозумієте, як мова, про яку йде мова, розподіляє пам’ять для її типів даних та об’єктів.

Приклад Java для доступу до масиву 1000x1000 ints

Розглянемо нижченаведений зразок коду - він отримує доступ до тієї ж області пам'яті (масив 1000x1000 ints), але в іншому порядку. На моєму mac mini (Core i7, 2,7 ГГц) висновок виглядає наступним чином, показуючи, що обхід масиву рядками більш ніж удвічі збільшує продуктивність (в середньому більше 100 раундів у кожному).

Processing columns by rows*** took 4 ms (avg)
Processing rows by columns*** took 10 ms (avg) 

Це відбувається тому, що масив зберігається таким чином, що послідовні стовпці (тобто значення int) розміщуються поруч із пам'яттю, тоді як послідовні рядки не є. Щоб процесор фактично використовував дані, його потрібно перенести в свої кеші. Передача пам’яті здійснюється блоком байтів, що називається кеш-рядком - завантаження кеш-рядки безпосередньо з пам'яті вводить затримки і, таким чином, знижує продуктивність програми.

Для Core i7 (піщаний міст) лінія кешу вміщує 64 байти, таким чином кожен доступ до пам'яті отримує 64 байти. Оскільки перший тест отримує доступ до пам'яті у передбачуваній послідовності, процесор попередньо отримає дані до того, як програма фактично споживає їх. В цілому це призводить до меншої затримки доступу до пам'яті і тим самим покращує продуктивність.

Код зразка:

  package test;

  import java.lang.*;

  public class PerfTest {
    public static void main(String[] args) {
      int[][] numbers = new int[1000][1000];
      long startTime;
      long stopTime;
      long elapsedAvg;
      int tries;
      int maxTries = 100;

      // process columns by rows 
      System.out.print("Processing columns by rows");
      for(tries = 0, elapsedAvg = 0; tries < maxTries; tries++) {
       startTime = System.currentTimeMillis();
       for(int r = 0; r < 1000; r++) {
         for(int c = 0; c < 1000; c++) {
           int v = numbers[r][c]; 
         }
       }
       stopTime = System.currentTimeMillis();
       elapsedAvg += ((stopTime - startTime) - elapsedAvg) / (tries + 1);
      }

      System.out.format("*** took %d ms (avg)\n", elapsedAvg);     

      // process rows by columns
      System.out.print("Processing rows by columns");
      for(tries = 0, elapsedAvg = 0; tries < maxTries; tries++) {
       startTime = System.currentTimeMillis();
       for(int c = 0; c < 1000; c++) {
         for(int r = 0; r < 1000; r++) {
           int v = numbers[r][c]; 
         }
       }
       stopTime = System.currentTimeMillis();
       elapsedAvg += ((stopTime - startTime) - elapsedAvg) / (tries + 1);
      }

      System.out.format("*** took %d ms (avg)\n", elapsedAvg);     
    }
  }

1

JVM може і часто втручається, і компілятор JIT може суттєво змінюватися між версіями. Деякі мікрооптимізації в Java неможливі через мовні обмеження, наприклад, дружнє до гіперточок або остання колекція SIMD останніх процесорів Intel.

Блог із великою інформацією по темі від авторів Disruptor рекомендується прочитати:

Завжди потрібно запитати, навіщо турбуватися з використанням Java, якщо ви хочете мікрооптимізації, існує безліч альтернативних методів прискорення функції, таких як використання JNA або JNI для переходу до рідної бібліотеки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.