Когда оптимизация преждевременна?

Как сказал Кнут,

Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла.

Это то, что часто встречается в ответах Stack Overflow на такие вопросы, как "какой механизм цикла является наиболее эффективным", "Методы оптимизации SQL?" ( и так далее). Стандартный ответ на эти вопросы по оптимизации состоит в том, чтобы сначала профилировать код и посмотреть, является ли это проблемой в первую очередь, а если нет, то, следовательно, ваша новая техника не нужна.

Мой вопрос: если конкретный метод отличается, но не особенно неясен или не запутан, можно ли это считать преждевременной оптимизацией?

Вот связанная статья Рэндалла Хайда под названием "Ошибка преждевременной оптимизации".

20 ответов

Решение

Дон Кнут начал движение за грамотное программирование, потому что считал, что наиболее важной функцией компьютерного кода является донесение намерений программиста до читателя. Любая практика кодирования, которая затрудняет понимание вашего кода во имя производительности, является преждевременной оптимизацией.

Некоторые идиомы, которые были введены во имя оптимизации, стали настолько популярными, что все понимают их, и они стали ожидаемыми, а не преждевременными. Примеры включают

  • Использование арифметики указателей вместо нотации массива в C, включая использование таких идиом, как

    for (p = q; p < lim; p++)
    
  • Перепривязка глобальных переменных к локальным переменным в Lua, как в

    local table, io, string, math
        = table, io, string, math
    

Помимо таких идиом, используйте ярлыки на свой страх и риск.

Вся оптимизация преждевременна, если только

  • Программа слишком медленная (многие люди забывают эту часть).

  • У вас есть измерение (профиль или подобное), показывающее, что оптимизация может улучшить ситуацию.

(Также допустимо оптимизировать для памяти.)

Прямой ответ на вопрос:

  • Если ваша "другая" техника усложняет понимание программы, тогда это преждевременная оптимизация.

РЕДАКТИРОВАТЬ: В ответ на комментарии, использование быстрой сортировки вместо более простого алгоритма, как сортировка вставки, является еще одним примером идиомы, которую все понимают и ожидают. (Хотя если вы пишете свою собственную процедуру сортировки вместо использования библиотечной процедуры сортировки, можно надеяться, что у вас есть очень веская причина.)

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

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

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

Если вы не профилировали, это преждевременно.

Мой вопрос: если конкретный метод отличается, но не особенно неясен или не запутан, можно ли это считать преждевременной оптимизацией?

Хм... Итак, у вас есть две готовые методики, одинаковые по стоимости (одинаковые усилия по использованию, чтению, изменению) и одна из них более эффективна. Нет, в этом случае использование более эффективного не было бы преждевременным.

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

Вот проблема, которую я вижу со всей концепцией предотвращения преждевременной оптимизации.

Существует разрыв между тем, чтобы говорить это и делать это.

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

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

Какова причина, приведенная в учебном материале, где преподаются эти абстрактные концепции проектирования, такие как архитектура на основе уведомлений и сокрытие информации, где простая установка логического свойства объекта может иметь неограниченный волновой эффект от действий? Эффективность

Итак, это была преждевременная оптимизация или нет?

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

Любое изменение кода, выполненное до этапа № 3, является преждевременным. Я не совсем уверен, как классифицировать варианты дизайна, сделанные до этого (например, использование хорошо подходящих структур данных), но я предпочитаю ориентироваться на использование абстракций, с которыми легко программировать, а не с теми, которые хорошо работают, пока я не достигну этап, на котором я могу начать использовать профилирование и иметь правильную (хотя и часто медленную) эталонную реализацию для сравнения результатов.

С точки зрения базы данных, не принимать во внимание оптимальный дизайн на этапе проектирования, в лучшем случае безрассудно. Базы данных не легко рефакторинг. Как только они плохо спроектированы (это то, что дизайн, который не учитывает оптимизацию, независимо от того, как вы можете попытаться скрыться за бессмысленной преждевременной оптимизацией), почти никогда не сможет восстановиться после этого, потому что база данных слишком базова для работа всей системы. Гораздо менее затратно спроектировать правильно с учетом оптимального кода для ожидаемой ситуации, чем ждать, пока миллионы пользователей и люди кричат, потому что вы использовали курсоры во всем приложении. Другие оптимизации, такие как использование sargeable кода, выбор наилучших возможных индексов и т. Д., Имеют смысл делать только во время разработки. Есть причина, почему быстрая и грязная так называется. Потому что он никогда не работает хорошо, поэтому не используйте быстроту вместо хорошего кода. Также, откровенно говоря, когда вы понимаете настройку производительности в базах данных, вы можете написать код, который с большей вероятностью будет работать хорошо в то же время или меньше, чем требуется для написания кода, который не работает хорошо. Не тратить время на изучение того, что является хорошим дизайном базы данных, - это лень для разработчиков, а не лучшая практика.

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

Тип оптимизации, о которой говорит правило Кнута, заключается в минимизации длины наиболее распространенных путей кода, оптимизации кода, который выполняется чаще всего, например, переписыванием в сборке или упрощением кода, что делает его менее общим. Но делать это бесполезно до тех пор, пока вы не будете уверены, какие части кода нуждаются в такого рода оптимизации, и оптимизация сделает (может?) Затруднить понимание или поддержание кода, следовательно, "преждевременная оптимизация - корень всего зла".

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

