Повлиял ли постепенный сдвиг в методологии написания кода на производительность системы? И мне все равно?

35

TD; DR:

Был некоторый беспорядок относительно того, что я спрашивал, таким образом, вот движущая идея позади вопроса:

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

И вопрос не о ...

не о микрооптимизации, преждевременной оптимизации и т. д. Речь идет не о «оптимизировать ту или иную часть до смерти».

Что тогда?

Речь идет об общей методологии, методах и способах мышления о написании кода, появившегося со временем:

  • «внедрить этот код в ваш класс как зависимость»
  • "написать один файл на класс"
  • msgstr "отделить ваше представление от вашей базы данных, контроллера, домена".
  • не пишите спагетти однородный одиночный кодовый блок, но пишите много отдельных модульных компонентов, которые работают вместе

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

Исходный вопрос следует:

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

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

Это изменение по сравнению со «старыми», «устаревшими» или «спагетти» методами кодирования, когда у вас есть методы, охватывающие 2500 строк, а большие классы и объекты бога делают все.

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

когда вызов сводится к машинному коду, к 1 и 0, к инструкциям по сборке, к пластинам жесткого диска, должен ли я вообще быть обеспокоен тем, что мой совершенно разнесенный по классам ОО-код с множеством переработанных мелких и маленьких функций тоже генерирует много лишних накладных расходов?

Детали

Хотя я не очень хорошо знаком с тем, как в конце концов ASO-код и его вызовы обрабатываются в ASM, и как вызовы DB и вызовы компилятора преобразуются в перемещение рычага привода на жестком диске, у меня есть некоторые идеи. Я предполагаю, что каждый дополнительный вызов функции, вызов объекта или вызов "#include" (в некоторых языках) генерируют дополнительный набор инструкций, тем самым увеличивая объем кода и добавляя различные накладные расходы "связывания кода", без добавления фактического "полезного" кода , Я также полагаю, что хорошая оптимизация может быть сделана для ASM до того, как он будет фактически запущен на оборудовании, но эта оптимизация может сделать слишком много.

Следовательно, мой вопрос - сколько накладных расходов (в пространстве и скорости) делает хорошо разделенный код (код, разбитый на сотни файлов, классов и шаблонов проектирования и т. Д.), По сравнению с наличием «одного большого метода, который содержит все в одном монолитном файле ", из-за этого накладные расходы?

ОБНОВЛЕНИЕ для ясности:

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

Где-то было сказано (цитата TBD), что до 70% всего кода составлено из инструкции ASM MOV - загрузка регистров ЦП с соответствующими переменными, а не фактические выполняемые вычисления. В моем случае вы загружаете процессорное время инструкциями PUSH / POP, чтобы обеспечить связь и передачу параметров между различными частями кода. Чем меньше вы делаете свои куски кода, тем больше накладных расходов требуется. Я обеспокоен тем, что эта связь увеличивает раздувание и замедление работы программного обеспечения, и мне интересно, стоит ли мне беспокоиться об этом и насколько сильно, если вообще нужно, потому что нынешнее и будущие поколения программистов, которые создают программное обеспечение для следующего столетия , придется жить и использовать программное обеспечение, созданное с использованием этих методов.

ОБНОВЛЕНИЕ: несколько файлов

Сейчас я пишу новый код, который медленно заменяет старый код. В частности, я заметил, что одним из старых классов был файл из ~ 3000 строк (как упоминалось ранее). Теперь он становится набором из 15-20 файлов, расположенных в разных каталогах, включая тестовые файлы и не включая PHP-фреймворк, который я использую, чтобы связать некоторые вещи вместе. Также появятся и другие файлы. Когда дело доходит до дискового ввода-вывода, загрузка нескольких файлов происходит медленнее, чем загрузка одного большого файла. Конечно, загружаются не все файлы, они загружаются по мере необходимости, и существуют опции кэширования на диске и памяти, но, тем не менее, я считаю, что это loading multiple filesтребует больше обработки, чем loading a single fileпамяти. Я добавляю это к моей проблеме.

