- Оригинальный адрес:Using Causal Profiling to Optimize the Go HTTP/2 Server
- Оригинальный автор:Morsing
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:JackEggie
- Корректор:foxxnuaa
Оптимизация серверов Go HTTP/2 с помощью причинно-следственного анализа
Введение
Если вы следите за этим блогом, то вы должны прочитать этоэссе, вводящее в причинно-следственный анализ. Этот стиль анализа направлен на установление связи между циклами потребления производительности и оптимизацией производительности. Я практиковал такой анализ в Go. Я думаю, пришло время применить это на практике в реальном программном обеспечении — HTTP/2-реализации стандартной библиотеки Go.
HTTP/2
HTTP/2 — это свежая реализация протокола HTTP/1, с которым мы уже знакомы и сыты по горло. Одно из его соединений может использоваться для отправки или получения запросов несколько раз, чтобы уменьшить накладные расходы на установление соединения. Реализация в Go выделяет одну горутину на запрос или выделяет несколько горутин в соединении для обработки асинхронной связи.Чтобы решить, кто и когда может записывать данные в соединение, несколько горутин будут координировать свои действия друг с другом.
Этот дизайн очень подходит для причинно-следственного анализа. Если есть что-то в темноте, блокирующее запрос, он легко обнаружит это в причинно-следственном анализе, и это может быть менее просто в режиме традиционного анализа.
Экспериментальная конфигурация
Для простоты измерения я построил комплексный тест на основе сервера HTTP/2 и его клиентов. Сервер запрашивает главную страницу Google, чтобы получить заголовки и тело запроса, и записывает каждый запрос. Клиенты используют заголовки на стороне клиента Firefox для запроса документов по корневому пути. Максимальное количество одновременных запросов для клиентов — 10. Это количество выбирается произвольно, но его должно быть достаточно для насыщения процессора.
Нам нужно проследить программу, чтобы выполнить причинно-следственный анализ. мы установимProgressфлаг, который записывает время выполнения, прошедшее между двумя строками кода. Сервер HTTP/2 вызоветrunHandlerфункция, которая запустит обработчик HTTP в горутине. Мы отметили начало перед созданием горутины, чтобы оценить параллельную задержку планирования и накладные расходы на обработку HTTP. Маркер конца устанавливается после того, как обработчик записал все данные в канал.
Чтобы получить тестовую базу, воспользуемся традиционным способом получения данных анализа ЦП с сервера.Результаты следующие:
Ну, вот что мы получаем от большого приложения, которое уже оптимизировано, огромный граф вызовов, который трудно оптимизировать. Большое красное поле — это системный вызов, который мы не можем оптимизировать.
Приведенные ниже данные дают нам более релевантный контент, но также не очень помогают нам.
(pprof) top
Showing nodes accounting for 40.32s, 49.44% of 81.55s total
Dropped 453 nodes (cum <= 0.41s)
Showing top 10 nodes out of 186
flat flat% sum% cum cum%
18.09s 22.18% 22.18% 18.84s 23.10% syscall.Syscall
4.69s 5.75% 27.93% 4.69s 5.75% crypto/aes.gcmAesEnc
3.88s 4.76% 32.69% 3.88s 4.76% runtime.futex
3.49s 4.28% 36.97% 3.49s 4.28% runtime.epollwait
2.10s 2.58% 39.55% 6.28s 7.70% runtime.selectgo
2.02s 2.48% 42.02% 2.02s 2.48% runtime.memmove
1.84s 2.26% 44.28% 2.13s 2.61% runtime.step
1.69s 2.07% 46.35% 3.97s 4.87% runtime.pcvalue
1.26s 1.55% 47.90% 1.39s 1.70% runtime.lock
1.26s 1.55% 49.44% 1.26s 1.55% runtime.usleep
Похоже, что программа состоит в основном из вызовов методов времени выполнения и вызовов методов шифрования. Пока отложим в сторону метод шифрования, так как он уже достаточно оптимизирован.
Используйте причинно-следственный анализ, чтобы сохранить программу
Рекомендуется проверить, как работает программа, прежде чем использовать причинно-следственный анализ для получения результатов. Когда причинно-следственный анализ включен, программа выполняет серию тестов. Тест сначала выбирает вызов и выполняет некоторые процедуры ускорения. Когда вызов выполняется (определяется профилированием нижнего уровня программы), мы замедляем выполнение других потоков, ускоряя программу.
Это может показаться нелогичным, но поскольку мы знаем изProgressОтмечаем, насколько медленнее будет работать программа при запуске, мы можем убрать этот эффект, чтобы получить время работы программы после ускорения посещения сайта. Я предлагаю вам прочитать другие мои статьи о причинно-следственном анализе илиоригинальная бумагачтобы узнать больше о том, как это работает.
В конечном итоге причинно-следственный анализ выглядит как некие ускоренные запросы, заставляющиеProgressИзменилось время выполнения кода между тегами. Для сервера HTTP/2 результат запроса выглядит следующим образом:
0x4401ec /home/daniel/go/src/runtime/select.go:73
0% 2550294ns
20% 2605900ns +2.18% 0.122%
35% 2532253ns -0.707% 0.368%
40% 2673712ns +4.84% 0.419%
75% 2722614ns +6.76% 0.886%
95% 2685311ns +5.29% 0.74%
В этом примере мы наблюдаемselectв коде времени выполненияunlockперечислить. Мы фактически ускорили этот звонок, изменив количество звонков, затраченное время и разницу с базовым уровнем. Получается, что мы не получаем больше потенциального прироста производительности от таких ускорений. На самом деле, когда мы ускоряемсяselectкод, программа становится медленнее.
Четвертый столбец данных выглядит немного странно. Это доля выборок, обнаруженных в этом запросе, которая должна быть пропорциональна ускорению. В традиционном анализе его можно грубо выразить как ожидаемый прирост производительности при ускорении.
Теперь посмотрите на более интересный результат анализа звонков:
0x4478aa /home/daniel/go/src/runtime/stack.go:881
0% 2650250ns
5% 2659303ns +0.342% 0.84%
15% 2526251ns -4.68% 1.97%
45% 2434132ns -8.15% 6.65%
50% 2587378ns -2.37% 8.12%
55% 2405998ns -9.22% 8.31%
70% 2394923ns -9.63% 10.1%
85% 2501800ns -5.6% 11.7%
Вызов находится в коде стека, и приведенные выше данные показывают, что ускорение здесь может дать хорошие результаты. Данные в четвертом столбце показывают, что на эту часть кода приходится значительная часть кода во время работы программы. Давайте взглянем на результаты анализа традиционным методом анализа с упором на код стека на основе приведенных выше тестовых данных.
(pprof) top -cum newstack
Active filters:
focus=newstack
Showing nodes accounting for 1.44s, 1.77% of 81.55s total
Dropped 36 nodes (cum <= 0.41s)
Showing top 10 nodes out of 65
flat flat% sum% cum cum%
0.10s 0.12% 0.12% 8.47s 10.39% runtime.newstack
0.09s 0.11% 0.23% 8.25s 10.12% runtime.copystack
0.80s 0.98% 1.21% 7.17s 8.79% runtime.gentraceback
0 0% 1.21% 6.38s 7.82% net/http.(*http2serverConn).writeFrameAsync
0 0% 1.21% 4.32s 5.30% crypto/tls.(*Conn).Write
0 0% 1.21% 4.32s 5.30% crypto/tls.(*Conn).writeRecordLocked
0 0% 1.21% 4.32s 5.30% crypto/tls.(*halfConn).encrypt
0.45s 0.55% 1.77% 4.23s 5.19% runtime.adjustframe
0 0% 1.77% 3.90s 4.78% bufio.(*Writer).Write
0 0% 1.77% 3.90s 4.78% net/http.(*http2Framer).WriteData
Приведенные выше данные показывают, чтоnewstackОтwriteFrameAsyncпозвонил в. Всякий раз, когда сервер HTTP/2 отправляет кадр данных клиенту, создается горутина и вызывается метод. И в любой момент есть только одинwriteFrameAsyncможет работать, если программа попытается отправить больше кадров данных, она будет заблокирована до тех пор, пока не будет получен предыдущийwriteFrameAsyncвозвращение.
так какwriteFrameAsyncВызовы охватывают несколько логических уровней, поэтому неизбежно генерируется большое количество вызовов стека.
Как я улучшил производительность своего сервера HTTP/2 на 28,2%
Рост стека замедляет выполнение программы, поэтому нам нужно принять некоторые меры, чтобы этого избежать. Вызывается каждый раз при создании горутиныwriteFrameAsync, поэтому мы несем расходы на рост стека каждый раз, когда пишем в кадр данных.
И наоборот, если мы можем повторно использовать горутину, мы можем увеличить стек только один раз и повторно использовать сгенерированный стек для каждого последующего вызова. Я развернул это изменение на сервере, и базовый уровень теста для причинно-следственного анализа снизился с 2,650 мс до 1,901 мс, что означает повышение производительности на 28,2%.
Важно отметить, что серверы HTTP/2 обычно локально не работают на полной скорости. По моим оценкам, если сервер подключен к Интернету, выигрыш будет намного меньше, поскольку время ЦП, потребляемое ростом стека, намного меньше, чем задержка в сети.
в заключении
Метод причинно-следственного анализа еще не созрел, но я думаю, что этот небольшой пример ясно показывает его потенциал. Вы можете ознакомиться с проектомфилиал, что добавило могилу причинно-следственному анализу. Вы также можете порекомендовать мне другие тесты, чтобы посмотреть, какие еще выводы мы можем сделать.
Примечание: в настоящее время я ищу работу. Если вам нужен кто-то, кто понимает основы языка Go и знаком с распределенными архитектурами, ознакомьтесь с моимрезюмеили отправить письмо наdaniel@lasagna.horse.
Статьи по Теме
- Обновление концепции причинно-следственного анализа
- Причинно-следственный анализ в Go
- Обработка исключений в Go
- netpoller в Go
- планировщик в Go
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.