От простого к сложному

20 марта 2023
От простого к сложному

В мартовском Golang-дайджесте расскажем, как удобно демонстрировать результаты работы заказчикам, создавая туннели в публичный интернет. Также рассмотрим легковесную альтернативу веб-фреймворку makaron, почитаем стайлгайд от Uber и узнаем, как ускорить отдельно взятую функцию, переписав её на языке ассемблера.

Пробиваем туннели

Большую часть времени разработчики тратят не на написание кода, а на коммуникации. Им часто приходится демонстрировать свою работу заказчикам и это должно быть не просто скриншотами или видео. Клиенту хочется самостоятельно потыкать мышкой в результат, причем на своём компьютере. Значит надо придумать, как дать временный доступ.

При разработке интеграции с внешними сервисами им тоже надо достучаться до рабочего компьютера, отправить или получить данные. Это сложно, поскольку в большинстве случаев разработчики выходят в сеть из-за NAT и не имеют статического IP-адреса. Это значит, что нужно сделать проброс портов (port forwarding) на маршрутизаторе, открыть порт файервола и стать клиентом сервиса, который будет актуализировать IP-адрес и привязывать к какому-либо домену (собственному или предоставляемому сервисом).

Такой набор действий может не сработать. Если вы сидите через мобильный интернет, то у вас будет серый IP без возможности подключения извне. Ваш рабочий ноутбук вполне может иметь ограничение на управление файерволом, исходя из принятой в компании политики безопасности. Да и время обновления DNS-записей (штатно до 24 часов) может стать проблемой, если надо «здесь и сейчас».

Одним из способов решения может быть аренда VDS со статическим IP, но это не всегда удобно. Получается, что либо разработку надо вести сразу там, либо тратить время на перемещение и запуск проекта на удалённом сервере. Куда проще воспользоваться сервисами, организующими туннель до вашего рабочего компьютера. Один из таких сервисов ngrok, реализует модель ingress-as-a-service. Минимальная команда для работы выглядит так:

ngrok http 80

Сервис выделит временный поддомен, который будет резолвиться сразу же, выполнит все необходимые действия по созданию туннеля через собственный промежуточный сервер. Таким образом мы избавляемся от всех проблем, связанных с пробросом портов, файерволами и NAT. Бонусом станет контроль трафика и его просмотр без отдельной настройки сниффера. Дополнительные плюшки в виде SSO, OAuth и SSL/TLS-сертификатов присутствуют.

Единственный недостаток — цена. Для некоммерческих проектов сервис предоставляется бесплатно и с ограниченным функционалом. Независимым коммерческим разработчикам или командам придётся разово выложить от $8 до $65 за лицензию плюс от $10 до $75 ежемесячно за обслуживание.