ОБНОВЛЕНИЕ: Зависимость Внедрить все

Возвращаясь к этому через некоторое время ... Я думаю, что мой вопрос был неправильно понят. Или, возможно, я решил неправильно понять некоторые ответы. Я говорю не о микрооптимизации, поскольку некоторые ответы выделены (по крайней мере, я думаю, что называть то, что я говорю о микрооптимизации, является неправильным), а о движении «кода рефакторинга для ослабления тесной связи» в целом на каждом уровне кода. Я пришел из Zend Con совсем недавно, где этот стиль кода был одним из ключевых пунктов и центральных элементов конвенции. Отсоедините логику от представления, от представления модели, модели от базы данных и, если можете, отсоедините данные от базы данных. Dependency-Inject все, что иногда означает просто добавление кода проводки (функции, классы, шаблон), который ничего не делает, но служит точкой шва / крючка, в большинстве случаев легко удваивая размер кода.

ОБНОВЛЕНИЕ 2. Влияет ли «разделение кода на несколько файлов» на производительность (на всех уровнях вычислений)

Как compartmentalize your code into multiple filesвлияет философия сегодняшних вычислений (производительность, использование диска, управление памятью, задачи обработки процессора)?

Я говорю о

До...

В гипотетическом, но вполне реальном, не столь далеком прошлом, вы могли бы легко написать один моноблок файла, который имеет модель и представление и спагетти контроллера или не-спагетти-кодирован, но который запускает все, как только он уже загружен. Выполняя некоторые тесты в прошлом с использованием кода на языке C, я обнаружил, что загружать один файл объемом 900 МБ в память и обрабатывать его большими кусками НАМНОГО быстрее, чем загружать кучу файлов меньшего размера и обрабатывать их с меньшими затратами времени. куски делают ту же самую работу в конце.

.. И сейчас*

Сегодня я смотрю на код, который показывает бухгалтерскую книгу, которая имеет такие особенности, как ... если элемент представляет собой «порядок», показывает блок HTML «показать порядок». Если можно скопировать элемент строки, распечатайте блок HTML, который отображает значок и параметры HTML, позволяющие сделать копию. Если элемент можно переместить вверх или вниз, отобразите соответствующие стрелки HTML. И т.д. могу, через Zend Framework создатьpartial()звонки, что по сути означает «вызов функции, которая берет ваши параметры и вставляет их в отдельный HTML-файл, который он также вызывает». В зависимости от того, насколько подробно я хочу получить, я могу создать отдельные функции HTML для самых маленьких частей бухгалтерской книги. Один для стрелка вверх, стрелка вниз, один для «я могу скопировать этот элемент» и т. Д. Легко создавать несколько файлов только для отображения небольшой части веб-страницы. Взяв мой код и закулисный код Zend Framework, система / стек, вероятно, вызывает около 20-30 различных файлов.

Какая?

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

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

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

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

Использование Zend Form API против простого HTML

Я использовал Zend Form API с последними и лучшими современными методами OO, чтобы создать HTML-форму с проверкой, трансформируясь POSTв доменные объекты.

Мне потребовалось 35 файлов, чтобы сделать это.

35 files = 
    = 10 fieldsets x {programmatic fieldset + fieldset manager + view template} 
    + a few supporting files

Все они могут быть заменены несколькими простыми файлами HTML + PHP + JS + CSS, возможно, всего 4 облегченными файлами.

Это лучше? Стоит ли? ... Представьте, что вы загружаете 35 файлов + многочисленные файлы библиотеки Zend Zramework, которые заставляют их работать, против 4 простых файлов.

