我知道这个问题有点愚蠢,也许这只是编写代码的一部分,但似乎定义简单的函数确实会严重损害性能......我尝试过这个简单的测试:
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 !! "])
这是非常好的风格并且使代码清晰,但它的速度可能只是字面上的三倍。对于可能产生副作用的函数来说,它是不可避免的,但实际上几乎无足轻重地定义一些函数,这些函数每次出现时都应该用代码行替换,将源代码翻译成只然后编译。同样我认为对于幻数,它不需要花费很多时间从内存中读取,但如果它们不应该被更改,那么为什么不在代码编译之前用文字替换'magic'的每个实例呢? / p>
答案 0 :(得分:6)
函数调用开销不大;你通常不会注意到它们。在这种情况下,您只能看到它们,因为您的实际代码(x * x)本身就是如此完全无关紧要。在任何真正有效的程序中,在函数调用开销上花费的时间可以忽略不计。
(在任何情况下,并不是说我真的建议在示例中使用foo,identity和square;它们是如此微不足道,它们可以读取内联并且它们并不真正封装或抽象任何东西。)
如果它们不应该被更改,那么为什么不在代码编译之前用文字替换'magic'的每个实例?
因为程序是为了你而易于阅读和维护而编写的。你可以用它们的文字值替换常量,但它会使程序更难以使用,因为这样的好处很小,你甚至可能永远无法测量它:高度premature optimisation
答案 1 :(得分:2)
我不知道python编译器有多好,但是对于许多语言来说这个问题的答案是编译器会通过内联来优化对小程序/函数/方法的调用。事实上,在某些语言实现中,您通常不会尝试自己“微优化”代码,从而获得更好的性能。
答案 2 :(得分:2)
封装只是一件事而且是一件事:可读性。如果你真的非常担心性能,你愿意开始剥离封装逻辑,你也可以开始编写汇编代码。
封装还有助于调试和添加功能。请考虑以下事项:假设您有一个简单的游戏,并且需要添加在某些情况下耗尽玩家健康状况的代码。很简单,是吗?
def DamagePlayer(dmg):
player.health -= dmg;
这是非常简单的代码,所以简单地将“player.health - =”散布到各处都很诱人。但是如果以后你想要添加一个能够在激活时对玩家造成的伤害减半的通电呢?如果逻辑仍然被封装,那很简单:
def DamagePlayer(dmg):
if player.hasCoolPowerUp:
player.health -= dmg / 2
else
player.health -= dmg
现在,考虑一下你是否因为它的简单而忽略了对那一点逻辑的封装。现在你正在考虑将相同的逻辑编码到50个不同的地方,其中至少有一个你几乎肯定会忘记,这会导致奇怪的错误,例如:“当玩家上电时,所有伤害都减半,除非被AlienSheep敌人击中。 ..“
你想和异形绵羊有问题吗?我不这么认为。 :)
严肃地说,我想说的是,在适当的情况下封装是一件非常好的事情。当然,它也很容易过度封装,这可能同样有问题。此外,在某些情况下,速度确实真正重要(虽然它们很少见),并且额外的几个时钟周期是值得的。关于找到正确平衡的唯一方法是练习。不要避免封装,因为它的速度较慢。这些好处通常远远超过成本。
答案 3 :(得分:1)
您所谈论的是内联函数对提高效率的影响。
在你的Python示例中,封装会损害性能。但是有一些反例:
在Java中,定义getter& setter而不是定义公共成员变量不会导致性能下降,因为JIT内联getter& setter。
有时重复调用函数可能比执行内联更好,因为执行的代码可能适合缓存。内联可能会导致代码爆炸......
答案 4 :(得分:0)
确定要在函数中制作什么以及包含内联的内容是一门艺术。许多因素(性能,可读性,可维护性)都可以纳入等式。
我实际上发现你的例子在很多方面都是愚蠢的 - 一个只返回它的参数的函数?除非它是一个改变规则的过载,否则它是愚蠢的。解决问题的功能?再次,为什么这么麻烦。您的函数'foo'应该返回一个字符串,以便可以直接使用它:
''.join(foo(x)," -- bar !! "])
在这个例子中,这可能是更正确的封装级别。
正如我所说,这实际上取决于具体情况。不幸的是,这种情况并不适用于例子。
答案 5 :(得分:0)
IMO,这与Function Call Costs有关。通常可以忽略不计,但不是零。在许多非常小的函数中拆分代码可能会受到伤害。特别是在没有完全优化的解释语言中。
内联函数可以提高性能,但也可能会恶化。例如,请参阅C++ FQA Lite进行解释(“内联可以通过消除函数调用开销使代码更快,或者通过生成太多代码使代码更慢,从而导致指令缓存未命中”)。这不是C ++特有的。除非非常必要,否则最好对编译器/解释器进行优化。
顺便说一句,我看不出两个版本之间存在巨大差异:
$ python bench.py
fine-grained function decomposition: 5.46632194519
one-liner: 4.46827578545
$ python --version
Python 2.5.2
我认为这个结果是可以接受的。请参阅pastebin中的bench.py。
答案 6 :(得分:0)
使用函数会有性能损失,因为跳转到新地址,在堆栈上推送寄存器以及最后返回时会产生开销。然而,这种开销非常小,即使在性能上,担心这种开销的批评系统也很可能是过早优化。
许多语言通过使用内联来避免在称为函数的小频率中出现这些问题,这基本上就是您在上面所做的。
Python不进行内联。您可以做的最接近的事情是使用宏来替换函数调用。
如果你需要通过内联获得的那种速度(大多数是边缘的,有时是非常的),这种性能问题可以通过另一种语言更好地服务,那么你需要考虑不使用python来处理你正在做的任何事情。
答案 7 :(得分:0)
有一个很好的技术原因,为什么你的建议是不可能的。在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代码应用了一些优化,但我不认为你建议的那种。