【问题标题】:Python equivalence to inline functions or macrosPython 等价于内联函数或宏
【发布时间】:2011-09-20 11:41:03
【问题描述】:

我才意识到这样做

x.real*x.real+x.imag*x.imag

比做起来快三倍

abs(x)**2

其中 x 是一个 numpy 复数数组。为了代码的可读性,我可以定义一个函数,如

def abs2(x):
    return x.real*x.real+x.imag*x.imag

仍然比 abs(x)**2 快得多,但它是以函数调用为代价的。是否可以像我在 C 中使用宏或使用 inline 关键字那样内联这样的函数?

【问题讨论】:

  • 如果你需要这种优化,你可能需要使用 Cython 之类的东西。
  • PyPy 来救援!
  • 如果你关心这么小的优化,你应该使用 C,而不是 python。 python 与速度无关,真的。
  • 您是否尝试过对语句和函数调用进行计时,看看是否真的有区别?
  • 除了非常正确和重要的(说真的,听他们说),请注意,由于 Python 的动态特性,唯一可能发生内联的时间是在运行时。这是 PyPy 所做的众多优化之一(尽管它还没有远程完整的 NumPy;但至少它正在开发中),并且 PyPy 在惯用的 Python 代码上效果最好,而不是在编写用于去除微小的暂停执行开销。

标签: python optimization inline-functions


【解决方案1】:

是否可以像在 C 中使用宏或使用 inline 关键字那样内联这样的函数?

没有。在到达这个特定指令之前,Python 解释器甚至不知道是否有这样的函数,更不用说它的作用了。

如 cmets 中所述,PyPy 将自动内联(以上仍然成立 - 它“简单地”在运行时生成优化版本,从中受益,但在它失效时会中断),尽管在这种特定情况下,在 PyPy 上实施 NumPy 只是不久前才开始的,而且直到今天还不是 beta 级别。但底线是:不要担心 Python 中这一级别的优化。实现自己优化或不优化,这不是你的责任。

【讨论】:

  • +1 "不要担心 Python 中这一级别的优化。无论是实现自己优化还是不优化,这不是你的责任。"
  • @phant0m 不知道你们为什么这么喜欢那句话……它基本上是说如果不让代码变得丑陋就无法优化。我只需要内联几个调用就可以使我的程序速度提高一倍。至少这是值得的……
  • 我也觉得有点难以接受最后的评论。这很好,而且“不是我的责任”,但归根结底,如果我的代码未达到性能目标,我不能告诉我的老板这是别人的错。
【解决方案2】:

不完全是 OP 所要求的,但很接近:

Inliner 内联 Python 函数调用。 this blog post的概念证明

from inliner import inline

@inline
def add_stuff(x, y):
    return x + y

def add_lots_of_numbers():
    results = []
    for i in xrange(10):
         results.append(add_stuff(i, i+1))

在上面的代码中,add_lots_of_numbers 函数被转换为 这个:

def add_lots_of_numbers():
    results = []
    for i in xrange(10):
         results.append(i + i + 1)

任何对这个问题以及在 CPython 中实现此类优化器所涉及的复杂性感兴趣的人也可能想看看:

【讨论】:

  • 对不起,你的解决方案和问题有什么区别?
  • @RogerS,OP 询问了类似于 C 宏(内联关键字)的东西,它们非常灵活和高效。这个库有一些 limitations 并且有启动时间成本,但除此之外,它会按照问题的要求进行。
【解决方案3】:

我同意其他所有人的观点,即这样的优化只会让你在 CPython 上感到痛苦,如果你关心性能,你应该考虑 PyPy(尽管我们的 NumPy 可能太不完整而没有用处) .但是我不同意并说您可以关心 PyPy 上的此类优化,而不是像所说的那样 PyPy 自动执行此优化,但是如果您对 PyPy 非常了解,您真的可以调整您的代码以使 PyPy 发出您想要的程序集,并不是说你几乎永远都需要。

【讨论】:

    【解决方案4】:

    没有。

    最接近 C 宏的是可以包含在 makefile 中的脚本(awk 或其他),它用长格式替换 Python 脚本中的特定模式,如 abs(x)**2。

    【讨论】:

    • 无论如何,Python 并不是最快的语言,这没关系,因为它的开发周期很快。确实强烈建议不要为新的 python 项目添加“预处理”步骤。
    • 他并没有声称这是个好主意。从技术上讲,他是正确的。
    【解决方案5】:

    实际上计算起来可能会更快,例如:

    x.real** 2+ x.imag** 2
    

    因此,函数调用的额外成本可能会减少。让我们看看:

    In []: n= 1e4
    In []: x= randn(n, 1)+ 1j* rand(n, 1)
    In []: %timeit x.real* x.real+ x.imag* x.imag
    10000 loops, best of 3: 100 us per loop
    In []: %timeit x.real** 2+ x.imag** 2
    10000 loops, best of 3: 77.9 us per loop
    

    并将计算封装在一个函数中:

    In []: def abs2(x):
       ..:     return x.real** 2+ x.imag** 2
       ..: 
    In []: %timeit abs2(x)
    10000 loops, best of 3: 80.1 us per loop
    

    无论如何(正如其他人指出的那样)这种微优化(为了避免函数调用)并不是编写 python 代码的真正有效的方式。

    【讨论】:

    • ~3us 如果你做 100 次或 10000 次可能不会很多。做一百万次你会想要刮胡子
    • @MrMesees 那里有 C
    【解决方案6】:

    你可以尝试使用lambda:

    abs2 = lambda x : x.real*x.real+x.imag*x.imag
    

    然后调用它:

    y = abs2(x)
    

    【讨论】:

    • 好主意,但我只是尝试了一下... 根本没有提高性能: def foo(bar): return barfoo = lambda bar: bar 都在我的系统上执行 57.5 nanoseconds .用 timeit 测量。所以 lambda 与常规函数及其调用完全一样。至少在 CPython 3.8 上。
    猜你喜欢
    • 2011-10-13
    • 2014-04-27
    • 1970-01-01
    • 2012-09-07
    • 2014-10-02
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    相关资源
    最近更新 更多