Badoo corporate blog
Programming
Go
August 2017 29

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

Original author: Agniva De Sarker
Translation


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=%s\n", 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. Ну, и для справки:


+66
18.6k 68
Comments 85