Почему многие разработчики считают, что производительность, удобочитаемость и удобство обслуживания не могут сосуществовать?


34

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

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

Хотя я признаю, что существуют крайние случаи, почему многие разработчики настаивают на том, что эффективная программа / дизайн приведет к плохой читабельности и / или плохой ремонтопригодности, и, следовательно, производительность не должна учитываться при проектировании?


9
Было бы почти невозможно рассуждать об этом в большом масштабе, но для небольших фрагментов кода это вполне очевидно. Просто сравните удобочитаемые и эффективные версии, скажем, быстрой сортировки.
SK-logic

7
Mu. Вам следует начать с поддержки вашего заявления о том, что многие разработчики настаивают на том, что эффективность ведет к невозможности обслуживания.
Питер Тейлор

2
SK-logic: На мой взгляд, это одна из лучших частей всех сайтов по обмену стеками, поскольку каждый может подвергнуть сомнению очевидное, которое может время от времени быть полезным. То, что может быть очевидным для вас, может не быть очевидным для кого-то другого, и наоборот. :) Обмен это забота.
Андреас Йоханссон

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

2
-1 за вопрос. Когда я читал это, я думал, что это соломенный человек, чтобы выселить единственный верный ответ: «Потому что они не используют Python».
Инго

Ответы:


38

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

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


8
«Когда кто-то пытается противостоять таким взглядам, легко впасть - или, по крайней мере, выглядеть - в другую крайность». У меня постоянно возникают проблемы с людьми, которые думают, что придерживаются противоположной точки зрения, когда я просто уравновешиваю плюсы с минусы Не только в программировании, во всем.
Джоккинг

1
Мне так надоело, что все обсуждают это, что я злюсь и впадаю в крайности ..
Томас Бонини

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

Мой ответ ... большинство разработчиков плохо
справляются

38

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

  • Не преждевременно пессимизировать. Если у вас есть выбор между двумя одинаковыми по сложности дизайнами, выберите тот, который имеет наилучшие характеристики производительности. Одним из известных примеров C ++ является распространенность постинкрементных счетчиков (или итераторов) в циклах. Это совершенно ненужная преждевременная пессимизация, которая МОЖЕТ не стоить вам ничего, но МОЖЕТ, так что не делайте этого.
  • Во многих случаях у вас нет бизнеса, чтобы идти к микрооптимизации. Алгоритмическая оптимизация - это менее важный плод, и почти всегда ее гораздо легче понять, чем действительно низкоуровневую оптимизацию.
  • Если и ТОЛЬКО если производительность абсолютно критична, вы падаете и пачкаетесь. На самом деле, сначала вы изолируете код настолько, насколько можете, и затем вы падаете и пачкаетесь. И это становится очень грязным, со схемами кэширования, отложенной оценкой, оптимизацией макета памяти для кэширования, встроенными внутренними компонентами или сборками, послойными шаблонами и т. Д. Здесь вы тестируете и документируете как сумасшедшие, вы знаете, что это происходит Это может повредить, если вам придется выполнять какое-либо обслуживание в этом коде, но вы должны это делать, потому что производительность абсолютно необходима. Изменить: Кстати, я не говорю, что этот код не может быть красивым, и он должен быть сделан настолько красивым, насколько это возможно, но он все равно будет очень сложным и часто запутанным по сравнению с менее оптимизированным кодом.

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


Мне нравится эмпирическое правило: сделай это красиво, сделай это быстро. В этой последовательности'. Я собираюсь начать использовать это.
Мартин Йорк,

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

@KeithB - ​​вы делаете хорошее замечание, я добавлю его в свой ответ.
Джорис Timmermans

+1: «Сделай это правильно, сделай это красиво, сделай это быстро. В таком порядке». Очень хорошее резюме, с которым я согласен на 90%. Иногда я могу исправить только некоторые ошибки (сделать это правильно), как только я сделаю это красиво (и более понятным).
Джорджио

+1 за "Не преждевременно пессимизировать". Совет избегать преждевременной оптимизации - не разрешать бессмысленно использовать алгоритмы с тупыми головами. Если вы пишете на Java, и у вас есть коллекция, к которой вы будете часто обращаться contains, используйте a HashSet, а не an ArrayList. Производительность может не иметь значения, но нет причин не делать этого. Используйте соответствие между хорошим дизайном и производительностью - если обрабатываете какую-то коллекцию, попробуйте сделать все за один проход, который, вероятно, будет и более читабельным, и быстрее (вероятно).
Том Андерсон

16

Если я могу позволить себе «позаимствовать» красивую диаграмму @ greengit и сделать небольшое дополнение:

|
P
E
R
F
O  *               X <- a program as first written
R   * 
M    *
A      *
N        *
C          *  *   *  *  *
E
|
O -- R E A D A B I L I T Y --

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

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


Лично я думаю, что есть другой конец кривой, где он снова поднимается с правой стороны (пока вы двигаетесь достаточно далеко вправо (что, вероятно, означает переосмысление вашего алгоритма)).
Мартин Йорк,

2
+1 за «В большинстве программ есть много возможностей для улучшения во всех измерениях».
Стивен

5

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

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

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

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

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


3

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


2
    |
    п
    Е
    р
    F
    O *
    Р * 
    М *
    A *
    N *
    C * * * * *
    Е
    |
    O - READABILITY -

Как вы видете...

  • Нарушение читабельности может повысить производительность - но только настолько. После определенного момента вам приходится прибегать к «реальным» средствам, таким как лучшие алгоритмы и аппаратные средства.
  • Кроме того, потеря производительности за счет читабельности может произойти только в некоторой степени. После этого вы можете сделать вашу программу максимально удобной для чтения, не влияя на производительность. Например, добавление более полезных комментариев не влияет на производительность.

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


