0:00
[БЕЗ СЛОВ] В
предыдущем видео мы рассмотрели каким образом мы можем сравнить два подхода,
каких-то два кода, какой из них быстрее, и мы узнали,
что подход с кодогенерацией будет быстрее, подхода с рефлексией.
Мы пользовались флагом benchmem у бенчмарка,
и в этом видео мы поговорим о том,
как посмотреть не просто какой вариант быстрее,
а за счет чего какой-то вариант быстрее или медленнее.
В Go вы можете снять профиль либо процессора,
используя директиву cpuprofile и путь
куда складировать данные,
либо профилирование памяти.
Причем, я указал rate 1,
то есть фактически регистрировать в каждую локацию.
Вы можете использовать либо и CPU, и память вместе, либо по отдельности.
Я использую вместе для того, чтобы он мне их построил.
Сейчас я это запущу.
Итак, я запустил.
Видно, как по-прежнему, что кодогенерация будет быстрее практически в два раза.
Теперь я хочу посмотреть, почему.
Когда я снял профиль, теперь мне нужно его каким-то образом проанализировать.
В Go для этого есть инструмент, называется pprof.
Запускается он таким образом: go tool pprof,
ваш выполняемый файл.
Если вы собираете его через test,
то вот он таким образом собрался: test.exe и путь к профилировщику.
Начнем мы с CPU.
[БЕЗ СЛОВ] Запустился pprof.
Сначала давайте посмотрим на топ операции, которые выполнялись.
Ну, тут только runtime, то есть чисто сам runtime Go.
Тут нет наших операций, они не были достаточно быстрые.
Но я могу ввести нужную мне
функцию и посмотреть, что именно там, где занимало время.
Я знаю мою функцию, она называется Unpack, точнее их там две.
Я использую команду list и ввожу функцию Unpack, и теперь мне
вывелись на экран те места,
где было сколько-либо много
задействовано CPU времени.
Посмотрим сначала UnpackBin, это кодогенерация.
Вот где, что тут тратило время.
В основном, конечно, это чтение бинарных данных затрачивается,
ну, и преобразование слайса byte в строке.
А если мы посмотрим рефлексию,
то мы не только тратим время на чтение бинарных данных,
но еще и на их установку вот здесь, и вот тут тоже.
Но по коду иногда бывает ковыряться не очень много, хочется как-то и с высоты
птичьего полета обозревать свою программу, свои владения и смотреть, что там как.
Для этого pprof может строить уже картинку с
графиком в любом нужном вам формате, либо вы можете запустить ее прямо из pprof.
Делается это: команда web, enter.
У меня открылся браузер, и теперь видны фактически все кишки.
У меня не очень много горячих мест.
На самом деле все горячие места занимают runtime и операции, там, с памятью.
Но, тем не менее, тут можно найти мои программы.
Вот отлично!
Смотрите, вот test, вот BenchmarkCodegen.
Так, 0.17, а вот BenchmarkReflect, 0.25,
и дальше мы уже можем смотреть, где тут что выделяется.
Например, UnpackReflect,
они все выделяли какие-то новые объекты, все читали,
но вот Reflect куда-то ушел, ушел, ушел, ушел...
куда-то ушел в runtime.
Таким образом, вы можете найти нужное
узкое место и попробовать его оптимизировать.
Но это профиль CPU, давайте теперь посмотрим на профиль памяти.
Указываем mem.out.
В профиле памяти несколько разных режимов.
Тут можно посмотреть место, которое в данный момент занимает либо по количеству
объектов, либо по самой памяти либо места, где были совершены больше всего локаций.
Вот например, если я введу сразу, то в данный момент в основном занимает runtime,
и, более того, я больше всего вижу снятие самого хипа тут,
но если я посмотрю на аллокации, alloc...
это команда alloc_space, где были произведены аллокации.
Теперь смотрим top, и уже в top я вижу свои функции: UnpackBin,
UnpackReflect, Codegen, BenchmarkReflect.
Теперь я опять могу использовать команду list и посмотреть Unpack,
где было выделение.
Как всегда Reader с присваиваемой переменной
и выделение слайса byte для чтения.
string — конвертация слайса byte в строку, ну, и так далее.
Это кодогенерация, а вот reflect.
То есть помимо того, что я читаю, вот тут еще много выделено памяти.
Что это такое?
Это присвоение в поле.
То есть я не сразу присваиваю в поле структуры,
а должен провести это через reflect.Value.
Ну и тоже слайс byte.
Точно так же как и CPU, мы можем посмотреть на это в виде web.
Web.
И тут мы уже можем увидеть чуть-чуть побольше.
Смотрите, тут уже все-таки наша программа в этих местах выделила больше памяти.
Посмотрим, UnpackReflect и UnpackBin — они оба используют binary.Read,
оба выделяют строку, но вот Reflect,
в ряд с reflect еще тратит на reflect.(*rtype).Field,
reflect.(*structType).Field, то есть это вот он,
вот как раз overhead, который накладывает Reflect на ваши оперции.
Здесь его наглядно видно.
Так, это было alloc_space,
а теперь давайте посмотрим, где именно сами объекты выделялись.
[БЕЗ СЛОВ] [БЕЗ
СЛОВ] [БЕЗ СЛОВ] Ну,
по-прежнему мы видим, что там же,
хотя на первом месте выделяется создание новой строки.
Также давайте посмотрим по-прежнему нашу функцию Unpack.
Ну и где были объекты?
Вот здесь объекты, вот здесь много было выделено, ну, и также вот тут.
О, всего одна!
Всего один объект выделяется на данном Unpack.
Это очень мощный инструмент исследования.
Вы можете глубоко-глубоко зарыться туда и при помощи него
оптимизировать вашу программу так, как вам надо.
Ну, и давайте под конец посмотрим web.
Ну, и опять web уже AllocObject], где какие объекты были выделены.
Смотрите, вот UnpackBin, то есть метод, который был получен кодогенерацией.
Он всего лишь выделяет строку, ну и считает бинарные данные.
А вот метод, который распаковывает данные через Reflect.
Он не только читает бинарные данные,
но еще и использует конвертацию в пустой интерфейс,
вот этот вот кусок: runtime.convT2E, ну, и структуры Reflect:
reflect.(*rtype).Field и reflect.(*structType).Field.
Вот тут тоже видно overhead, который накладывает Reflect,
и именно из-за чего Reflect медленнее, чем кодогенерация.
pprof — очень мощный инструмент.
Я dам рассказываю только про очень мощные инструменты в Go, других там нет.
Вы можете использовать либо для бенчмарка.
Более того, вы можете снимать дампы памяти и дампы профилирования процессора
прямо с работающей программы, но это мы будем рассматривать отдельно.