Деннис
источник
1
Внушительный вопрос. Я сделаю несколько бенчмаркингов (возьмите у меня день или около того, чтобы найти хорошие примеры). Однако повышение скорости в этой степени приводит к огромным затратам на удобочитаемость и затраты на разработку. Мое первоначальное предположение - результат незначительного прироста производительности.
Дэн Сабин
7
@Dan: Не могли бы вы добавить его в свой календарь, чтобы сравнить код после 1, 5 и 10 лет обслуживания. Если я помню, я проверю результаты :)
mattnz
1
Да, это настоящий кикер. Я думаю, что мы все согласны делать меньше поисков и быстрее вызывать функции. Однако я не могу представить себе случай, когда это было бы предпочтительнее таких вещей, как удобство и простота обучения новых членов команды.
Дэн Сабин
2
Чтобы уточнить, есть ли у вас конкретные требования к скорости для вашего проекта. Или вы просто хотите, чтобы ваш код работал быстрее! Если бы это было последнее, я бы об этом не беспокоился, код, который достаточно быстр, но прост в обслуживании, гораздо лучше, чем код, который быстрее, чем достаточно быстр, но беспорядок.
Кормак Мулхолл
4
Идея избегать вызовов функций по соображениям производительности - именно та сумасшедшая мысль, против которой Дейкстра высказался в своей знаменитой цитате о преждевременной оптимизации. Серьезно, я не могу
Рибальд Эдди

Ответы:

10

Мой вопрос заключается в следующем: когда вызов сводится к машинному коду, к 1 или 0, к инструкциям по сборке, должен ли я вообще быть обеспокоен тем, что мой код, разделенный на классы, с множеством функций от маленького до крошечного генерирует слишком много дополнительных издержек?

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

Например, я работал в месте, где мне показали эталонный проект для веб-сервиса с 1 методом. Проект состоял из 32 файлов .cs - для одного веб-сервиса! Я полагал, что это было слишком сложно, хотя каждая часть была крошечной и легко понятной сама по себе, когда дело дошло до описания всей системы, я быстро обнаружил, что должен проследить через вызовы, просто чтобы посмотреть, какого черта она делает (там Также было задействовано слишком много абстракций, как и следовало ожидать). Мой заменяющий веб-сервис был 4 .cs файлами.

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

Где-то было сказано (цитата TBD), что до 70% всего кода составлено из инструкции ASM MOV - загрузка регистров ЦП с соответствующими переменными, а не фактические выполняемые вычисления.

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

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

gbjbaanb
источник
Я думаю, что это говорит мне больше всего прямо сейчас. Это говорит мне о том, что хорошим промежуточным положением является начинать с концепций отображения разума для фрагментов кода, следуя и используя определенные концепции (например, DI), вместо того, чтобы связываться с эзотерической декомпозицией кода и сходить с ума (т.е. DI - все, что вам нужно). это или нет).
Деннис
1
Лично я нахожу, что более «современный» код несколько легче профилировать ... Я думаю, чем более обслуживаемый фрагмент кода, тем проще профилировать, но есть предел, при котором деление на мелкие кусочки делает их менее ремонтопригодны ...
AK_
25

Без особой осторожности микрооптимизация, такая как эти проблемы, приводит к не поддерживаемому коду.

Изначально это выглядит как хорошая идея, профилировщик говорит вам, что код работает быстрее, а V & V / Test / QA даже говорит, что работает. Вскоре обнаруживаются ошибки, запрашиваются изменения и улучшения, которые никогда не рассматривались.

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

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

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

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

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