В ответ на множество других комментариев, размещенных на этот вопрос: алгоритм выбора!= Оптимизация

Суть максима в том, что, как правило, оптимизация является запутанной и сложной. И, как правило, вы, архитектор / дизайнер / программист / сопровождающий, должны иметь четкий и лаконичный код, чтобы понять, что происходит.

Если конкретная оптимизация ясна и лаконична, не стесняйтесь экспериментировать с ней (но вернитесь и проверьте, эффективна ли эта оптимизация). Суть в том, чтобы код оставался ясным и лаконичным на протяжении всего процесса разработки, пока преимущества производительности не перевесят предполагаемые затраты на написание и поддержку оптимизаций.

Оптимизация может происходить на разных уровнях детализации, от очень высокого до очень низкого уровня:

  1. Начните с хорошей архитектуры, слабой связи, модульности и т. Д.

  2. Выберите правильные структуры данных и алгоритмы для задачи.

  3. Оптимизируйте для памяти, пытаясь разместить больше кода / данных в кеше. Подсистема памяти работает в 10–100 раз медленнее, чем центральный процессор, а если ваши данные переносятся на диск, это в 1000–10 000 раз медленнее. Осторожность в отношении потребления памяти, скорее всего, даст большую выгоду, чем оптимизация отдельных инструкций.

  4. Внутри каждой функции используйте соответствующие операторы управления потоком. (Переместите неизменяемые выражения за пределы тела цикла. Сначала укажите наиболее распространенное значение в переключателе / ​​регистре и т. Д.)

  5. В каждом утверждении используйте наиболее эффективные выражения, дающие правильный результат. (Умножение против сдвига и т. Д.)

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

И, конечно, любая оптимизация преждевременна, если вы не определите порог достижения цели.

В большинстве случаев либо:

A) Вы можете достичь целевого порога производительности, выполняя высокоуровневые оптимизации, поэтому нет необходимости возиться с выражениями.

или же

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

По моему опыту, большинство проблем оптимизации могут быть решены на уровне архитектуры / дизайна или структуры данных / алгоритма. Часто требуется (хотя и не всегда) оптимизация для использования памяти. Но редко необходимо оптимизировать логику управления потоком и выражения. И в тех случаях, когда это действительно необходимо, этого редко бывает достаточно.

При программировании необходим ряд параметров. Среди них:

  • читабельность
  • Ремонтопригодность
  • сложность
  • прочность
  • правильность
  • Спектакль
  • Время разработки

Оптимизация (ориентируясь на производительность) часто происходит за счет других параметров и должна быть сбалансирована с "потерями" в этих областях.

Когда у вас есть возможность выбрать хорошо известные алгоритмы, которые работают хорошо, затраты на "оптимизацию" авансом часто приемлемы.

Я пытаюсь оптимизировать только тогда, когда проблема с производительностью подтверждена.

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

При написании кода (или запроса к БД) я стремлюсь написать "эффективный" код (т. Е. Код, который выполняет свою предназначенную функцию быстро и полностью с простейшей разумной логикой). Обратите внимание, что "эффективный" код не обязательно совпадает с "оптимизированным" код. Оптимизация часто вносит дополнительную сложность в код, что увеличивает как стоимость разработки, так и обслуживания этого кода.

Мой совет: старайтесь оплачивать расходы на оптимизацию только тогда, когда вы можете количественно оценить выгоду.

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

Я думаю, что "преждевременная оптимизация" невероятно субъективна.

Если я пишу какой-то код и знаю, что должен использовать Hashtable, я это сделаю. Я не буду реализовывать его каким-то некорректным образом, а затем подожду, пока отчет об ошибке появится через месяц или год, когда у кого-то возникнут проблемы с ним.

Редизайн обходится дороже, чем оптимизация дизайна очевидными способами с самого начала.

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

Поэтому: НЕ оптимизировать дизайн - это IMO, запах кода сам по себе.

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

