Python 等价于内联函数或宏

Posted

技术标签:

【中文标题】Python 等价于内联函数或宏【英文标题】:Python equivalence to inline functions or macros 【发布时间】: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 代码上效果最好,而不是在编写用于去除微小的暂停执行开销。 【参考方案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 中实现此类优化器所涉及的复杂性感兴趣的人也可能想看看:

Issue 10399: AST Optimization: inlining of function calls PEP 511 -- API for code transformers(被拒绝)

【讨论】:

对不起,你的解决方案和问题有什么区别? @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 上。

以上是关于Python 等价于内联函数或宏的主要内容,如果未能解决你的问题,请参考以下文章

内联函数

python 内联函数

Scala3元编程-内联

内联函数

Kotlin学习手记——单例内部类数据类枚举类密封类内联类

深入探讨 内联函数和宏定义的区别