mattnz
источник
Thankx. Чтобы немного продвинуться в этом направлении, я говорю не о микрооптимизации, а о глобальном переходе к написанию меньшего кода, включающему большее внедрение зависимостей и, следовательно, больше внешних функциональных частей и больше «движущихся частей» кода в целом. что все они должны быть связаны друг с другом, чтобы они работали. Я склонен думать, что это производит больше пуха связи / соединителя / передачи переменных / MOV / PUSH / POP / CALL / JMP на аппаратном уровне. Я также вижу ценность сдвига в сторону читабельности кода, хотя и за счет потери вычислительных циклов на аппаратном уровне для «пуха».
Деннис
10
Избегать вызовов функций по причинам производительности - это абсолютно микрооптимизация! Шутки в сторону. Я не могу придумать лучшего примера микрооптимизации. Какие у вас есть доказательства того, что разница в производительности действительно имеет значение для типа программного обеспечения, которое вы пишете? Похоже, у вас их нет.
Рибальд Эдди
17

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

Сайт подводит итоги - нет простого ответа . Это зависит от системы, зависит от того, что делает ваше приложение, зависит от языка, зависит от компилятора и оптимизации.

C ++, например, inline может повысить производительность. Много раз это могло ничего не делать или, возможно, снижать производительность, но лично я никогда не сталкивался с этим, хотя слышал об историях. Inline - это не более чем предложение компилятору оптимизировать, что можно игнорировать.

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

Но в программах более низкого уровня, где программист может выполнять много машинного кода вместе с чем-то вроде C ++, случайные изменения могут иметь решающее значение. Игры являются хорошим примером того, где конвейерная обработка ЦП имеет решающее значение, особенно на консолях, и что-то вроде inline может немного добавить кое-где.

Хорошее чтение, в частности, на сайте: http://www.gotw.ca/gotw/033.htm

user99999991
источник
Исходя из перспективы моего вопроса, я сконцентрирован не столько на инлайнировании, сколько на «кодированной проводке», которая требует времени процессора, шины и времени ввода-вывода, связывающих различные куски кода. Интересно, есть ли какой-то момент, когда 50% или больше кода проводки и 50% фактического кода вы хотите запустить. Я полагаю, что даже в самом трудном коде можно написать много ошибок, и это, кажется, факт жизни. Большая часть реального кода, который выполняется на уровне битов и байтов, - это логистика - перемещение значений из одного места в другое, переход в одно место или другое и только иногда добавление ..
Деннис
... вычитание или другие связанные с бизнесом функции. Точно так же, как развертывание цикла может ускорить некоторые циклы из-за меньших накладных расходов, выделяемых для приращения переменных, написание более крупных функций, вероятно, может добавить некоторую скорость, если для этого настроен ваш сценарий использования. Моя проблема здесь более общая: я вижу множество советов по написанию небольших кусочков кода, которые расширяют эту разводку, а также обеспечивают удобство чтения (надеюсь) и за счет раздувания на микроуровне.
Деннис
3
@Dennis - одна вещь, которую следует учитывать, заключается в том, что в ОО-языке может быть ОЧЕНЬ небольшая корреляция между тем, что пишет программист (a + b), и тем, что генерируется код (простое добавление двух регистров? Сначала идет из памяти? затем вызовы функций в оператор объекта +?). Таким образом, «маленькие функции» на уровне программиста могут быть чем угодно, только не маленькими, когда они представлены в машинном коде.
Майкл Кохне
2
@ Денис Я могу сказать, что написание кода ASM (напрямую, не скомпилированного) для Windows происходит по принципу «mov, mov, invoke, mov, mov, invoke». С помощью invoke, являющегося макросом, который выполняет вызов, заключенный в pushes / pops ... Иногда вы будете выполнять вызовы функций в своем собственном коде, но он затмевается всеми вызовами ОС.
Брайан Кноблаух
12

Это изменение по сравнению со «старой» или «плохой» практикой кода, когда у вас есть методы, охватывающие 2500 строк, и большие классы делают все.

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

Я думаю, что знаменитая цитата от Дональда Кнута очень актуальна здесь:

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

