Почему Вы должны оптимизировать читаемость, а не производительность

Подождите, что? Медленный код? Разве мы не должны беспокоиться об ускорении наших Go‑программ?

На самом деле, нет. Оптимизация кода на Golang ради производительности — это попросту трата времени, и вот почему:

  1. Производительность в большинстве случаев не имеет значения

  2. Go и так быстрый

  3. Читаемость важнее скорости

Эти аргументы нуждаются в объяснении, и я его дам. Для них есть исключения, как, собственно говоря, для всех нетривиальных утверждений. Честно говоря, стоит сказать, что эти 3 пункта вряд ли являются компромиссом среди программистов‑инженеров. Так что, прежде чем начать снижать мне рейтинг и писать негативные комментарии («Худшая статья на Хабре»), прочитайте до конца.

Производительность не имеет веского значения

Действительно, для большинства программ, которые многие из нас делают, не критично важна скорость. Например, вот несколько программ, над которыми я недавно работал:

  • Проверка ссылок на сайты(поиск битых ссылок)

  • Провайдер Terraform

  • Анализатор данных(скачивает и высчитывает таблицу статистики по данным мониторинга)

  • Сервис миграции сайтов(перемещает файлы и базы данных веб‑сайта между серверами)

Все эти программы запускаются относительно редко(возможно, 1 или 2 раза в день) и требуют для своего выполнения мало времени. Стоит ли тратить какие‑либо усилия для их ускорения? Конечно же нет.

Медленный не Go

Даже в тех случаях, когда мы беспокоимся о скорости программы, мы не будем ускорять Go. Например, проверка ссылок тратит 99% времени на ожидание HTTP‑запросов. Провайдер Terraform наибольшее количество времени тратит на «разговоры» с remote API. А слабым местом сервиса миграции является перемещение тяжелых tar‑архивов по сети.

Так что, если надо увеличить производительность каких‑либо программ, мы не будем этого делать на уровне Go. Процессоры невероятно быстры по сравнению с доступом к оперативной памяти, который намного быстрее доступа к жесткому диску, который намного быстрее сетевого доступа. Поэтому скорость выполнения кода — последнее, за что мы будем беспокоиться.

Когда нужно считать каждую ns/op

«Подожди‑ка, я создаю 3D игру на Go, и у меня невероятно ограничено количество кадров в секунду. Каждая наносекунда на счету, а ты говоришь, что мне не стоит беспокоиться об оптимизации???!!!!», — слышу я.

Нет. Игры являются исключением из этого правила, так как некоторые небольшие части кода (например, шейдеры) критичны к скорости. Заметьте, что даже тут мы ввели оговорку: производительность важна только для маленьких частей кода. И только если Вы не пишете 3D движок с нуля(не делайте так), Ваш код вряд ли станет слабым местом в отрисовке кадра.

Вы вряд ли будете писать код для спутников (наверное)

Но большинство программ на Go — не игры. Есть ли другие исключения? Есть: веб‑серверы и API. Они имеют критически нуждающиеся в производительности разделы. В большой, сложной распределенной системе общая задержка запроса важна. В разных подсистемах задержка может достигать наносекунды.

И, конечно, существует много разных специализированных и встраиваемых систем, где скорость имеет значение, например: драйверы ввода‑вывода, спутники и промышленные системы управления. Я допускаю все эти исключения, но утверждаю, что эти задачи не типичны для Go.

Когда производительность ну очень критична

Если производительность действительно критична для Вашей программы, то не тратьте время на настройку и доработку Go: просто используйте другой ЯП. C++ довольно хорош.

Читаемость важнее скорости

Большинство программ уделяют гораздо больше времени чтению их программистами, чем выполнению. А время программиста намного дороже, чем процессора (удачи найти разработчика за 5 центов в час). Так разве не имеет смысл оптимизировать программу в читаемости, а не скорости?)

Мы бы конечно хотели быстрые и при этом читаемые программы, и это зачастую возможно. Но когда программист начинает говорить «Я бы мог сделать это на несколько наносекунд быстрее, используя используя массив вместо словаря и вычисляя индекс с помощью младших битов ключа!», важно подумать о том, что мы отдаём взамен на наносекунды.

Пишите больше хорошего кода, меньше комментариев