Например, чтобы добавить в список Нормана:

  • Использование конкатенации StringBuilder в Java (или C# и т. Д.) Вместо String + String (в цикле);
  • Избегать зацикливания в C, как: for (i = 0; i < strlen(str); i++) (потому что здесь strlen - это вызов функции, каждый раз проходящий строку, вызываемый в каждом цикле);
  • Кажется, в большинстве реализаций JavaScript это сделать тоже быстрее for (i = 0 l = str.length; i < l; i++) и это все еще читабельно, так что все в порядке.

И так далее. Но такие микрооптимизации никогда не должны идти за счет читабельности кода.

Стоит отметить, что оригинальная цитата Кнута была взята из статьи, которую он написал, пропагандирующей использование goto в тщательно отобранных и измеренных областях как способ устранения горячих точек. Его цитата была оговоркой, которую он добавил, чтобы оправдать свое обоснование использования goto чтобы ускорить эти критические петли.

[...] опять же, это заметная экономия в общей скорости бега, если, скажем, среднее значение n составляет около 20, и если процедура поиска выполняется около миллиона раз в программе. Такие циклы оптимизации [с использованием gotos ] не сложны в освоении, и, как я уже сказал, они уместны лишь в небольшой части программы, но они часто дают существенную экономию. [...]

И продолжает:

Общепринятая мудрость, разделяемая многими современными разработчиками программного обеспечения, предусматривает игнорирование эффективности в малом; но я полагаю, что это просто чрезмерная реакция на злоупотребления, которые, как они видят, практикуются глупыми программистами, которые не могут отлаживать или поддерживать свои "оптимизированные" программы. В устоявшихся инженерных дисциплинах легко достижимое улучшение на 12% никогда не считается незначительным; и я считаю, что та же точка зрения должна преобладать в разработке программного обеспечения. Конечно, я бы не стал делать такую ​​оптимизацию на одноразовой работе, но когда речь идет о подготовке качественных программ, я не хочу ограничиваться инструментами, которые лишают меня такой эффективности [т.е. goto заявления в этом контексте.

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

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

... а затем еще немного о важности инструментов профилирования:

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

Люди неправильно использовали его цитату повсеместно, часто полагая, что микрооптимизации преждевременны, когда вся его статья защищала микрооптимизации! Одна из групп людей, которых он критиковал, повторяет эту "общепринятую мудрость", поскольку он всегда игнорирует эффективность в малом, часто неправильно использует свою цитату, которая первоначально была направлена, в частности, против таких типов, которые препятствуют всем формам микрооптимизации,

Тем не менее, это была цитата в пользу надлежащим образом примененных микрооптимизаций, когда использовалась опытная рука, держащая профилировщик. Сегодняшний аналог может выглядеть так: "Люди не должны слепо пытаться оптимизировать свое программное обеспечение, но пользовательские распределители памяти могут иметь огромное значение при применении в ключевых областях для улучшения местоположения ссылки", или " Рукописный код SIMD с использованием Репутацию SoA действительно сложно поддерживать, и вы не должны использовать ее повсеместно, но она может потреблять память намного быстрее, если ее применять соответствующим образом опытная и управляемая рука ".

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

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

Если вы не вписываетесь в категорию "безумно и безумно", то вы не преждевременно оптимизируете по стандартам Кнута, даже если вы используете goto чтобы ускорить критический цикл (что вряд ли сильно поможет сегодняшним оптимизаторам, но если это произойдет, и в действительно критической области, то вы не будете преждевременно оптимизировать). Если вы на самом деле применяете все, что вы делаете, в областях, которые действительно необходимы, и они действительно извлекают из этого выгоду, то в глазах Кнута у вас все просто замечательно.

Как я писал в аналогичном вопросе, правила оптимизации:

1) не оптимизировать

2) (только для экспертов) Оптимизация позже

Когда оптимизация преждевременна? Обычно.

Исключение возможно в вашем дизайне или в хорошо инкапсулированном коде, который интенсивно используется. В прошлом я работал над некоторым критичным по времени кодом (реализация RSA), где просмотр ассемблера, который создал компилятор, и удаление одной ненужной инструкции во внутреннем цикле дало ускорение на 30%. Но ускорение от использования более сложных алгоритмов было на несколько порядков больше.

Другой вопрос, который нужно задать себе при оптимизации: "Я делаю эквивалент оптимизации для модема на 300 бод здесь?", Другими словами, закон Мура сделает вашу оптимизацию неактуальной в ближайшее время. Многие проблемы масштабирования можно решить, просто добавив больше оборудования.

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

редактировать: кстати, касательно связанной статьи, я бы поставил под сомнение многие из сделанных предположений. Во-первых, это не правда, что закон Мура перестал работать в 90-х годах. Во-вторых, не очевидно, что время пользователя более ценно, чем время программиста. Большинство пользователей (мягко говоря) не отчаянно используют каждый доступный цикл ЦП, они, вероятно, ожидают, что сеть что-то предпримет. Плюс к этому есть альтернативные издержки, когда время программиста отвлекается от реализации чего-то другого, чтобы сэкономить несколько миллисекунд на том, что делает программа, пока пользователь разговаривает по телефону. Все, что больше, чем обычно, не является оптимизацией, это исправление ошибок.

Если вы не обнаружите, что вам требуется более высокая производительность вашего приложения из-за потребностей пользователя или бизнеса, нет оснований беспокоиться об оптимизации. Даже тогда не делайте ничего, пока не профилируете свой код. Затем атакуйте части, которые занимают больше всего времени.

Я не думаю, что признанные лучшие практики являются преждевременными оптимизациями. Это больше о потере времени на то, что если есть потенциальные проблемы с производительностью в зависимости от сценариев использования. Хороший пример: если вы сжигаете неделю, пытаясь оптимизировать отражение над объектом, прежде чем у вас есть доказательства того, что это узкое место, вы преждевременно оптимизируете.

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

Я вижу это так: если вы оптимизируете что-то, не зная, какую производительность вы можете получить в другом сценарии, это преждевременная оптимизация. Цель кода должна действительно облегчить чтение человеком.

Другие вопросы по тегам