Оптимізація продуктивності Java додатків

  1. 1. Уникайте синхронізації
  2. 2. Використовуйте попередні обчислення
  3. 3. Витягування масивів
  4. 4. Розгортання циклів for
  5. 5. Стиснення циклів for
  6. 6. Уникайте інтерфейсних викликів методів
  7. 7. Уникайте непотрібних звернень до масиву
  8. 8. Уникайте використання аргументів
  9. 9. Відмовтеся від використання локальних змінних
  10. 10. Не використовуйте getter-и / setter-и
  11. 11. Ефективна математика
  12. 12. Пишіть коротко
  13. 13. Використовуйте вбудовані методи
  14. 14. Використовуйте StringBuffer замість String
  15. висновок

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

1. Уникайте синхронізації

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

2. Використовуйте попередні обчислення

Якщо Ви розробляєте гру з 3D або 2.5D графікою, то напевно використовуєте масу математичних обчислень з тригонометричними функціями. Такі розрахунки сильно навантажують процесор, тому варто заздалегідь прорахувати найбільш складні вирази і представити їх у вигляді масиву, звідки діставати готові значення в процесі виконання програми. Крім графіки існує маса програм, де попередні обчислення можна зробити заздалегідь і оформити у вигляді масивів даних.

3. Витягування масивів

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

приклад

// До оптимізації int [] [] table; // Таблиця 4x4 // Після оптимізації int [] table; // Таблиця 1x16


А ще одномірні масиви споживають менше динамічної пам'яті, ніж їх багатовимірні зібратися.

4. Розгортання циклів for

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

void printMsg () {for (int loop = 0; loop <15; loop ++) {System. out .println (msg); }}

Виконується віртуальною машиною наступним чином:

void printMsg () {int loop = 0; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; System. out .println (msg); if (loop> = 15) {return; } Loop ++; }

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

void printMsg () {for (int loop = 0; loop <15; loop + = 5) {System. out .println (msg); System. out .println (msg); System. out .println (msg); System. out .println (msg); System. out .println (msg); }}

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

5. Стиснення циклів for

Сенс операції стиснення полягає у винесенні за межі циклу всього того, що не потребує повторного обчисленні:

// Погано оптимізований код for (int loop = 0; loop <10; loop ++) {int a = 5; int b = 10; int c = a * b + loop; } // А ось тут - все в порядку int a = 5; int b = 10; for (int loop = 0; loop <10; loop ++) {int c = a * b + loop; }

У другому прикладі змінним a і b значення присвоюється лише один раз. Таким чином, у порівнянні з першим варіантом нам вдалося позбутися від 20 зайвих операцій присвоювання значень.
Наведу ще одну менше очевидну техніку стиснення циклів. Ніколи не викликайте методи обчислення розмірів в заголовку циклу. Порівняйте:

// Поганий код for (int loop = 0; loop <msgs .size (); loop ++) {System. out .println (msgs .get (loop)); } // Хороший код int msgCount = msgs .size (); for (int loop = 0; loop <msgCount; loop ++) {System. out .println (msgs .get (loop)); }

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

6. Уникайте інтерфейсних викликів методів

У байткод Java існує 4 типи методів. Нижче вони перераховані в порядку зменшення швидкості виклику.
invokestatic
Статичні методи не використовують екземпляр класу, тому їм не потрібно дотримуватися правил поліморфізму і шукати відповідний параметрам виклику екземпляр.
invokevirtual
Звичайні методи.
invokespecial
Спеціальні методи - конструктори, private і super class методи.
invokeinterface
Вимагають пошук підходящої реалізації интерфейсного методу.
Тип використовуваних методів впливає на весь дизайн програми, тому пам'ятайте про швидкість викликів методів в процесі розробки.
Виклик статичних методів забезпечує найкращу продуктивність, оскільки Java VM не потрібно нічого шукати. Виклик інтерфейсу навпаки - найповільніший шлях, що вимагає два пошуку.

7. Уникайте непотрібних звернень до масиву

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

8. Уникайте використання аргументів

При виклику нестатичних методів ви неявно передаєте посилання this разом з іншими параметрами. В Java VM виклик методів реалізований за принципом стека. При кожному виклику аргументи заносяться в стек, а потім витягуються з нього при виконанні методу. У деяких випадках можна уникнути необхідності використання стека, відмовившись від передачі аргументів.

// Погано return multiply (int a, int b) {return a * b; } // Добре int result; int a; int b; void multiply () {result = a * b; }

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

9. Відмовтеся від використання локальних змінних

Локальні змінні поміщаються і витягуються з стека при виклику кожного методу. З точки зору продуктивності набагато ефективніше використовувати звичайні змінні.

10. Не використовуйте getter-и / setter-и

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

11. Ефективна математика

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

// Погано int a = 5 * 2; // Уже краще int a = 5 + 5; // Добре: побітовий зрушення еквівалентний множенню і діленню на 2 int a = 5 << 1; // Зрушуємо в 5 (0000 0101) 1-біт вліво, отримуємо 10 (0000 1010). // Ще краще int a = 10; // Подумайте, чи дійсно ви хочете // щось вважати під час виконання програми?

Цими методами оптимізації користувалися ще предки, коли писали перші ігри під DOS. Для Java вони теж підходять.

12. Пишіть коротко

Застосовуйте оператори типу + =, оскільки вони генерують короткий байткод.

\\ Погано x = x + 1; \\ Добре x + = 1;

13. Використовуйте вбудовані методи

Користуйтеся методами, наданими платформою. Наприклад, використання System.arraycopy для копіювання елементів масиву буде більш ефективним, ніж аналогічна власна реалізація.

14. Використовуйте StringBuffer замість String

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

висновок

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


джерела: j2medevcorne
javajazzup.com
Переклад: Олександр льодком


Ще краще int a = 10; // Подумайте, чи дійсно ви хочете // щось вважати під час виконання програми?