1

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


1

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

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

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

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


1

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

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


1

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

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

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

ДОБАВЛЕНО: В древние времена стоимость одного компьютера составляла миллионы, поэтому оптимизация времени процессора была очень важной. Затем стоимость разработки и обслуживания кода стала больше, чем стоимость компьютеров, поэтому оптимизация оказалась не в пользу по сравнению с производительностью программистов. Теперь, однако, другая стоимость становится больше, чем стоимость компьютеров, стоимость питания и охлаждения всех этих центров обработки данных в настоящее время становится больше, чем стоимость всех процессоров внутри.


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

1

Я думаю, что трудно достичь всех трех. Два, я думаю, могут быть осуществимы. Например, я думаю, что в некоторых случаях возможно достичь эффективности и читабельности, но с микро-настройкой кода может быть затруднено сопровождение. Самый эффективный код на планете, как правило, испытывает недостаток как в удобстве сопровождения, так и в удобочитаемости, что, вероятно, очевидно для большинства, если только вы не из тех, кто понимает ручной векторизованный SoA-код многопоточного SIMD-кода, который Intel пишет со встроенной сборкой, или самый режущий Алгоритмы, используемые в промышленности, с 40-страничными математическими работами, опубликованными всего 2 месяца назад, и 12 библиотеками кода для одной невероятно сложной структуры данных.

Micro-Efficiency

Я бы сказал, что это может противоречить распространенному мнению, что самый умный алгоритмический код зачастую сложнее поддерживать, чем самый простой микро-настраиваемый алгоритм. Эту идею о том, что улучшения масштабируемости дают больший эффект от микротонизированного кода (например, шаблоны доступа с поддержкой кэша, многопоточность, SIMD и т. Д.), Я бы оспорил, по крайней мере, работая в отрасли, наполненной чрезвычайно сложным структуры данных и алгоритмы (индустрия визуальных эффектов), особенно в таких областях, как обработка сеток, потому что эффект может быть большим, но доллар стоит очень дорого, когда вы вводите новые алгоритмы и структуры данных, о которых никто никогда раньше не слышал, поскольку они являются брендом новый. Далее я

Так что идея о том, что алгоритмическая оптимизация всегда превосходит, скажем, оптимизацию, связанную с шаблонами доступа к памяти, всегда была чем-то, с чем я не совсем согласился. Конечно, если вы используете пузырьковую сортировку, никакая микрооптимизация не поможет вам в этом ... но в разумных пределах я не думаю, что это всегда так ясно. И, возможно, алгоритмические оптимизации сложнее поддерживать, чем микрооптимизации. Я бы обнаружил, что гораздо проще поддерживать, скажем, Intel Embree, который использует классический и простой алгоритм BVH и просто настраивает из него дерьмо, чем код Dreamwork OpenVDB для передовых способов алгоритмического ускорения симуляции жидкости. Так что, по крайней мере, в моей отрасли я хотел бы, чтобы больше людей знакомо с микро-оптимизацией компьютерной архитектуры, как Intel, когда они вышли на сцену, в отличие от тысяч и тысяч новых алгоритмов и структур данных. Благодаря эффективной микрооптимизации люди могут найти все меньше и меньше причин для изобретения новых алгоритмов.

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

Функциональные языки

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

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

Удобочитаемость и ремонтопригодность

Как я уже сказал, я считаю, что удобочитаемость и ремонтопригодность не являются гармоничными понятиями. В конце концов, наиболее читаемый код для большинства из нас, смертных, очень интуитивно отображает шаблоны человеческого мышления, а шаблоны человеческого мышления по своей природе подвержены ошибкам: « Если это происходит, делайте это. Если это происходит, делайте это. В противном случае сделайте это. Я что-то забыл! Если эти системы взаимодействуют друг с другом, это должно произойти так, чтобы эта система могла сделать это ... о, подождите, а как насчет этой системы, когда происходит это событие?«Я забыл точную цитату, но кто-то однажды сказал, что если бы Рим был построен как программное обеспечение, то для того, чтобы свалить его, понадобилась бы только посадка птицы на стену. Так обстоит дело с большинством программного обеспечения. Это более хрупко, чем мы часто заботимся Подумайте. Несколько строк, казалось бы, безобидного кода здесь и там могут остановить его до такой степени, что заставят нас пересмотреть весь дизайн, и языки высокого уровня, которые стремятся быть максимально удобочитаемыми, не являются исключением из таких ошибок человеческого дизайна. ,

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


1
«Микроэффективность» - это как сказать «Нет доступа к памяти O (1)»
Caleth

0

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

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


Очевидно, что вы можете посвятить больше времени, но в конечном итоге вы начинаете сомневаться, почему разработчики отнимают время на программировании Emacs, чтобы выразить любовь к своим детям, и в этот момент вы в основном Шелдон из Теории Большого взрыва
deworde

0

Потому что опытные программисты узнали, что это правда.

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

Мы работали над большим количеством кода, который для решения проблем производительности ОЧЕНЬ сложен.

Один из непосредственных примеров, который приходит мне в голову, - это то, что мой последний проект включал 8192 таблицы SQL, разделенных вручную. Это было необходимо из-за проблем с производительностью. Настроить выбор из 1 таблицы намного проще, чем выбрать и сохранить 8 192 шарда.


0

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

Вот самый известный, как мне кажется. Взято с Quake III Arena и приписано Джону Кармаку, хотя я думаю, что было несколько итераций этой функции, и она изначально не была создана им ( разве Википедия не хороша? ).

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the fuck?
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
    //      y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.