Если же у вас есть арендованный сервер и вы хотите получить selfhosted ngrok, то можно обратить внимание на проект pgrok. Его ещё называли ngrok для бедных (poor man's ngrok). Автор создал бесплатный сервис ejemplo.me, которым, к сожалению, воспользовались злоумышленники для распространения вредоносного ПО. Итог был предсказуем: домен заблокировали, а репозиторий теперь в архиве, в статусе read-only. Тем не менее, серверную часть pgrokd можно запустить на своём сервере и использовать клиент pgrok для доступа.

Аналогичный подход можно использовать с помощью проекта frp (сокращение от fast reverse proxy). Его преимущество в том, что он продолжает активно развиваться. Файерволы и NAT ему не помеха, дополнительные фичи, вроде аутентификации через OIDC-провайдера есть. Отдельно отметим хорошую документацию с примерами конфигурации.

Модульный фреймворк Flamego

Ненадолго поставим в сторону бокалы с gin и martini и взглянем на альтернативные веб-фреймворки. Наше внимание привлек Flamego, впитавший в себя идеи модульного фреймворка makaron, но этот легче и проще. Разработчики пожертвовали расширяемостью и получили много полезных бонусов.

Основное, чем может похвастаться Flamego — мощный синтаксис маршрутизации, опережающий всех конкурентов по экосистеме. Этот фреймворк не накладывает ограничений на внедрение промежуточного программного обеспечения и обеспечивает достаточно простую интеграцию с существующими Go-приложениями.

Отдельно стоит упомянуть возможность внедрения зависимостей через сигнатуры функции. Это значит, что зависимости передаются в функцию через её параметры. Например, если функция требует доступ к базе данных, то объект этой базы может быть передан в качестве аргумента. Такой подход позволяет гибче управлять зависимостями и сделать веб-приложение модульным.

Модульное приложение имеет ряд преимуществ. Его легче тестировать, поскольку тестирование каждого модуля можно сделать независимо от других. На практике это означает более быструю локализацию проблем и их решение. Ну а добавить или удалить новые фичи можно без необходимости глобальных изменений всего приложения.

Есть у Flamego и очевидные недостатки. Этот веб-фреймворк использует собственный формат middleware, что делает невозможным многие полезные вещи. В частности, нельзя использовать официальные прослойки от Prometeus / Grafana или любого стандартного роутера, такого как chi. Ещё один минус — активное использование reflect не лучший выбор с точки зрения производительности.

Uber Go стайлгайд

В нашем мартовском Python-дайджесте мы рассказывали, что крупные компании вроде Google всё активнее делятся с сообществом собственными наработками. В частности, своими best practices, основанными на опыте использования разных языков программирования. Это позволяет значительно экономить средства на переделывание кода, написанного независимыми программистами для OSS-сообщества.

Советуем обратить внимание на руководство, созданное двумя Go-разработчиками из Uber. Они хотели улучшить продуктивность коллег при работе с Golang. Этот документ неоднократно дополнялся и теперь содержит множество советов и рекомендаций. Каждый пункт основан на опыте решения проблем экспертами, имеющими многолетний опыт работы с Go.

В документе можно найти подтверждение, что бытовые убеждения часто оказываются ложными. Когда вы пишете приложение, даже такая банальная вещь, как подсчёт времени может быть неожиданной. Если вы уверены, что в часе 60 минут, в сутках 24 часа, в неделе 7 дней, а в году их 365, то удивитесь. Все эти убеждения ошибочны, так что не стоит пренебрегать пакетом time.

Авторы выработали аргументированное мнение, что штатные механизмы паники и восстановления не являются верной стратегией обработки ошибок. С их точки зрения, любой код в продакшене должен избегать паники, поскольку та может быть источником каскадных отказов. Если возникла ошибка, то её надо вернуть и дать таким образом с ней справиться. Паника — признак непоправимой ситуации. Это справедливо всегда, за исключением паники во время инициализации приложения.

Это лишь малая часть того, что детально разобрано в руководстве. Примеры очень наглядные и хорошо проиллюстрированные. Даже если не использовать все приведённые советы, то часть точно стоит взять за основу собственного руководства по стилю.

Экстремальное ускорение

Большинство приложений в нашем веке разрабатывается на высокоуровневых языках. Повсеместная виртуализация и контейнеризация отучили программистов думать, на каком железе будет работать их код. Это позволило существенно снизить порог входа и не особо задумываться, что происходит на уровне ниже.

Но если нам надо ускорить какую-то одну определённую функцию, чтобы та работала существенно быстрее? Тут на ум приходит воспользоваться языком ассемблера и попробовать говорить с процессором на его языке. Один из пользователей Хабра проделал такой трюк и описал свой опыт.

Здесь стоит начать с дисклеймера — это вряд ли пригодится в реальной разработке. Ни о какой надёжности речи не идёт. Рассматривайте это, как любопытный эксперимент, не более. (прим. автора)

В качестве подопытного кролика была выбрана модифицированная функция Copy для решения задачи по спортивному программированию:

func Copy(srcRGBA, dstPaletted []uint8) {
	srcPtr := unsafe.Add(unsafe.Pointer(&srcRGBA[0]), 2)
	dstPtr := unsafe.Pointer(&dstPaletted[0])
	for range dstPaletted {
		*(*uint8)(dstPtr) = *(*uint8)(srcPtr)
		dstPtr = unsafe.Add(dstPtr, 1)
		srcPtr = unsafe.Add(srcPtr, 4)
	}
}

У Go есть полезный флаг компиляции -gcflags "-S", выводящий программу в виде кода на языке ассемблера. Чтобы упростить вывод, автор статьи задействовал флаг -B, отключающий проверку границ (Bounds Check). Вот та же самая функция Copy:

0x0000 00000     TEXT    round-3/fastcopy.Copy(SB), NOSPLIT|ABIInternal, $0-48
0x0000 00000     MOVQ    AX, round-3/fastcopy.srcRGBA+8(FP)
0x0005 00005     MOVQ    DI, round-3/fastcopy.dstPaletted+32(FP)
0x000a 00010     FUNCDATA        $0, gclocals·cNGUyZq94N9QFR70tEjj5A==(SB)
0x000a 00010     FUNCDATA        $1, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
0x000a 00010     FUNCDATA        $5, round-3/fastcopy.Copy.arginfo1(SB)
0x000a 00010     FUNCDATA        $6, round-3/fastcopy.Copy.argliveinfo(SB)
0x000a 00010     PCDATA  $3, $1
0x000a 00010     ADDQ    $2, AX
0x000e 00014     XORL    CX, CX
0x0010 00016     JMP     33
0x0012 00018     MOVBLZX (AX), DX
0x0015 00021     MOVB    DL, (DI)
0x0017 00023     INCQ    CX
0x001a 00026     INCQ    DI
0x001d 00029     ADDQ    $4, AX
0x0021 00033     CMPQ    SI, CX
0x0024 00036     JGT     18
0x0026 00038     RET

Было выбрано несколько направлений оптимизации.

  1. Уменьшить количество чтений из памяти.
  2. Уменьшить количество записей в память.
  3. Выполнять операции с большим количеством данных за счёт AVX-инструкций.

Первое направление было выполнено чтением сразу 8 байт за раз, в то время как в оригинальном коде читался только 1 байт в регистр размером 8 байт. Полученный код был оптимизирован за счёт развёртки цикла, выполнения XORQ на первом шаге цикла и сборке 8 байт в регистр перед записью. Ну а вишенкой на торте стало задействование векторных инструкций. Суммарно все эти оптимизации смогли ускорить функцию почти в 10 раз.

Само же встраивание ассемблерной функции в Go-приложение выполняется элементарно. В код добавляется функция без тела, вида:

func CopyAsm(srcRGBA, dstPaletted []uint8)

Реализация на языке ассемблера кладётся в файл с расширением .s. При этом нужно использовать суффикс, обозначающий конкретную архитектуру, например, fastcopy_amd64.s. Вот такой любопытный способ для экстремального ускорения одной конкретно взятой функции.

Видеоподкаст про Go

Подкаст снимают и развивают двое наших коллег из Evrone. Они обсуждают его преимущества и недостатки, сравнивают с другими языками.

Выпуски длятся 50-70 минут, а значит можно совместить какие-нибудь скучные занятия с обучением. Отличная возможность погрузится в нюансы Go, стоя в пробке или проезжая очередную станцию метро!

Ну а если вам есть что сказать — добро пожаловать в комментарии!

Митапы

Онлайн

Go meetup

26 апреля 2023

Уже совсем скоро мы встретимся на Go Meetup. Программа мероприятия формируется, но регистрация уже открыта. Кстати, вы уже можете подать доклад прямо в режиме онлайн.

Интересуетесь нашими мероприятиями? В Telegram-канале Evrone meetups мы выкладываем анонсы с подробными описаниями докладов, а также студийные записи прошедших митапов. Тем для кого выступать в новинку, мы оказываем всяческую поддержку и помогаем оформить экспертизу в яркое выступление. Подписывайтесь и пишите @andrew_aquariuss, чтобы узнать подробности.

Регистрация

Вакансии

Удаленка / Офис

Evrone 

Мы открыты для новых Go-разработчиков. В Evrone можно работать удалённо с первого дня, мы поддерживаем и оплачиваем участие в Open-source проектах и выступления на конференциях, а расти в грейдах можно с помощью честной системы проверки навыков и менторства.

Подробнее

Подписаться
на Digest →
Важные новости и мероприятия без спама
Технологии которыми вы владеете и которые вам интересны
Ваш адрес электронной почты в безопасности - вот наша политика конфиденциальности.