除了本地/全局变量存储时间之外,操作码预测使功能更快。

正如其他答案所解释的,该函数使用STORE_FAST循环中的操作码。下面是函数循环的字节码:

>>   13 FOR_ITER                 6 (to 22)   # get next value from iterator
16 STORE_FAST               0 (x)       # set local variable
19 JUMP_ABSOLUTE           13           # back to FOR_ITER

通常,当程序运行时,Python会一个接一个地执行每个操作码,跟踪堆栈,并在每个操作码执行后预先对堆栈帧进行其他检查。Opcode预测意味着在某些情况下Python能够直接跳转到下一个操作代码,从而避免了一些开销。

在这种情况下,每次Python看到FOR_ITER(循环的顶部),它将“预测”STORE_FAST是它必须执行的下一个操作码。Python然后窥视下一个操作码,如果预测是正确的,它直接跳到STORE_FAST..这样做的效果是将这两个操作码压缩成一个操作码。

另一方面,STORE_NAME在全局级别的循环中使用操作码。Python做*不是*当它看到这个操作码时做类似的预测。相反,它必须回到计算循环的顶部,这对循环的执行速度有明显的影响。

为了给出更多关于优化的技术细节,下面引用ceval.c文件(Python虚拟机的“引擎”):一些操作码往往成对出现,从而使得在第一个代码运行时能够预测第二个代码。例如,GET_ITER经常跟在后面FOR_ITER..和FOR_ITER经常跟在后面STORE_FAST或UNPACK_SEQUENCE.

验证预测需要对一个常量对寄存器变量进行一次高速测试.如果配对是好的,那么处理器自己的内部分支预测很有可能成功,从而导致向下一个操作码的几乎零开销转换。成功的预测保存了遍历val循环的行程,包括它的两个不可预测的分支,即HAS_ARG测试和开关箱。结合处理器的内部分支预测,一个成功的PREDICT这两个操作码的运行效果就好像它们是一个单一的新操作码,并将其与身体结合在一起。

我们可以在源代码中看到FOR_ITER操作码,准确地预测到STORE_FAST现制作:

case FOR_ITER:                         // the FOR_ITER opcode case
v = TOP();
x = (*v->ob_type->tp_iternext)(v); // x is the next value from iterator
if (x != NULL) {
PUSH(x);                       // put x on top of the stack
PREDICT(STORE_FAST);           // predict STORE_FAST will follow - success!
PREDICT(UNPACK_SEQUENCE);      // this and everything below is skipped
continue;
}
// error-checking and more code for when the iterator ends normally
这个PREDICT功能扩展到if (*next_instr == op) goto PRED_##op也就是说,我们只是跳到预测操作码的开始。在这种情况下,我们跳到这里:PREDICTED_WITH_ARG(STORE_FAST);case STORE_FAST:
v = POP();                     // pop x back off the stack
SETLOCAL(oparg, v);            // set it as the new local variable
goto fast_next_opcode;

现在设置局部变量,并开始执行下一个操作码。Python继续遍历可迭代直到它到达终点,每次都做出成功的预测。

这个Python wiki页面拥有有关CPython虚拟机工作方式的更多信息。