Нужно, что бы практически любой Go‑программист смог заглянуть в любую функцию из Вашего кода и понять то, зачем нужен этот код и как он работает. Говоря это, я не имею ввиду, что Вы должны заполонить свой код комментариями. Их стоит писать только когда сделали все возможное для упрощения кода, но он все равно не понятен, можно сказать, что в этот момент Вы признаете поражение. «Я знаю, что ты не поймешь то, что я написал, поэтому вот моя попытка объяснить это проще».

Да, признаю, что сказать намного легче, чем сделать. Писать простой и понятный код сложно. Во‑первых, для того, чтобы кому‑либо объяснить концепцию, нужно разобраться в ней самому. Для того, чтобы объяснить это компьютеру, нужно знать это вдоль и поперек.

Вывод: Пиши медленный код, не гонись за скоростью

Естественно, я не выступаю за то, что нужно делать программы излишне медленными. Я говорю о том, что, во‑первых, не нужно так сильно гнаться за производительностью, как Вы вероятно думаете. Зачастую, когда Вы пытаетесь ускорить свой код, существуют более простые и лучшие способы улучшить его, чем написание запутанного и хитроумного алгоритма.

Во‑вторых, нужно больше беспокоиться за простоту и читаемость кода, чем, Вы, возможно, беспокоитесь сейчас. Когда у Вас появляется мысль оптимизировать код, попробуйте потратить час на улучшение его читаемости.

В‑третьих, будьте готовы на жертвы ради улучшения читаемости. Если Вы можете провести рефакторинг своей программы, чтобы сделать ее значительно понятнее и легче за счет того, что сделаете ее немного медленнее — ДЕЛАЙТЕ ЭТО. Если производительность действительно важна, помните, что она не дается бесплатно. Когда Вы подумываете об усложнении своего кода в погоне за скоростью, подумайте еще раз. Вы всегда можете купить более быстрое устройство, но вряд‑ли купите программистов, которые будут понимать сложный код.

«Junior разработчик: Мой код простой и понятный.
Middle разработчик: Мой код гениальный, быстрый, инновационный.
Senior разработчик: Мой код простой и понятный.» — John Arundel (@bitfield) January 20, 2020

