Инкапсуляция сильно снижает производительность? - PullRequest
3 голосов
/ 01 сентября 2009

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

def make_legal_foo_string(x):
    return "This is a foo string: " + str(x)

def sum_up_to(x):
    return x*(x+1)/2

def foo(x):
    return [make_legal_foo_string(x),sum_up_to(x),x+1]

def bar(x):
    return ''.join([str(foo(x))," -- bar !! "])

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

Ответы [ 8 ]

6 голосов
/ 01 сентября 2009

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

(Не то, чтобы я действительно рекомендовал использовать foo, identity и square в примере, в любом случае; они настолько тривиальны, что их можно легко читать как встроенные, и они ничего не инкапсулируют и не абстрагируют.)

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

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

2 голосов
/ 01 сентября 2009

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

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

def DamagePlayer(dmg):
    player.health -= dmg;

Это очень тривиальный код, поэтому очень заманчиво просто разбросать "player.health - =" везде. Но что, если позже вы захотите добавить powerup, который вдвое уменьшает урон, нанесенный игроку во время активности? Если логика все еще инкапсулирована, это просто:

def DamagePlayer(dmg):
    if player.hasCoolPowerUp:
        player.health -= dmg / 2
    else
        player.health -= dmg

Теперь подумайте, не пренебрегали ли вы инкапсулированием этого кусочка логики из-за его простоты. Теперь вы смотрите на кодирование одной и той же логики в 50 разных местах, по крайней мере, одно из которых вы почти наверняка забудете, что приводит к странным ошибкам, таким как: «Когда игрок получает powerup, весь урон уменьшается вдвое, кроме случаев, когда он поражен врагами AlienSheep. .. "

Хотите ли вы иметь проблемы с Alien Sheep? Я так не думаю. :)

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

2 голосов
/ 01 сентября 2009

Я не знаю, насколько хороши компиляторы Python, но ответ на этот вопрос для многих языков заключается в том, что компилятор оптимизирует вызовы небольших процедур / функций / методов, вставляя их в строку. Фактически, в некоторых языковых реализациях вы, как правило, получаете лучшую производительность, НЕ пытаясь «микрооптимизировать» код самостоятельно.

1 голос
/ 01 сентября 2009

То, о чем вы говорите, - это эффект встроенных функций для повышения эффективности.

В вашем примере с Python определенно верно, что инкапсуляция снижает производительность. Но есть контрпример к этому:

  1. В Java определение getter & setter вместо определения общедоступных переменных-членов не приводит к снижению производительности, поскольку JIT встроен в getter & setters.

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

0 голосов
/ 01 сентября 2009

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

Например, вот идиома логирования:

# beginning of the file xxx.py
log = lambda *x: None 

def something():
    ...
    log(...)
    ...

(здесь log ничего не делает), а затем в каком-либо другом модуле или в интерактивном режиме:

import xxx
xxx.log = print
xxx.something()

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

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

Есть некоторые инструменты, которые фактически применяют некоторые оптимизации к коду Python, но я не думаю, что вы предложили.

0 голосов
/ 01 сентября 2009

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

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

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

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

0 голосов
/ 01 сентября 2009

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

Встроенные функции могут улучшить производительность, но могут также ухудшиться. См., Например, C ++ FQA Lite для объяснений («Встраивание может сделать код быстрее, устраняя издержки при вызове функции, или медленнее, генерируя слишком много кода, вызывая пропуски кэша команд»). Это не специфично для C ++. Лучше оставить оптимизации для компилятора / интерпретатора, если они действительно необходимы .

Кстати, я не вижу большой разницы между двумя версиями:

$ python bench.py 
fine-grained function decomposition: 5.46632194519
one-liner: 4.46827578545
$ python --version
Python 2.5.2

Я думаю, что этот результат приемлем. См. Файл bench.py ​​в pastebin .

0 голосов
/ 01 сентября 2009

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

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

''.join(foo(x)," -- bar !! "])

Это, вероятно, более правильный уровень инкапсуляции в этом примере.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...