[БЕЗ СЛОВ] В предыдущем видео мы рассмотрели каким образом мы можем сравнить два подхода, каких-то два кода, какой из них быстрее, и мы узнали, что подход с кодогенерацией будет быстрее, подхода с рефлексией. Мы пользовались флагом 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, других там нет. Вы можете использовать либо для бенчмарка. Более того, вы можете снимать дампы памяти и дампы профилирования процессора прямо с работающей программы, но это мы будем рассматривать отдельно.