http-серверов, http-лиснеров в разных рутинах. В прошлом видео мы сделали две реализации распаковки бинарных данных через рефлексию и через кодогенерацию, но мы так и не выяснили, какой из этих вариантов быстрее. В этом видео я расскажу, каким образом можно делать бенчмарки кода и смотреть, какой из вариантов быстрее. Итак, бенчмарки в Go лежат рядом с тестами, то есть в пакете с суффиксом test, вы можете видеть test. Начинаются бенчмарки, функция бенчмарка начинается с суффикса Benchmark, куда передается testing B, в котором вы должны сделать цикл, что пока меньше количества итераций, которые хочет бенчмарк. После этого он выведет вам красивую картинку с цифрами. Давайте запустим тот код для кодогенерации, который использует функцию unpack и для рефлексии, который использует функцию UnpackReflect. Запускаем, запускаем так: go test -bench. Bench — это значит нужно делать бенчмарк. Точка... там можно написать регулярное выражение, чтобы пробенчмаркить не вообще все функции, которые есть у файла, а только некоторые из них, например, одну. Дальше файл, который мы хотим запустить. Запускаем. Итак, вариант с кодогенерацией, одна операция заняла 1000 наносекунд и успело выполниться 1 миллион итераций. Вариант с рефлектом: одна операция занимает 2500 наносекунд и успела выполниться 500 000 итераций. То есть, грубо можно говорить, что вариант с кодогенерацией вдвое быстрее, чем вариант с рефлексией. Но это не все. Помимо того что мы можем посмотреть просто скорость выполнения итераций, мы можем попросить ещё делать замеры использования памяти. Делается это при помощи ключа benchmem. [БЕЗ СЛОВ] Итак, вариант с кодогенерацией тратит 176 байт на одну операцию, делает 11 аллокаций памяти за операцию. Вариант с рефлексией делает 432 байта на операцию, памяти тратит, и делает аж 22 аллокации. Понятно, что вариант с кодогенерацией получается двое лучше и по скорости и по использованию памяти. Окей, это круто. Если кодогенерация такая крутая вещь, что сразу напрашивается вопрос, а давайте сделаем кодогенерацию для json. Ну, это приложение очень хорошее, поэтому в mail.ru мы сделали такую кодогенерацию, назвали пакет easyjson, вы можете скачать его на GitHub, и получили впечатляющий рост производительности. Давайте я его запущу. [БЕЗ СЛОВ] Окей. Раз, два, три, четыре. Отлично, сделали бенчмарки. Итого: декодирование стандартным способом у нас занимает 4000 наносекунды, делает 320 байт на операцию, тратит памяти, делает 6 аллокаций на операцию. Декодирование при помощи easyjson тратит 834 наносекунды на операцию, делает всего 16 байт на операцию, тратит памяти, и делает 2 аллокации. Кодирование, то есть стерилизация, маршелинг json стандартными способами тратит 1800 наносекунд на операцию, занимает 328 байт памяти на операцию, делает 3 аллокации памяти. При этом encoding, используя easyjson, тратит всего 544 наносекунды, тратит в 3 раза меньше памяти и в 3 раза меньше аллокаций памяти. То есть, если смотреть чисто по скорости, то декодирование в 4 раза быстрее, и стерилизация, то есть encoding, тоже почти в 4 раза быстрее. Поэтому, если вам нужен очень быстрый упаковщик распаковщик json и это у вас узкое место, то вы знаете, где его искать. Хорошо, давайте посмотрим теперь ещё какие-нибудь примеры. Есть пример для поиска вхождения какого-то слова в строке. У нас есть браузер, какой-то браузер, и мы делаем 3 бенчмарка. Первый бенчмарк — мы каждый раз компилируем регулярку при каждом вызове, то есть на каждую операцию создается новая регулярка. Второй вариант — мы прикомпилировали регулярку вот здесь, и на каждой итерации мы просто вызываем функцию матчинга на прикомпилированной регулярке. И в третьем варианте мы просто вызываем функцию contains из пакета strings. Давайте посмотрим, кто быстрее. [БЕЗ СЛОВ] Итого, судя по скорости и количеству итераций, быстрее всего просто contains из пакета strings, потом идет прикомпилированная регулярка, она успела сделать аж 10 миллионов операций, и совсем позади них идет неприкомпилированная регулярка, которую мы каждый раз компилируем. Посмотрите, она значительно отстает от всех других показателей. Поэтому, если вдруг вы делаете компиляцию регулярки каждый раз, остановитесь, это не очень хорошо. Давайте рассмотрим еще один вариант, еще один пример. У нас есть просто добавление каких-то данных в слайс. В первом варианте мы создаем пустой слайс, вообще пустой, и начинаем добавлять туда данные. Во втором варианте мы создаем также слайс пустой, однако мы сразу создаем capacity у того слайса. В чём разница? В первом случае мы создаем сначала пустой слайс, и с каждой итерацией мы добавляем туда значение. Чтобы вместить все значения, ему нужно увеличивать слайс, то есть создавать новый слайс, копировать туда данные, тем самым его расширяя. Получается 1, 2, 3, 4 — мы расширяем слайс несколько раз. При этом в преаллоцированном варианте мы сразу указываем, что создай нам, пожалуйста, большой слайс, большой capacity. Давайте посмотрим, что быстрее и почему. О! Смотрите, 327 операций, 327 наносекунд на одну операцию при пустом слайсе, 248 байт на операцию и целых 5 аллокаций. При этом вариант с преаллоцированным слайсом успел выполнить аж 100 миллионов операций, потому что он тратит всего 19 наносекунд на операцию и делает 0 аллокаций. Поэтому преаллоцировать сразу нужный объем в слайсе, это очень хорошо. В этом примере мы познакомились с тем, как вообще сравнивать бенчмарки, как смотреть, какой из вариантов лучше. Однако из него непонятно, что именно тормозит, что именно занимает время, что именно тратит память, что именно делает аллокация памяти. В Go есть средства для того, чтобы посмотреть это, для того, чтобы залезть внутрь и посмотреть какую-то более детальную информацию. И это как раз тема следующего видео.