Таким образом, в 97% вашего кода, просто используйте передовой опыт, пишите небольшие методы (насколько мелким является вопрос мнения, я не думаю, что все методы должны занимать всего несколько строк) и т. Д. Для остальных 3%, где производительность имеет значение, измерить это. И если измерения показывают, что наличие множества маленьких методов на самом деле значительно замедляет ваш код, то вам следует объединить их в более крупные методы. Но не пишите не поддерживаемый код только потому, что он может быть быстрее.

svick
источник
11

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

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

  • Предположение, что любая строка кода занимает примерно столько же времени, сколько и любая другая. Например cout << endlпротив a = b + c. Первый занимает в тысячи раз больше, чем второй. У Stackexchange много вопросов в форме: «Я пробовал разные способы оптимизации этого кода, но, похоже, это не имеет значения, почему бы и нет?» когда есть большой старый вызов функции в середине этого.

  • Предположение о том, что любой вызов функции или метода после его написания, безусловно, необходимо. Функции и методы легко вызывать, и вызов обычно довольно эффективен. Проблема в том, что они похожи на кредитные карты. Они соблазняют вас тратить больше, чем вы действительно хотите, и они склонны скрывать то, что вы потратили. Вдобавок к этому, большое программное обеспечение имеет слои на уровнях абстракции, поэтому, даже если на каждом слое есть только 15% отходов, более 5 слоев, которые составляют фактор замедления 2. Ответ на это не состоит в том, чтобы удалить функциональность или записать большие функции, это дисциплинировать себя, чтобы быть настороже для этой проблемы и быть готовым и способным устранить ее .

  • Скачущая общность. Ценность абстракции в том, что она позволяет вам делать больше с меньшим количеством кода - по крайней мере, это надежда. Эта идея может быть доведена до крайности. Проблема со слишком большой общностью заключается в том, что каждая проблема является специфической, и когда вы решаете ее с помощью общих абстракций, эти абстракции не обязательно могут использовать конкретные свойства вашей проблемы. Например, я видел ситуацию, когда класс очереди с причудливыми приоритетами, который мог бы быть эффективным при больших размерах, использовался, когда длина никогда не превышала 3!

  • Скачущая структура данных. ООП - очень полезная парадигма, но она не побуждает к минимизации структуры данных, а скорее побуждает пытаться скрыть всю сложность ее. Например, существует понятие «уведомление», при котором, если элемент A каким-либо образом изменяется, A генерирует событие уведомления, чтобы B и C могли также изменить себя, чтобы сохранить целостность всего ансамбля. Это может распространяться на многие слои и значительно увеличивает стоимость модификации. Тогда вполне возможно, что изменение A вскоре может быть отмененоили изменен на еще одно изменение, означающее, что усилия, прилагаемые для обеспечения согласованности ансамбля, должны быть предприняты еще раз. Смущает то, что есть вероятность ошибок во всех этих обработчиках уведомлений, цикличность и т. Д. Гораздо лучше попытаться сохранить нормированную структуру данных, чтобы любое изменение нужно было вносить только в одном месте. Если нельзя избежать ненормированных данных, лучше иметь периодические проходы для устранения несоответствия, а не делать вид, что его можно согласовать с коротким поводком.

... когда я подумаю больше, я добавлю это.

Майк Данлавей
источник
8

Краткий ответ - да". И, как правило, код будет немного медленнее.

Но иногда правильный рефакторинг OO-ish выявляет оптимизации, которые делают код быстрее. Я работал над одним проектом, в котором мы создали гораздо более сложный алгоритм Java с правильными структурами данных, геттерами и т. Д. Вместо грязных вложенных массивов объектов. Но благодаря лучшей изоляции и ограничению доступа к структурам данных мы смогли перейти от гигантских массивов двойных значений (с нулевыми значениями для пустых результатов) к более организованным массивам двойных чисел с NaN для пустых результатов. Это привело к увеличению скорости в 10 раз.

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

user949300
источник
1
Я не понимаю, почему переключение с Doubles на doubles требует более структурированного кода.
svick
Вау, даунвот? Исходный код клиента не работал с Double.NaNs, но проверял наличие нулей для представления пустых значений. После реструктуризации мы могли бы обработать это (с помощью инкапсуляции) с помощью методов получения различных результатов алгоритма. Конечно, мы могли бы переписать код клиента, но это было проще.
user949300
Для справки, я не был тем, кто отрицал этот ответ.
svick
7

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

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


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

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

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

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


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

PS: эти 2 выступления Крокфорда всплыли в моей голове, и я думаю, что они несколько связаны. Во-первых, это супер-краткая история КС (которую всегда полезно знать при любой точной науке); и второй вопрос о том, почему мы отвергаем хорошие вещи.

оборота эльмигранто
источник
Мне действительно нравится этот ответ больше всего. Люди ужасно угадывают компилятор и снимают, где находятся узкие места. Конечно, о большой временной сложности следует помнить, но большой метод спагетти из 100 строк по сравнению с вызовом фабричного метода + некоторые виртуальные методы никогда не должны обсуждаться. производительность в этом случае совсем не интересна. Кроме того, большой плюс в том, что «оптимизация» без серьезных фактов и измерений - пустая трата времени.
Сара
4

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

Рори Хантер
источник
1
Я согласен, но мобильные устройства становятся настолько популярными, что я думаю, что они являются большим исключением. Хотя вычислительная мощность увеличивается, вы не можете создать приложение для iPhone и ожидать, что сможете добавить память, как вы можете на веб-сервере.
JeffO
3
@JeffO: Я не согласен - мобильные устройства с четырехъядерными процессорами теперь в норме, производительность (особенно учитывая то, что это влияет на время автономной работы), хотя и не столь важна, как стабильность Медленный мобильный телефон или планшет получают плохие отзывы, а нестабильный - убивают. Мобильные приложения очень динамичны, меняются почти ежедневно - источник должен идти в ногу.
Mattnz
1
@JeffO: Виртуальная машина, используемая на Android, очень плохая. Согласно бенчмаркам, он может быть на порядок медленнее, чем нативный код (в то время как лучший в своем классе, как правило, немного медленнее). Угадай что, никому нет дела. Написание приложения происходит быстро, а процессор сидит на месте и все время ждет ввода от пользователя 90% времени.
Ян Худек
Производительность выше, чем сырые тесты ЦП. Мой телефон Android работает нормально, за исключением случаев, когда AV сканирует обновление, а затем кажется, что оно зависает дольше, чем мне нравится, и это модель четырехъядерного ОЗУ объемом 2 ГБ! Сегодня пропускная способность (будь то сеть или память), вероятно, является основным узким местом. Ваш сверхбыстрый процессор, вероятно, крутит пальцы в 99% случаев, и в целом он все еще оставляет желать лучшего.
gbjbaanb
4

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

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

Что касается кода, сгенерированного компилятором (если хотите, машинного кода), чем больше функция, тем больше возможностей для того, чтобы пролить промежуточные значения в регистры в стек. При использовании кадров стека потребление стека увеличивается. Чем меньше функции, тем больше возможностей для хранения данных в регистрах и тем меньше зависимость от стека. Меньшие куски стека необходимы для каждой функции, естественно. Кадры стека имеют плюсы и минусы для производительности. Более мелкие функции означают больше настройки функций и очистки. Конечно, это также зависит от того, как вы компилируете, какие возможности вы предоставляете компилятору. Вы можете иметь 250 10-строчных функций вместо одной 2500-строчной функции, одну 2500-строчную функцию, которую собирается получить компилятор, если он может / хочет оптимизировать все это. Но если вы возьмете эти 250 10-строчных функций и распределите их по 2, 3, 4, 250 отдельным файлам, скомпилируете каждый файл отдельно, тогда компилятор не сможет оптимизировать почти столько же мертвого кода, сколько мог бы иметь. суть здесь в том, что есть плюсы и минусы для обоих, и невозможно установить общее правило в отношении того или иного способа.

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

