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. Ну, и для справки:
- мой первый CL, который не приняли: https://go-review.googlesource.com/c/54952/;
- а это второй, который приняли: https://go-review.googlesource.com/c/55210/.
Автор: Марко Кевац