Além dos tempos de armazenamento variáveis locais / globais, a previsão do código de operação torna a função mais rápida.
Como as outras respostas explicam, a função usa o STORE_FAST
código de operação no loop. Aqui está o bytecode para o loop da função:
>> 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
Normalmente, quando um programa é executado, o Python executa cada código de operação um após o outro, acompanhando a pilha e pré-formando outras verificações no quadro da pilha após a execução de cada código de operação. A previsão de código de operação significa que, em certos casos, o Python é capaz de pular diretamente para o próximo código de operação, evitando parte dessa sobrecarga.
Nesse caso, toda vez que o Python vir FOR_ITER
(a parte superior do loop), ele "prediz" que STORE_FAST
é o próximo opcode que ele deve executar. O Python então espreita o próximo código de operação e, se a previsão estiver correta, salta diretamente para STORE_FAST
. Isso tem o efeito de espremer os dois códigos de operação em um único código de operação.
Por outro lado, o STORE_NAME
código de operação é usado no loop em nível global. O Python * não * faz previsões semelhantes quando vê esse opcode. Em vez disso, ele deve voltar ao topo do loop de avaliação, o que tem implicações óbvias para a velocidade com que o loop é executado.
Para fornecer mais detalhes técnicos sobre essa otimização, aqui está uma citação do ceval.c
arquivo (o "mecanismo" da máquina virtual do Python):
Alguns opcodes tendem a vir em pares, tornando possível prever o segundo código quando o primeiro é executado. Por exemplo,
GET_ITER
geralmente é seguido por FOR_ITER
. E FOR_ITER
é frequentemente seguido porSTORE_FAST
ou UNPACK_SEQUENCE
.
Verificar a previsão custa um único teste de alta velocidade de uma variável de registro em relação a uma constante. Se o emparelhamento foi bom, a predicação de ramificação interna do próprio processador tem uma alta probabilidade de sucesso, resultando em uma transição quase zero para o próximo código de operação. Uma previsão bem-sucedida salva uma viagem pelo loop de avaliação, incluindo seus dois ramos imprevisíveis, o HAS_ARG
teste e o caso do switch. Combinado com a previsão de ramificação interna do processador, um êxito PREDICT
tem o efeito de executar os dois opcodes como se fossem um único novo opcode com os corpos combinados.
Podemos ver no código fonte do FOR_ITER
opcode exatamente onde a previsão STORE_FAST
é feita:
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
A PREDICT
função se expande para, if (*next_instr == op) goto PRED_##op
ou seja, apenas saltamos para o início do código de operação previsto. Nesse caso, pulamos aqui:
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;
A variável local está agora definida e o próximo opcode está pronto para execução. O Python continua pelo iterável até chegar ao fim, fazendo a previsão bem-sucedida a cada vez.
A página wiki do Python tem mais informações sobre como a máquina virtual do CPython funciona.