Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-125837: Split LOAD_CONST into three. #125972

Open
wants to merge 33 commits into
base: main
Choose a base branch
from

Conversation

markshannon
Copy link
Member

@markshannon markshannon commented Oct 25, 2024

Splits LOAD_CONST into three instructions

  • LOAD_INT for ints in range(256). Avoids the need for a space in the co_consts tuple and avoids an incref
  • LOAD_CONST_IMMORTAL for other immortal objects. Avoids an incref
  • LOAD_CONST for the remaining constants

📚 Documentation preview 📚: https://cpython-previews--125972.org.readthedocs.build/

Python/bytecodes.c Outdated Show resolved Hide resolved
Python/compile.c Outdated Show resolved Hide resolved
Python/flowgraph.c Outdated Show resolved Hide resolved
@picnixz
Copy link
Contributor

picnixz commented Oct 25, 2024

LOAD_INT for ints in range(256)

I think the range is -5 -> 256 (included) right? (at least _PyLong_SMALL_INTS works that way, but maybe 256 should be excluded even though it's stored in the _PyLong_SMALL_INTS).

@markshannon
Copy link
Member Author

I think the range is -5 -> 256 (included) right? (at least _PyLong_SMALL_INTS works that way, but maybe 256 should be excluded even though it's stored in the _PyLong_SMALL_INTS).

I want LOAD_INT 0 to push 0 not -5 to keep things simple.
So LOAD_INT will only cover range(256) even though the range of statically allocated ints is larger.

We can always add -5,-4,-3,-2 and -1 to LOAD_COMMON_CONST later if we want.

@markshannon
Copy link
Member Author

Performance for tier 1 appears to be about 0.5% faster.

Stats show the following fraction of constants loaded:

Opcode Fraction
LOAD_INT 47%
LOAD_CONST_IMMORTAL 42%
LOAD_CONST 11%

With a reduction in increfs of immortal objects of about 20%.

Include/internal/pycore_magic_number.h Outdated Show resolved Hide resolved
Lib/test/test_ast/test_ast.py Outdated Show resolved Hide resolved
Adds :opcode:`LOAD_INT` and :opcode:`LOAD_CONST_IMMORTAL` instructions.
``LOAD_INT`` pushes a small integer equal to the ``oparg`` to the stack.
``LOAD_CONST_IMMORTAL`` does the same as ``LOAD_CONST`` but is more
efficient for immortal objects.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and removes RETURN_CONST.

Python/specialize.c Outdated Show resolved Hide resolved
Python/flowgraph.c Outdated Show resolved Hide resolved
@brettcannon brettcannon removed their request for review October 25, 2024 20:01
Python/codegen.c Outdated Show resolved Hide resolved
@@ -656,6 +664,9 @@ codegen_setup_annotations_scope(compiler *c, location loc,
codegen_enter_scope(c, name, COMPILE_SCOPE_ANNOTATIONS,
key, loc.lineno, NULL, &umd));

// Insert None into consts to prevent an annotation
// appearing to be a docstring
_PyCompile_AddConst(c, Py_None);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this have to do with this PR?

Copy link
Member Author

@markshannon markshannon Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The consts for an __annotate__ function could be (0, "a_string"). Removing the 0 makes "a_string" the doc string.

#126072

@@ -2173,7 +2155,8 @@ remove_unused_consts(basicblock *entryblock, PyObject *consts)

for (basicblock *b = entryblock; b != NULL; b = b->b_next) {
for (int i = 0; i < b->b_iused; i++) {
if (OPCODE_HAS_CONST(b->b_instr[i].i_opcode)) {
int opcode = b->b_instr[i].i_opcode;
if (OPCODE_HAS_CONST(opcode) && opcode != LOAD_SMALL_INT) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasconst is defined in the dis docs as "Sequence of bytecodes that access a constant". I think it will make more sense if we clarify that it means "access a constant from co_consts" and then remove LOAD_SMALL_INT from hasconst and OPCODE_HAS_CONST.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. LOAD_COMMON_CONST is not in hasconst.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth testing whether creating integers within the range(256) will not affect co_consts?

consts.append(instr.argval)
return consts

@support.cpython_only
def test_load_const(self):
consts = [None,
True, False,
124,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we replace it with an int which is not captured by LOAD_SMALL_INT instead of removing it? For example, 1000.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants