Влияет ли модульное программирование на время вычислений?


19

Все говорят, что я должен сделать свой код модульным, но разве это не менее эффективно, если я использую больше вызовов методов, а не меньше, но больше методов? Какая разница в Java, C или C ++ в этом отношении?

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


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

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

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

1
@greyfade: Это верно для прямых переходов, но дополнительный косвенный прогнозируемый переход может стоить, например, ~ 3% от общего времени выполнения программы (просто число из программы, которую я недавно проверял - хотя, возможно, оно не было репрезентативным). В зависимости от вашей области вы можете или не можете считать это значительным, но оно действительно зарегистрировано на диаграмме (и это, конечно, по крайней мере частично ортогонально модульности).
Мацей Печотка

4
Преждевременная оптимизация - корень всего зла. Линейный код немного быстрее модульного кода. Модульный код намного быстрее, чем спагетти-код. Если вы стремитесь к линейному коду без очень (ОЧЕНЬ) тщательного проекта всего этого, вы получите код спагетти, я гарантирую это.
SF.

Ответы:


46

Да, это не имеет значения.

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

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


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

28
+1 только за выражение «сжалиться над процессором», потому что оно так приятно акцентирует неверность в преждевременной оптимизации.
Майкл Боргвардт

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

7
+1 за столь красноречивую продажу того, чего не хватает многим, но вы забыли добавить самый важный вес к балансу, который делает жалость к процессору еще более серьезной: игнорируя потерянные деньги и время, потраченное на одноразовое обслуживание; если это заняло 1 секунду, там все еще есть гораздо более коварная раковина. Ошибка риска . Чем сложнее и труднее для этого более позднего сопровождающего изменить ваш код, тем более значительный риск того, что он внедрит в него ошибки, которые могут привести к непреодолимо большим неизбежным затратам от риска для пользователей, и любая ошибка приведет к последующему обслуживанию (что может привести к жучки ...)
Джимми Хоффа

Мой старый учитель говорил: «Если вы думаете, преждевременно ли вы оптимизируете или нет, остановитесь прямо здесь. Если бы это был правильный выбор, вы бы знали об этом».
Ven

22

По-разному.

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

В мире программирования встроенных систем и обработчиков прерываний для высокоскоростных прерываний это, безусловно, имеет значение. В этой среде обычные модели «доступ к памяти дешев» и «процессор бесконечно быстр» выходят из строя. Я видел, что происходит, когда объектно-ориентированный программист мэйнфрейма пишет свой первый высокоскоростной обработчик прерываний. Это было не красиво.

Несколько лет назад я выполнял нерекурсивную 8-полосную раскраску блогов для подключения к изображениям FLIR в реальном времени на том, что в то время было достойным процессором. Первая попытка использовала вызов подпрограммы, и издержки вызова подпрограммы сожрали процессор. (4 вызова на ПИКСЕЛ x 64K пикселей на кадр x 30 кадров в секунду = вы выясните это). Вторая попытка превратила подпрограмму в макрос C без потери читаемости, и все было розами.

Вы должны смотреть ТРУДНО на то, что вы делаете, и на среду, в которой вы будете это делать.


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

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

2
Это правда, но вы оптимизировали свой код после того, как написали логику и профилировали ее, чтобы найти проблемную часть в своем алгоритме. Вы начали кодировать не для производительности, а для удобства чтения. А макросы помогут вам сохранить ваш код читабельным.
Уве Плонус

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

1
@ детально: да и нет. Современные компиляторы в общем могут лучше работать в рамках ограничений, налагаемых языком. Программист-человек знает, применимы ли ограничения, налагаемые языком, в конкретном конкретном случае. Например, языковые стандарты требуют компиляторов Cn и C ++, чтобы позволить программисту совершать определенные патологические действия и генерировать код, который работает в любом случае. Программист может знать, что он не сумасшедший, не глупый и не достаточно авантюрный, чтобы делать такие вещи, и делать оптимизации, которые в противном случае были бы небезопасными, потому что он знает, что в этом случае они безопасны.
Джон Р. Стром,

11

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

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

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


5
Я бы сказал, что мы должны писать программы таким образом, чтобы другие их понимали.
Бартломей Левандовски

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

5

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

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


2

Вероятно, нет затрат на вычисления. Обычно компиляторы / JIT за последние 10-20 лет или около того прекрасно справляются с функцией встраивания. Для C / C ++ обычно это ограничивается «встроенными» функциями (т.е. определение функции доступно для компилятора во время компиляции - то есть оно находится в заголовке того же файла), но современные методы LTO преодолевают это.

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

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

  • Где проблемы? Обычно людям трудно найти горячие точки, поскольку мы по-разному читаем исходный код. У нас разные соотношения времени работы, и мы выполняем их последовательно, а современные процессоры - нет .
  • Нужны ли какие-то вычисления каждый раз? Например, если вы измените один параметр из тысяч, вы можете вычислить только затронутую часть, а не всю модель.
  • Используете ли вы оптимальный алгоритм? Изменение с O(n)на O(log n)может оказать гораздо большее влияние, чем все, что вы могли бы достичь с помощью микрооптимизации.
  • Вы используете правильные структуры? Скажем, вы используете, Listкогда вам нужно, HashSetчтобы у вас был O(n)поиск, когда вы могли бы иметь O(1).
  • Эффективно ли вы используете параллелизм? В настоящее время даже мобильные телефоны могут иметь 4 или более ядер, что может быть заманчиво для использования потоков. Однако они не являются «серебряной пулей», поскольку имеют стоимость синхронизации (не говоря уже о том, что если проблема связана с памятью, они все равно не имеют смысла).

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

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

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

  • В Java есть JIT, который во многих случаях может предсказать тип класса и, следовательно, цель перехода заранее, и поэтому в горячих точках у вас не должно быть много проблем.
  • Компиляторы C ++ часто выполняют программный анализ и, по крайней мере, в некоторых случаях могут предсказать цель во время компиляции.
  • В обоих случаях, когда цель была предсказана, вставка должна работать. Если компилятор не может выполнить встроенные возможности, мы тоже не сможем.

0

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

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

Вы знаете YouTube? Скорее всего, это сайт с самой высокой пропускной способностью или второй после Netflix? Они пишут большую часть своего кода на Python, который является высокомодульным языком, не совсем созданным для первоклассной производительности.

Дело в том, что когда что-то идет не так, и пользователи жалуются на медленную загрузку видео, существует не так много сценариев, в которых эта медлительность в конечном итоге объясняется медленной скоростью выполнения Python. Однако быстрая перекомпиляция Python и его модульная возможность пробовать новые вещи без проверки типов, вероятно, позволят инженерам довольно быстро отладить то, что идет не так («Вау. Наш новый стажер написал цикл, который выполняет новый подзапрос SQL для КАЖДОГО результата. ") или (" О, Firefox устарел, этот старый формат кэширования заголовка; и они создали библиотеку Python для легкой установки нового ")

В этом смысле, даже с точки зрения времени выполнения, модульный язык можно считать более быстрым, потому что, как только вы обнаружите свои узкие места, вам, вероятно, станет легче реорганизовать код, чтобы он работал наилучшим образом. Так много инженеров скажут вам, что хиты с высокой производительностью были не там, где они думали, что будут (и на самом деле то, что они ДЕЙСТВИТЕЛЬНО оптимизировали, вряд ли было нужно, или даже не работало так, как они ожидали!)


0

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

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

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

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

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