2. www.luxoft.com
Пара слов обо мне
- работаю в «Люксофте» с 2013 г.
- пишу на Java
- люблю разбираться с проблемами производительности
Связаться со мной можно
- написав на корпоративную почту stsypanov@luxoft.com
- написав на личную почту sergei.tsypanov@yandex.ru
- написав ВК https://vk.com/sergei.tsypanov
- написав ФБ https://www.facebook.com/sergei.tsypanov
3. www.luxoft.com
Вычислительная сложность
Каноническое определение звучит так:
Вычислительная сложность — понятие в информатике и теории алгоритмов, обозначающее функцию зависимости
объёма работы, которая выполняется некоторым алгоритмом, от размера входных данных.
https://ru.wikipedia.org/wiki/Вычислительная_сложность
5. www.luxoft.com
А так?
Для пропуска блока if необходимо и достаточно, чтобы только один из операндов был false.
Обратите внимание, что метод checkFlags выполняется значительно быстрее, чем определение значения
переменной hasGoodRating.
6. www.luxoft.com
Меняем порядок операндов
… а обращение к БД обернём в предикат
(можно и в метод, но предикат – это функционально, стильно, модно, молодёжно)
Это позволяет отложить запрос до того времени, когда он станет действительно необходимым.
13. www.luxoft.com
Не всё так просто
Метод getAnotherValue
вызывается всегда
Метод getAnotherValue
вызывается только при наличии
пустого Optional-а
14. www.luxoft.com
1 ПРАВИЛО КЛУБА:
ЕСЛИ МЕДЛЕННОЕ ДЕЙСТВИЕ
МОЖНО ОТЛОЖИТЬ – ОТЛОЖИ ЕГО.
ВОЗМОЖНО, ДЕЛАТЬ ЕГО ВОВСЕ НЕ
ПРИДЁТСЯ.
Действия лучше выполнять в
порядке возрастания их сложности.
16. www.luxoft.com
Присмотрись внимательнее
Количество обращений к репозиторию
равно количеству DTO в списке
Чтобы отбросить повторяющиеся
сущности, используется Set
Множественные запросы к БД?
HashSet там где достаточно ArrayList-а?
Не отчаивайся, $USER, выход есть
21. www.luxoft.com
Почему так? Или пятиминутка скучной теории
Что происходит под капотом?
1 Получаем сессию
2 Открываем транзакцию
3 Работаем
4 Завершаем транзакцию
5 Закрываем сессию
Это шаблон session-per-request (https://developer.jboss.org/wiki/Sessionsandtransactions)
22. www.luxoft.com
Почувствуйте разницу…
Указанные действия выполняются при каждом
вызове repository.findOne(ID), т. е.
вычислительная сложность равна О(n)
При вызове repository.findAll(Iterable<ID>)
указанные действия выполняются всегда один раз, т. е.
вычислительная сложность равна О(1)
23. www.luxoft.com
2 ПРАВИЛО КЛУБА:
ГРАБЬ, ВОРУЙ, …
СОБИРАЙ И ВЛАСТВУЙ.
Однотипные действия, которые
выполняются в цикле, часто можно
выполнить за один проход, особенно
когда речь идёт о запросах в БД.
26. www.luxoft.com
А что это у нас в третьей строке?
У свеженайденной сущности мы берём сущность-дочку, а потом – сущность-внучку.
…дочка за внучку, внучка за Жучку, и вытащили они – нет, не репку, – а полновесный
OutOfMemoryError…
27. www.luxoft.com
Шутка,
OutOfMemoryError на таком примере не выловить, однако это вполне жизненный пример анти-паттерна под
замысловатым названием Circuitous Treasure Hunt.
Его определение:
Occurs when an object must look in several places to find the information that it needs. If a large amount of
processing is required for each look (i.e. a database access), performance will suffer.
Способ борьбы прост: не размазывайте логику.
32. www.luxoft.com
Обратите внимание
Ускорение на 90 мс достигается в худшем случае, т.е. при проверке обоих условий:
В лучшем случае первое условие скажет решительное «нет» уронив общее время выполнения почти до нуля, т. е.
выигрыш составит около 200 мс.
33. www.luxoft.com
И наша арифметика засияет новыми красками
1 проход = 200 мс ≈ 1 фигня
10 проходов = 2000 мс ≈ 2 с
100 проходов = 20000 мс = 20 с
1000 проходов = 200000 мс = 200 с = 3 минуты 20 секунд
38. www.luxoft.com
Что скажете?
отсеиваем лишнее
и ещё раз отсеиваем лишнее
А теперь, внимание, вопрос:
Что мешает выбрать нужные данные сразу?
Можете ли вы с первого взгляда понять, что делают последние 3 строки?
40. www.luxoft.com
4 ПРАВИЛО КЛУБА:
НЕ ДЕЛАЙТЕ ТОГО, ЧТО МОЖЕТ ЗА
ВАС СДЕЛАТЬ БД.
Если задача состоит в поиске
данных и отсеивании лишнего –
делайте это с помощью базы. Она
специально заточена под это.
42. www.luxoft.com
Внезапно
Транзакция открывается как для «хороших», так и для «плохих» данных.
Если данные «плохие», то транзакция тут же откатывается.
А зачем нам в таком случае транзакция?
Achtung !!!
43. www.luxoft.com
Что можно с этим сделать?
Испечём «Наполеон»: всю транзакционную логику перенесём во внутренний сервис, а всю логику проверки оставим во
внешнем сервисе. Таким образом, транзакция становится ленивой:
Транзакция открывается
только тогда, когда данные
проверены
Внешний сервис более не
является транзакционным
45. www.luxoft.com
«Наполеон», он же шаблон Service Layers
https://en.wikipedia.org/wiki/Service_layers_pattern
• проще писать
• легче читать
• проще тестировать
• легче поддерживать
• проще рефакторить
И это просто вкусно
46. www.luxoft.com
5 ПРАВИЛО КЛУБА:
РАЗДЕЛЯЙ И ВЛАСТВУЙ.
УВЕЛИЧИВ КОЛИЧЕСТВО СЛОЁВ,
ПРИЛОЖЕНИЕ ЛЕГЧЕ ТЕСТИРОВАТЬ
И ОНО (В НЕКОТОРЫХ СЛУЧАЯХ)
СТАНОВИТСЯ БОЛЕЕ
ОТЗЫВЧИВЫМ.
47. www.luxoft.com
Лирическо-логическое отступление
<режим-капитана-очевидности>
В JVM существует сборщик мусора (GC), который включается, когда приложению не хватает памяти.
Иногда работа GC приводит к stop-the-world задержкам в работе приложения.
GC освобождает память и не даёт приложению голодать, но всё имеет свою цену.
В обмен на помощь в освобождении памяти GC берёт дань процессорным временем.
Таким образом, если доступная вычислительная мощность равна 1, а GC отнимает у нас х, то приложению
остаётся лишь 1 - х вычислительной мощности.
Отсюда вывод: производительность можно повысить, если уменьшить дань, забираемую GC.
</режим-капитана-очевидности>
48. www.luxoft.com
Немного отвлечёмся от вычислительной сложности
Небольшое пояснение: это общий предок для около 50 сущностей одного из приложений.
Метод includeInAudit() вызывается при изменении значений некоторых полей, и вызывается он очень часто
49. www.luxoft.com
Вернёмся к нашим баранам
На основе реальных событий: в результате 1 действия 1 пользователя данный метод вызывается 3,5 тыс. раз
И при каждом вызове метода
создаётся HashSet…
50. www.luxoft.com
Пятиминутка арифметики:
Пустой HashSet в 64-разрядной JVM «весит» 240* 80** байт.
Путём сложнейших вычислений определим, что 3,5 тыс. пустых (берём по нижней границе) HashSet-ов «весят» 820
273 кБ.
И это 1 действие 1 пользователя…
* Накладные расходы памяти у коллекций https://habrahabr.ru/post/159557/ (внимание, Java 6)
** Oracle HotSpot JVM x64 (Java 8)
51. www.luxoft.com
Присмотримся внимательнее
…и обнаружим, что по завершении цикла переменная drafts может иметь только 4 состояния:
• пустая коллекция
• коллекция, содержащая OLD_DRAFT
• коллекция, содержащая NEW_DRAFT
• коллекция, содержащая обе записи
53. www.luxoft.com
Ещё пример
Так делать не стоит:
Бессмысленно создавать новую коллекцию при каждом вызове метода, тем более что создаётся она
только для чтения. Ничто не мешает сделать коллекцию постоянной.
55. www.luxoft.com
Не сразу бросается в глаза
Как видим, формат даты не зависит от аргументов метода, поэтому его повторное создание при каждом
вызове метода бессмысленно.
Обратите внимание, что java.text.SimpleDateFormat не является потокобезопасным, поэтому его
необходимо оборачивать в ThreadLocal.
60. www.luxoft.com
Сравнение поиска в списке и наборе
0.008 0.062 0.527 5.896 56.757
2106.069
15126.756
0.007 0.006 0.006 0.007 0.006 0.007 0.007
0
2,000
4,000
6,000
8,000
10,000
12,000
14,000
16,000
10 100 1000 10000 100000 1000000 10000000
ArrayList.contains
HashSet.contains
мкс
Количество элементов
61. www.luxoft.com
Закономерно возникает вопрос
Если поиск в наборе настолько эффективнее поиска по
списку, то стоит ли оборачивать в Set полученный в
качестве аргумента список/массив?
Есть только один способ выяснить это – проверить.
62. www.luxoft.com
Вывод: игра не стоит свеч
0.008 0.062 0.527 5.896 56.757 2106.069 15126.756
0.161 1.662 18.652 221.583 4843.397
95631.165
1621857.897
0
200,000
400,000
600,000
800,000
1,000,000
1,200,000
1,400,000
1,600,000
1,800,000
10 100 1000 10000 100000 1000000 10000000
contains in
ArrayList
contains in
copied
HashSet
мкс
Количество элементов