Комментарии (15)


  1. dersoverflow
    05.01.2025 13:47

    1. Производительность в большинстве случаев не имеет значения

    нет, суть дела не в этом!

    и дело не в Go, а во "всем бизнесе в целом". почему? потому, что

    Ради этого места реального бизнеса АБСОЛЮТНО нет смысла стараться. Хуже дурака - только дурак с инициативой!

    Нет смысла стараться нигде, кроме узкого места (bottleneck). Но развальцуете бизнесу bottleneck -- он сразу пойдет веселее!

    https://ders.by/arch/scale/scale.html


    1. un1t
      05.01.2025 13:47

      Bottleneck может возникнуть если люди всё-таки старались писать код который должен работать быстро и возможно даже наладили нагрузочное тестирование. Если изначально в проекте никто этим не заморачивался, то тормоза размазаны по всему проекту и так просто от них не избавиться. Если архитектура кривая, то проще переписать, чем пытаться исправить в существующем.


      1. dersoverflow
        05.01.2025 13:47

        Bottleneck может возникнуть если люди всё-таки старались

        bottleneck - это не оскорбление :) он есть всегда.

        это как скорость эскадры определяется скоростью самого медленного корабля.

        тормоза размазаны по всему проекту

        практика показывает, что в реальных проектах есть только один СЕРЬЕЗНЫЙ bottleneck. максимум два.


        1. un1t
          05.01.2025 13:47

          Моя практика показывает, что такие вот выделенные боттлнэки бывают редко, а чаще все размазано толстым слоем по проекту.


      1. dersoverflow
        05.01.2025 13:47

        Если архитектура кривая, то проще переписать

        не бывает такой Роскоши, увы.

        (в серьезных проектах)


        1. un1t
          05.01.2025 13:47

          Тут согласен. Приходится обкладывать костылями, чтобы хоть как-то работало.


          1. Avatap
            05.01.2025 13:47

            Тут согласен. Приходится обкладывать костылями, чтобы хоть как-то работало

            Ну да и в итоге получаем виндовс. Который скорее костылемобиль.


  1. starwalkn
    05.01.2025 13:47

    Вообще - согласен с автором. Код в целом должен быть более простым для чтения, чем сложным, но с оптимизированным latency в 38мс вместо 40мс, хотя бы потому что на чтение такого "сложного" кода другим разработчиком явно уйдет гораздо больше времени, а еще больше времени займет рефакторинг этого кода с сохранением текущего поведения. А вообще я придерживаюсь мнения, чтоосновная проблема это обычно архитектура самого продукта.

    Напомнило
    Напомнило


    1. zubrbonasus
      05.01.2025 13:47

      38 миллисекунд это долго.

      Хорошо когда в пределах 40 микро секунд.


  1. VBKesha
    05.01.2025 13:47

    Производительность не важна...важно время программиста....

    Не идея хороша но вспоминаю ещё когда учился в институте, была задача распарсить пару тысяч эксель файлов и сделать из них базу данных.Файлы были почти одинаковы по структуре, но попадались с мелкими отличиями которые надо было отлавливать и писать обходные варианты. Так вот первая версия софтины парсила их около 40-50 минут, а потом напоролась на ошибку и упала. После исправление ошибки было ещё 60 минут и падение на другом файле. Но после небольшой оптимизации(две дополнительные строчки) все файлы начали парсится за 2-3 минуты. И через 20 минут уже все файлы парсились на отлично. Если бы не оптимизация отладка заняла бы часов 8.

    Мораль должен быть баланс.


    1. mo0Oonnn Автор
      05.01.2025 13:47

      Мы бы конечно хотели быстрые и при этом читаемые программы, и это зачастую возможно. Но когда программист начинает говорить «Я бы мог сделать это на несколько наносекунд быстрее, используя используя массив вместо словаря и вычисляя индекс с помощью младших битов ключа!», важно подумать о том, что мы отдаём взамен на наносекунды.

      Об этом тоже сказано в статье)
      На самом деле у меня была похожая с вашей сутуация, что нужно было парсить тысячи пользователей ВК, так еще и все это в бд сохранять. Работало больше 10 минут - точно не скажу, потому что на 10 минутах я понял, что это как-то долго) добавил горутин - все стало выполняться меньше минуты. В данном случае горутины - спасение, так как данные не взаимодействуют между собой или с одними и теми же переменными, но бывают ситуации похуже, там уже как-будто бы go сильно не спасет)


  1. slonopotamus
    05.01.2025 13:47

    Какая-то зацикленная аргументация. На го не пишут перф-оптимизированные программы потому что он для этого не предназначен, поэтому и не надо особо оптимизировать.


    1. mo0Oonnn Автор
      05.01.2025 13:47

      И, конечно, существует много разных специализированных и встраиваемых систем, где скорость имеет значение, например: драйверы ввода‑вывода, спутники и промышленные системы управления. Я допускаю все эти исключения, но утверждаю, что эти задачи не типичны для Go.


      1. slonopotamus
        05.01.2025 13:47

        Окей, а почему эти задачи не типичны для Go?


        1. mo0Oonnn Автор
          05.01.2025 13:47

          На го не пишут перф-оптимизированные программы потому что он для этого не предназначен

          Вас же цитирую. Зачем спрашивать, если сами знаете?)


        1. dersoverflow
          05.01.2025 13:47

          потому что у Go есть GC, а сборщик мусора в многопоточном приложении - это УБИЙЦА производительности!!!

          1. многопоточность очень сложна, но оправдана при росте производительности.

          2. а потом мы прикручиваем костыль, убивающий всю производительность...

          3. за что боролись граждане??

          держу пари, что вы это не знали:

          • by using GC, applications spend 7–82% more wall-clock time and 6–92% more CPU cycles relative to their intrinsic costs.

          • both Shenandoah and ZGC have worse (by factors of 10–100×) query latencies than the other three collectors!

          здесь подробности https://www.linkedin.com/pulse/how-java-garbage-collection-works-sergey-derevyago-af84f


  1. zubrbonasus
    05.01.2025 13:47

    Вывод: Пиши медленный код, не гонись за скоростью

    Какой код медленный, а какой быстрый?


    1. mo0Oonnn Автор
      05.01.2025 13:47

      В данном случае медленный код - код, над которым Вы не пыхтели в погоне за скоростью, быстрый - наоборот