Рефакторинг кода Go: 23-кратный взрыв производительности
Несколько недель назад я прочитал статью под названием "Плохие коды в Good Code vs Go Code», автор шаг за шагом ведет нас, чтобы завершить рефакторинг реальных вариантов использования в бизнесе.
Смысл этой статьи в том, чтобы превратить «плохой код» в «хороший код»: более идиоматический, более читабельный, используя особенности языка go. Но он также настаивает на производительности как на важном аспекте проекта. Это разожгло мое любопытство: давайте копать глубже!
Программа в основном считывает входной файл и анализирует каждую строку, чтобы заполнить объекты в память.
Автор не толькона Гитхабевыпускисходный код, он же написал идиоматический бенчмарк. Это очень хорошая идея, как приглашение настроить код и воспроизвести измерения с помощью команды:
$ go test -bench =。

Каждое выполнение мкс (чем меньше, тем лучше)Таким образом, «хороший код» на моей машине работает на 16% быстрее. Можем ли мы получить больше?
По моему опыту, существует интересная связь между качеством кода и производительностью. Когда вы успешно реконструируете код, чтобы сделать его более ясным и разделенным, вы обычно в конечном итоге делаете его быстрее, потому что это не делает несвязанные инструкции ранее запутанными, и потому что некоторые возможные оптимизации становятся очевидными. Их легко реализовать.
С другой стороны, если вы будете гнаться за производительностью дальше, вам придется отказаться от простоты и прибегнуть к взлому. Вы соскабливаете несколько миллисекунд, но качество кода страдает, поскольку его становится труднее читать и анализировать, он становится более хрупким и менее гибким.
Поднимитесь на гору Простота, затем спуститесь
Это компромисс: как далеко вы готовы зайти?
Чтобы правильно расставить приоритеты в своей работе, наиболее ценная стратегия — определить узкие места и сосредоточиться на них. Для этого используйте инструменты аналитики!PprofиTraceТвой друг:
$ go test -bench =。-cpuprofile cpu.prof
$ go tool pprof -svg cpu.prof> cpu.svg
Значительный график использования ЦП (нажмите на SVG)
$ go test -bench =。-trace trace.out
$ go工具跟踪trace.out
Отслеживание радуги: множество мелких задач (нажмите, чтобы открыть, только для Chrome)
Трассировка доказывает, что используются все ядра процессора (нижняя строка 0, 1 и т. д.), что на первый взгляд кажется хорошим. Но он показывает тысячи маленьких цветных фрагментов вычислений, а также несколько свободных слотов с некоторыми простаивающими ядрами. Увеличиваем:
Каждое ядро на самом деле проводит много времени в простое и постоянно переключается между микрозадачами. Кажется, что степень детализации задач не оптимальна, в результате чего многиепереключатель контекстаи конфликт из-за синхронизации.
Давай проверимдетектор конфликтовКорректна ли синхронизация (если нет, то наша проблема больше, чем производительность):
$ go test -race
PASS
Да! ! Выглядит корректно, гонок данных не наблюдается. Тестовая функция и эталонная функция отличаются (см. документацию), но здесь вызывают одну и ту же функциюParseAdexpMessage, мы можем использовать-race
.
Параллельная политика в «хорошей» версии включает обработку каждого ввода строки в собственной горутине для использования нескольких ядер. Это юридическая интуиция, потому что репутация Goroutines легковесна и дешева. Какую пользу мы получаем от параллелизма? Давайте сравним один и тот же код в одном порядке в Goroutine (непосредственно перед удалением функции разрешения строкgoключевое слово)
мкс на выполнение (чем меньше, тем лучше)
Черт возьми, это на самом деле быстрее без параллелизма. Это означает, что (ненулевые) накладные расходы на запуск горутины перевешивают время, сэкономленное за счет одновременного использования нескольких ядер.
Естественный следующий шаг, поскольку теперь мы обрабатываем строки последовательно, а не одновременно, состоит в том, чтобы избежать (ненулевых) накладных расходов на использование канала результатов: давайте заменим его кубиком.
Теперь мы доступны от «хорошей» версии примерно на 40% от ускорения, но упрощает код, удалить одновременный (разница).
С одной горутиной в любой момент времени работает только 1 ядро ЦП.
Теперь давайте посмотрим на горячие вызовы функций в графе Pprof:
найти узкие места
Наша текущая версия бенчмарка (последовательная, со срезами) тратит 86% времени на разбор сообщения, и это хорошо. Мы быстро заметили, что 43% всего времени было потрачено на объединение регулярного выражения с(* Regexp) .findall соответствует.
Хотя регулярные выражения являются удобным и гибким способом извлечения данных из необработанного текста, у них есть недостатки, включая затраты памяти и времени выполнения. Они мощные, но могут быть излишними для многих случаев использования.
В нашей программе шаблон
patternSubfield =“ - 。[^ - ] *”
В основном используется для идентификации " ", которое начинается с тире "-"Заказ", и в строке может быть несколько команд. С некоторой настройкой вы можете использоватьbytes.SplitЗаканчивать.让我们调整代码(commit,commit), чтобы заменить регулярное выражение на Split:
мкс на выполнение (чем меньше, тем лучше)
Ого, это дополнительные 40%!
График ЦП теперь выглядит так:
Без огромных затрат на дополнительные регулярные выражения. Выделение памяти из 5 разных функций заняло совсем немного времени (40%). Интересно, что 21% всего времени сейчас составляютбайтЗанимать.Trim.
Эта функция меня заинтересовала: можем ли мы сделать лучше?
bytes.Trimожидать "cutsetстрока" в качестве аргумента (для символов, которые нужно удалить слева и справа), но мы используем только одинкосмосбайты как набор вырезок. Вот пример, когда вы можете повысить производительность, внеся некоторую сложность: реализуйте свою собственную функцию «обрезки» вместо стандартной библиотечной функции. существуетПользовательская «тонкая настройка»Транзакция имеет только один вырезанный набор байтов.
Да, еще 20% урезали. Текущая версия в 4 раза быстрее исходной «плохой» скорости, при этом машина использует только 1 ядро ЦП. Довольно существенно!
Ранее мы отказались от параллелизма на уровне оперативной обработки, но все еще есть возможности для улучшения с одновременными обновлениями и более грубой детализацией. Например, обработка 6000 файлов (6000 сообщений) выполняется быстрее на моей рабочей станции, когда каждый файл обрабатывается в собственной горутине:
мкс на сообщение (чем меньше, тем лучше, фиолетовый параллелизм)
66% победы (т.е. ускорение в 3 раза), это хорошо, но "не так много", потому что использует все мои 12 ядер процессора! Это может означать использование нового кода оптимизации, обработка всего файла по-прежнему является «небольшой задачей», нельзя игнорировать горутины и синхронные накладные расходы.
Интересно, что увеличение количества сообщений с 6 000 до 120 000 не повлияло на производительность последовательной версии, ауменьшатьпроизводительность версии "1 горутина на сообщение". Это связано с тем, что запуск большого количества горутинвозможный, иногдаполезный, но это дает пройти времяПланировщиквызвал некоторое давление.
Мы можем сделать это, создав всего несколько рабочих местперсоналсократить время выполнения (не в 12 раз, но все же),Например, 12 долгосрочных Goroutines, каждая обработка части сообщения:
Каждое сообщение [mu] s (чем меньше, тем лучше, фиолетовый параллельный)
Настроенный параллелизм для массовых сообщений сокращает время выполнения на 79 % по сравнению с последовательной версией. Обратите внимание, что эта стратегия имеет смысл только в том случае, если вы действительно имеете дело с большим количеством файлов.
Оптимальное использование всех ядер ЦП включает в себя несколько горутин, каждая из которых обрабатывает достаточное количество данных без какой-либо связи и синхронизации до завершения.
Выбор такого количества процессов (горутин), сколько доступно ядер ЦП, является обычной эвристикой, но невсегдаЛучший: Ваш пробег может варьироваться в зависимости от характера миссии. Например, если ваши задачи читают из файловой системы или делают сетевые запросы, для производительности вполне разумно иметь больше горутин, чем ядер ЦП.
мкс на сообщение (чем меньше, тем лучше, фиолетовый параллелизм)
Мы дошли до того, что стало сложно повысить эффективность кода синтаксического анализа за счет усовершенствований локализации. Теперь время выполнения небольших объектов (таких как Message Structure) выделения и сборки мусора увеличилось, что имеет смысл, поскольку известно относительно медленные операции управления памятью. Дальнейшая оптимизация политики размещения ...... оставила хитрое упражнение читателю.
Использование совершенно другого алгоритма также может дать большое ускорение.
Вот я черпаю вдохновение из этого разговора
Go — сканирование словарного запаса в Робе ПайкеСоздайте собственный лексер (источник) и пользовательский парсер (источник). Это доказательство концепции (я не реализовал все крайние случаи), он не такой интуитивный, как исходный алгоритм, а правильная обработка ошибок может быть сложной. Однако он на 30% быстрее, чем предыдущая оптимизированная версия.
мкс на сообщение (чем меньше, тем лучше, фиолетовый параллелизм)
Да, это ускорение в 23 раза по сравнению с исходным кодом.
Вот и все на сегодня, я надеюсь, вам понравится путешествие. Вот некоторые оговорки и выводы:
- Производительность может быть улучшена на нескольких уровнях абстракции с использованием различных методов, и выигрыш мультипликативен.
- Сначала настройте высокоуровневые абстракции: структуры данных, алгоритмы, правильную развязку. Позже настройте низкоуровневые абстракции: ввод-вывод, пакетную обработку, параллелизм, использование stdlib, управление памятью.
- Big-OАнализ является фундаментальным, но обычно не подходящим инструментом для ускорения работы данной программы.
- Бенчмаркинг — это сложно. Используйте профилирование и бенчмаркинг, чтобы обнаружить узкие места и получить представление о вашем коде. Имейте в виду, что результаты тестов не являются «реальной» задержкой, с которой конечные пользователи сталкиваются в производственной среде, и относитесь к этим цифрам с долей скептицизма.
- К счастью, инструменты (Bench,Pprof ,Trace,Race detector ,Cover) делает исследование производительности доступным и захватывающим.
- Написать хорошие релевантные тесты непросто. Но они бесценны и могут помочь «сохранить правильность», то есть провести рефакторинг, сохранив при этом исходную правильность и семантику.
- Найдите минутку, чтобы спросить себя, насколько быстро «достаточно быстро». Не тратьте время на чрезмерную оптимизацию одноразовых скриптов. Учтите, что оптимизация сопряжена с затратами: временем разработки, сложностью, ошибками, техническим долгом.
- Подумайте дважды, прежде чем скрывать код.
- ΩАлгоритмы (n²) и выше обычно дороги.
- O (n) или O (n log n) или более низкая сложность обычно в порядке.
- изрецессивный факторЭто нельзя игнорировать! Например, все улучшения в статье достигнуты за счет уменьшения этих факторов, а не за счет изменения класса сложности алгоритма.
- Ввод-вывод часто является узким местом: сетевые запросы, запросы к базе данных, файловые системы.
- Регулярные выражения, как правило, являются более дорогим решением, чем это действительно необходимо.
- Выделение памяти дороже вычислений.
- Объекты в стеке дешевле, чем объекты в куче.
- Нарезку можно использовать как альтернативу дорогостоящему перераспределению.
- Строки допустимы только для чтения (включая сбросы), но []байты более эффективны для любых других операций.
- Память важна (удобство кэш-памяти процессора).
- Полезно иметь параллель, но это сложно.
- При копании все глубже и ниже возникает «стеклянный пол», через который очень не хочется пробиваться. Если вам нужны инструкции ассемблера, встроенные функции, SIMD... возможно, вам следует подумать о прототипировании, а затем переключиться на язык низкого уровня, чтобы получить максимальную отдачу от оборудования и каждой наносекунды!
posted on 2018-07-11 18:50 sunsky303Читать(640) Комментарии(3)редактировать собирать
Комментарий
#1-й этаж 2018-07-11 19:01 Блог Команда Парка
Здравствуйте, картинка в тексте не может быть отображена, пожалуйста, загрузите картинкуПоддержка(0)против (0)http://pic.cnblogs.com/face/35695/20140318223943.png
#2-й этаж[Арендодатель] 2018-07-11 20:06sunsky303
@Блог Команда Парка
прошел дальшеПоддержка(0)против (0)http://pic.cnblogs.com/face/420532/20170329141435.png
#3-й этаж40183882018/7/11 22:24:40 2018-07-11 22:24 вырезка
служба поддержкиПоддержка(0)против (0)http://pic.cnblogs.com/face/u41249.jpg обновить комментарийобновить страницуBack to topЗарегистрированные пользователи могут оставлять комментарии только после авторизации.Авторизоватьсяилирегистр,доступдомашняя страница сайта.[Рекомендуется] Более 500 000 исходных кодов VC++: крупномасштабное промышленное управление конфигурацией, моделирование мощности САПР и библиотека исходного кода ГИС!
[Рекомендация] Как быстро создавать приложения с искусственным интеллектом?
[Конкурс] 2018 Первый конкурс разработчиков искусственного интеллекта «Неукротимый»
· Бывший исполнительный директор Xiaomi Барра заработал состояние на IPO, владея пакетом акций на сумму 209 миллионов долларов.
· App Store 10 лет, вот 10 маленьких секретов о приложениях
· ARM и RISC-V откровенно рвутся, отец GNOME обвиняет ARM
· Facebook дает ученым 1 ПБ данных: для изучения дезинформации
· Пожалуйста, отпусти молодых, отпусти моих родителей: знания оплачены, а яд тонет.
» больше новостей...
· Три идеи, которые ставят под угрозу карьеру программиста
· Пошаговое выполнение точки останова — неэффективный метод отладки.
· Тест | Пусть у каждой пыли будет цель
· От Excel к микросервисам
· Как улучшить свои способности? Несколько советов юным программистам
» Другие статьи базы знаний...
навигация
статистика
- Эссе - 148
- Статья 1
- Комментарии - 30
- Цитаты - 0
объявление
поиск
Самая используемая ссылка
последнее эссе
- 1. Рефакторинг кода Go: 23-кратный взрыв производительности
- 2. 2PC и 3PC протоколов согласованности распределенных систем
- 3. Опровергнуть статью 2B "Почему я отказался от языка Go"
- 4. Все сокеты
- 5. Углубленное изучение swoole 2. TCP-сервер и TCP-клиент
- 6. схема памяти процесса Linux
- 7. Dnsmasq ускоряет локальные DNS-запросы
- 8. Подробное объяснение и руководство по dnsmasq
- 9. Погода в Пекине сегодня потрясающая.
- 10. Данные DNS: запись, имя поддомена, псевдоним CNAME, PTR, MX, TXT, SRV, TTL.
мой ярлык
- 2017(2)
- Сравнение производительности golang, python, php, c++, c, java, Nodejs в 2017 году(1)
- c(1)
- c++(1)
- Design Patterns(1)
- golang(1)
- java(1)
- Nodejs(1)
- php(1)
- python(1)
- Более
Архив эссе(149)
- июль 2018 (6)
- июнь 2018 (24)
- Май 2018 (21)
- Апрель 2018 (12)
- Март 2018 (13)
- Февраль 2018 (1)
- Январь 2018 (14)
- Декабрь 2017 (14)
- ноябрь 2017 г. (2)
- Октябрь 2017 (7)
- Сентябрь 2017 (4)
- август 2017 (5)
- Июнь 2017 (3)
- Май 2017 (2)
- Март 2017 (8)
- Февраль 2017 (3)
- декабрь 2013 г. (1)
- сентябрь 2013 г. (2)
- август 2013 г. (1)
- январь 2013 г. (2)
- июнь 2011 г. (1)
- март 2011 г. (3)
Архив статей (1)
Очки и рейтинг
- Кредиты - 27900
- Ранг - 15458