Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f606f87

Browse files
committed
Introduced macros for a simple opcode prediction protocol.
Applied to common cases: COMPARE_OP is often followed by a JUMP_IF. JUMP_IF is usually followed by POP_TOP. Shows improved timings on PyStone, PyBench, and specific tests using timeit.py: python timeit.py -s "x=1" "if x==1: pass" python timeit.py -s "x=1" "if x==2: pass" python timeit.py -s "x=1" "if x: pass" python timeit.py -s "x=100" "while x!=1: x-=1" Potential future candidates: GET_ITER predicts FOR_ITER FOR_ITER predicts STORE_FAST or UNPACK_SEQUENCE Also, applied missing goto fast_next_opcode to DUP_TOPX.
1 parent 0070f00 commit f606f87

1 file changed

Lines changed: 35 additions & 6 deletions

File tree

Python/ceval.c

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,26 @@ eval_frame(PyFrameObject *f)
602602
#define JUMPTO(x) (next_instr = first_instr + (x))
603603
#define JUMPBY(x) (next_instr += (x))
604604

605+
/* OpCode prediction macros
606+
Some opcodes tend to come in pairs thus making it possible to predict
607+
the second code when the first is run. For example, COMPARE_OP is often
608+
followed by JUMP_IF_FALSE or JUMP_IF_TRUE. And, those opcodes are often
609+
followed by a POP_TOP.
610+
611+
Verifying the prediction costs a single high-speed test of register
612+
variable against a constant. If the pairing was good, then the odds
613+
processor has a high likelihood of making its own successful branch
614+
prediction which results in a nearly zero overhead transition to the
615+
next opcode.
616+
617+
A successful prediction saves a trip through the eval-loop including
618+
its two unpredictable branches, the HASARG test and the switch-case.
619+
*/
620+
621+
#define PREDICT(op) if (*next_instr == op) goto PRED_##op
622+
#define PREDICTED(op) PRED_##op: next_instr++
623+
#define PREDICTED_WITH_ARG(op) PRED_##op: oparg = (next_instr += 3, (next_instr[-1]<<8) + next_instr[-2])
624+
605625
/* Stack manipulation macros */
606626

607627
#define STACK_LEVEL() (stack_pointer - f->f_valuestack)
@@ -873,6 +893,7 @@ eval_frame(PyFrameObject *f)
873893
SETLOCAL(oparg, v);
874894
goto fast_next_opcode;
875895

896+
PREDICTED(POP_TOP);
876897
case POP_TOP:
877898
v = POP();
878899
Py_DECREF(v);
@@ -920,7 +941,7 @@ eval_frame(PyFrameObject *f)
920941
STACKADJ(2);
921942
SET_TOP(x);
922943
SET_SECOND(w);
923-
continue;
944+
goto fast_next_opcode;
924945
} else if (oparg == 3) {
925946
x = TOP();
926947
Py_INCREF(x);
@@ -932,7 +953,7 @@ eval_frame(PyFrameObject *f)
932953
SET_TOP(x);
933954
SET_SECOND(w);
934955
SET_THIRD(v);
935-
continue;
956+
goto fast_next_opcode;
936957
}
937958
Py_FatalError("invalid argument to DUP_TOPX"
938959
" (bytecode corruption?)");
@@ -1918,8 +1939,10 @@ eval_frame(PyFrameObject *f)
19181939
Py_DECREF(v);
19191940
Py_DECREF(w);
19201941
SET_TOP(x);
1921-
if (x != NULL) continue;
1922-
break;
1942+
if (x == NULL) break;
1943+
PREDICT(JUMP_IF_FALSE);
1944+
PREDICT(JUMP_IF_TRUE);
1945+
continue;
19231946

19241947
case IMPORT_NAME:
19251948
w = GETITEM(names, oparg);
@@ -1974,10 +1997,13 @@ eval_frame(PyFrameObject *f)
19741997
JUMPBY(oparg);
19751998
goto fast_next_opcode;
19761999

2000+
PREDICTED_WITH_ARG(JUMP_IF_FALSE);
19772001
case JUMP_IF_FALSE:
19782002
w = TOP();
1979-
if (w == Py_True)
2003+
if (w == Py_True) {
2004+
PREDICT(POP_TOP);
19802005
goto fast_next_opcode;
2006+
}
19812007
if (w == Py_False) {
19822008
JUMPBY(oparg);
19832009
goto fast_next_opcode;
@@ -1991,10 +2017,13 @@ eval_frame(PyFrameObject *f)
19912017
break;
19922018
continue;
19932019

2020+
PREDICTED_WITH_ARG(JUMP_IF_TRUE);
19942021
case JUMP_IF_TRUE:
19952022
w = TOP();
1996-
if (w == Py_False)
2023+
if (w == Py_False) {
2024+
PREDICT(POP_TOP);
19972025
goto fast_next_opcode;
2026+
}
19982027
if (w == Py_True) {
19992028
JUMPBY(oparg);
20002029
goto fast_next_opcode;

0 commit comments

Comments
 (0)