способ unix состоит в том, чтобы сделать, используя мой термин, хорошо отполированные блоки lego. Зачем вам использовать функцию ленты через много лет после того, как мы перестали использовать ленты? Поскольку BLOB-объект очень хорошо справился со своей задачей, и на обратной стороне мы можем заменить интерфейс ленты на интерфейс файла и воспользоваться преимуществами программы. Зачем переписывать программное обеспечение для записи компакт-дисков только потому, что scsi ушла, поскольку доминирующий интерфейс был заменен ide (чтобы вернуться позже). Снова воспользуйтесь преимуществом отшлифованных субблоков и замените один конец новым интерфейсным блоком (также поймите, что разработчики аппаратного обеспечения просто добавили интерфейсный блок в конструкцию аппаратного обеспечения, чтобы в некоторых случаях сделать привод scsi идеальным для интерфейса scsi. Чтобы сократить это построить полированные лего-блоки разумного размера, каждый из которых имеет четко определенную цель и четко определенные входы и выходы. вы можете обернуть тесты вокруг этих блоков lego, а затем взять один и тот же блок и обернуть пользовательский интерфейс и интерфейсы операционной системы вокруг одного и того же блока и блока, теоретически будучи хорошо протестированными и хорошо понятными, отлаживать не нужно, только добавляются дополнительные новые блоки на каждом конце. до тех пор, пока все ваши интерфейсы блоков хорошо спроектированы и функциональность понятна, вы можете создать очень много вещей с минимальным количеством клея. так же, как с синими, красными, черными и желтыми блоками лего известных размеров и форм, вы можете сделать очень много вещей. до тех пор, пока все ваши интерфейсы блоков хорошо спроектированы и функциональность понятна, вы можете создать очень много вещей с минимальным количеством клея. так же, как с синими, красными, черными и желтыми блоками лего известных размеров и форм, вы можете сделать очень много вещей. до тех пор, пока все ваши интерфейсы блоков хорошо спроектированы и функциональность понятна, вы можете создать очень много вещей с минимальным количеством клея. так же, как с синими, красными, черными и желтыми блоками лего известных размеров и форм, вы можете сделать очень много вещей.

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

Старожил
источник
«Поэтому вы должны найти свой уровень комфорта, который может и будет варьироваться для каждого отдельного программиста». Я думаю, что уровень оптимизации должен определяться требованиями к производительности каждого куска кода, а не тем, что нравится каждому программисту.
svick
Компилятор уверен, предполагая, что программист сказал компилятору оптимизировать компиляторы, как правило, по умолчанию, чтобы не оптимизировать (в командной строке IDE может иметь другое значение по умолчанию, если используется). Но программист может не знать достаточно, чтобы оптимизировать размер функции и с таким количеством целей, где это бесполезно? Производительность ручной настройки имеет тенденцию отрицательно влиять на удобочитаемость, и, в частности, удобство сопровождения, тестируемость может пойти в любую сторону.
old_timer
2

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

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

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

Жиль ван Гурп
источник
0

[элемент компьютерного времени]

Влияет ли рефакторинг на более слабые функции связи и меньшие функции на скорость кода?

Да, это так. Но интерпретаторы, компиляторы и JIT-компиляторы должны урезать этот код «шов / разводка», и некоторые делают это лучше, чем другие, а некоторые нет.

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

[элемент человеческой скорости]

(И мне все равно?)

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

Таким образом, 2–4-кратное замедление выполнения самого нативного кода часто заглушается этими другими факторами.

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

Деннис
источник
0

ОТВЕТ (если вы его пропустили)

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

Короче

Не заботиться о производительности

В контексте вопроса, умные компиляторы и интерпретаторы уже позаботятся об этом

Позаботьтесь о написании поддерживаемого кода

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

Деннис
источник