修改方案
做了个测试,基于python 2.7.3,把PyEval_EvalFrameEx这个函数里的case都改成了label,然后利用gcc的labels as values特性,将里面用到的118个opcode与对应的label构成数组:
static void *label_hash[256] = {NULL};
static int initialized = 0;
if (!initialized)
{
#include "opcodes.c"
#include "labels.c"
int i, n_opcode = sizeof(opcode_list) / sizeof(*opcode_list);
for (i = 0; i < n_opcode; i++)
label_hash[opcode_list[i]] = label_list[i];
initialized = 1;
}
然后把 switch (opcodes) 改成
void *label = label_hash[opcode];
if (label == NULL)
goto default_opcode;
goto *label;
并逐个替换每个case里的break。
编译后通过了所有的测试(除了test_gdb,这个跟未修改版一样,都是没有sys.pydebug),也就是说这个修改是正确的。
性能测试
接下来的问题是性能……这个该怎么测试呢……没有好的想法,所以随便找了两段代码:
直接loop 5kw次:
i = 50000000
while i > 0:
i -= 1
修改前运行4次:[4.858, 4.851, 4.877, 4.850],去掉最大的一次,平均4.853s
修改后运行4次:[4.558, 4.546, 4.550, 4.560],去掉最大的一次,平均4.551s
性能提升 (100% - (4.551 / 4.853)) = 6.22%
递归Fibonacci,计算第38个
def fib(n):
return n if n <= 2 else fib(n - 2) + fib(n - 1)
print fib(37)
修改前 [6.227, 6.232, 6.311, 6.241],去掉最大的一次,平均6.233s
修改后 [5.962, 5.945, 6.005, 6.037],去掉最大的一次,平均5.971s
性能提升 (100% - (5.971 / 6.233)) = 4.20%
结论
综合看来,这个小小的改动,的确可以提高5%左右的性能,不知道对各位而言意义有多大……
不过因为用到了只有GCC支持的、非C标准的特性,所以不方便移植。根据StackOverflow的这个帖子,MSVC可以在一定程度上支持,但是貌似很tricky。不知道这个在Python的发展历程中是否有人做过这样的尝试,也许官方会偏好可移植性?也许抽空可以发个帖子去问问。 根据 @方泽图 的说法(见他答案里的评论),Python 3.0+引入了这个优化。详情可以参见他的答案。