【发布时间】:2018-10-12 23:38:26
【问题描述】:
在 C 程序中,内联函数是一种相当直观的优化。如果内联函数的主体足够小,则最终保存到函数的跳转和堆栈帧的创建,并将返回值存储在函数结果将被存储的任何地方,跳转到内联函数的“主体”的末尾" 而不是长跳转到返回指针。
我有兴趣在 Python 中做同样的事情,将两个 Python 函数转换为另一个有效的 Python 函数,其中第一个被“内联”到第二个。一个理想的解决方案可能如下所示:
def g(x):
return x ** 2
def f(y):
return g(y + 3)
# ... Becomes ...
def inlined_f(y):
return (y + 3) ** 2
显然,在像 Python 这样动态的语言中,自动执行此操作并非易事。我想出的最好的通用解决方案是使用dict 来捕获传递给函数的参数,将函数体包装在一个迭代for 循环中,使用break 跳转到函数的末尾, 并将参数的使用替换为参数字典中的索引。结果如下所示:
def inlined_f(y):
_g = dict(x=y + 3)
for ____ in [None]:
_g['return'] = _g['x'] ** 2
break
_g_return = _g.get('return', None)
del _g
return _g_return
我不在乎它难看,但我确实在乎它不支持从循环内返回。例如:
def g(x):
for i in range(x + 1):
if i == x:
return i ** 2
print("Woops, you shouldn't get here")
def inlined_f(y):
_g = dict(x=y + 3)
for ____ in [None]:
for _g['i'] in range(_g['x'] + 1):
if _g['i'] == _g['x']:
_g['return'] _g['i'] ** 2
break # <-- Doesn't exit function, just innermost loop
print("Woops, you shouldn't get here")
_g_return = _g.get('return', None)
del _g
return _g_return
我可以采取什么方法来避免需要使用break 来“跳”出内联函数的主体?我也愿意接受一种整体更好、更通用的方法来将一个 Python 函数内联到另一个函数中。
作为参考,我在 AST(抽象语法树)级别工作,因此使用已解析的 Python 代码;显然,在文字值之外,我不知道在执行这种转换时任何东西会有什么值或类型。生成的内联函数的行为必须与原始函数相同,并且必须支持调用函数时通常可用的所有功能。这在 Python 中是否可行?
编辑:我应该澄清一下,因为我使用了“优化”标签,我实际上对性能提升并不感兴趣。生成的代码不需要更快,它只是不能调用内联函数,同时仍然表现相同。您可以假设这两个函数的源代码都可以作为有效的 Python 使用。
【问题讨论】:
-
感谢您提出有趣的问题,但我很好奇您对这种转换的预期应用。
-
在字节码级别进行这种转换可能更有意义。 (不过,如果您正在将这种程度的努力投入到微优化中,那么使用 Cython 之类的东西可能会给您带来更多收益。)
-
嗯.. 这似乎不像内联的正常定义(即 alpha 转换 + 用代码替换调用)。你似乎在做某种急切的评估..?
-
@NPE 特别是I'm writing a library,其目标是执行动态句法更改以支持其他代码生成库,例如
numba.jit和tangent。因此,为什么我需要结果是一个有效的 Python 函数。如果我可以动态生成不使用某些代码特性的等效 Python 函数,那么我不需要依赖那些库来支持那些代码特性(例如函数调用)。 -
@scnerd:Numba 目前是doesn't support exception handling,但它确实支持函数调用,所以使用异常处理来转换函数调用听起来没什么用。
标签: python inline abstract-syntax-tree compiler-optimization