Как новичок в Go контрибьютил

в 9:53, , рубрики: Go, runtime, Блог компании Badoo, Компиляторы, новичок, Программирование

Как новичок в Go контрибьютил - 1
Rocky Runs Up The Stairs

Привет. Вы, наверно, меня помните: я – Марко Кевац, системный программист в Badoo. Недавно я наткнулся на небольшой рассказ о том, как новичок сделал изменение в рантайме языка Go. Несмотря на то, что этот пост, наверное, довольно неожиданно встретить в хабраблоге Badoo, и многие могут сказать, что он банален и переполнен наивной радостью, я считаю, что такие истории демонстрируют, насколько сообщество Go доброжелательно и внимательно по отношению ко всем его участникам. Поэтому и перевел его.

А ещё в посте есть два интересных факта, связанных с внутренностями языка. Приятного чтения!

Я уже некоторое время пишу open-source-программы на Go. И вот только недавно у меня появилась возможность писать на Go и на работе. Я с радостью переключился и стал самым настоящим Go-разработчиком.

Всё было отлично, пока не случилась последняя конференция GopherCon, где проводили мастер-класс для тех, кто хочет делать вклад в развитие языка. И вот, видя, как все эти люди коммитят код в основной репозиторий, я тоже захотел что-то сделать. А буквально через пару дней Francesc Campoy записал прекрасное видео, в котором подробно рассказывает, как контрибьютить в Go.

Желание быть причастным охватило меня. Я понятия не имел, что я могу сделать, но решил скачать исходный код и скомпилировать его. Так и начался мой путь по дороге контрибьютора Go.

Я читал инструкцию для контрибьюторов и шаг за шагом выполнял её. Подписать CLA получилось не сразу, так как инструкция была немного корявой. Собственно, почему бы не указать на это и не предложить исправить её? Это может быть моим первым CL! Вдохновлённый, я создал тикет. Но оказалось, я наступил на стандартные грабли новичка. Проблема уже была решена в Tip, а я даже не догадался посмотреть.

Поскольку у меня уже всё было готово, я периодически просто гулял по стандартной библиотеке в поисках, что бы поправить. И поскольку я уже несколько месяцев программировал на Go на работе, я столкнулся с частями рантайма, которые постоянно всплывали во время профилирования. Одна из таких частей – пакет fmt. Я решил посмотреть на него внимательно и понять, можно ли что-то с этим сделать. Примерно через час я натолкнулся на кое-что интересное.

Функция fmt_sbx в файле fmt/format.go начинается следующим образом:

func (f *fmt) fmt_sbx(s string, b []byte, digits string) {
    length := len(b)
    if b == nil {
        // No byte slice present. Assume string s should be encoded.
        length = len(s)
    }

Было ясно, что len() вызывается два раза в случае, если b равно nil, хотя чаще всего достаточно одного. Но если передвинуть его в else, то len() вызовется всегда только один раз. Я решил отправить CL об этом, чтобы посмотреть, что скажут другие.

Буквально через пару минут Ian дал оценку +2, а затем Avelino – +1. Я не мог поверить в это!

Но потом что-то пошло не так. Dave поставил -1, и Martin – тоже. Он взял бинарный дамп кода и увидел, что между двумя вариантами нет никакой разницы. Компилятор был достаточно умным, чтобы сделать эту небольшую оптимизацию. Так что в итоге я оказался в минусе: else плохо сказывается на читабельности кода, а выигрыша в производительности никакого.

Этот CL пришлось забросить…

Но я узнал много нового. Например, о таких утилитах, как benchstat и benchcmp. Более того, теперь я был знаком со всем процессом и попробовать ещё раз мне ничего не стоило.

Вскоре я узнал, что простая конкатенация строк гораздо быстрее, чем fmt.Sprintf(). С этим знанием я пошёл искать «жертву», и это не заняло много времени. Я остановился на пакете archive/tar. Функция formatPAXRecord в файле archive/tar/strconv.go содержит следующий код:

size := len(k) + len(v) + padding
size += len(strconv.Itoa(size))
record := fmt.Sprintf("%d %s=%sn", size, k, v)

После того как я поменял последнюю строчку на record := fmt.Sprint(size) + " " + k + "=" + v + "n", я увидел значительное ускорение:

name             old time/op    new time/op    delta
FormatPAXRecord     683ns ± 2%     457ns ± 1%  -33.05%  (p=0.000 n=10+10)

name             old alloc/op   new alloc/op   delta
FormatPAXRecord      112B ± 0%       64B ± 0%  -42.86%  (p=0.000 n=10+10)

name             old allocs/op  new allocs/op  delta
FormatPAXRecord      8.00 ± 0%      6.00 ± 0%  -25.00%  (p=0.000 n=10+10)

Остальное, как говорится, уже история. На этот раз Joe отревьюил код, и после нескольких мелких исправлений его замёржили! Ура! Я внёс свой вклад в развитие Go. Я прошёл путь от средненького open-source-контрибьютора до контрибьютора в язык программирования Go.

Это далеко не конец. Я начинаю лучше понимать тонкости языка и буду продолжать создавать CL каждый раз, когда что-то найду. Десять чашек чая господам из Go-команды за то, что они так изящно и безустанно управляют разработкой такого сложного проекта.

P.S. Ну, и для справки:

Автор: Марко Кевац

Источник

Поделиться

* - обязательные к заполнению поля