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
ceval: Wordcode follow up, explicit unsigned short read #71284
Comments
Attached is a patch based off bpo-26647 which converts ceval to read opcode/oparg by casting next_instr to an unsigned short. I don't believe we need to complicate the code with checking the pointer alignment of the instruction stream; some effort must be gone through to generate bytecode off alignment, & if the issue comes up it's because someone's using the C API on a CPU that doesn't support unaligned reads to generate their own custom bytecode. Anyways pybench went from 16.5 on wordcode to 16.0 for me The change works through replacing NEXTOP/NEXTARG with a NEXTOPARG macro |
This assignment:
has undefined behaviour, according to the C standards. I'm not sure it's a good idea to introduce this into the core. |
I retract this; I'm no longer convinced this is true. Sorry for the noise. |
What if change the type of next_instr to unsigned short*? This would guarantee that the read is aligned since the start of code data is aligned. I have small doubt about PREDICT(). I doubt that the compiler is smart enough to infer that *next_instr and *(unsigned short*)next_instr & 255 is the same. It can do unneeded work. I don't know whether this have negative effect. |
Using Raymond's microbenchmark from bpo-25823: $ ./python.exe exercise_oparg.py
Unpatched: 0.8331978429996525
Patched: 0.7600522060020012 Pybench exposes 7% speed up (detailed report is attached). |
I'm pretty sure this is undefined behavior. Here's how similar code was fixed in Chromium: |
I hope following patch is free from undefined behavior. |
There is no strict aliasing issues because aliasing is explicitly allowed for char buffers. The only issue is unaligned memory reads, but allocations are well aligned, so there'd have to be explicit work to allocate & then put code at an uneven offset. CPython never does this. This leaves the only issue being if a jump has an uneven jump offset. But jumping into the middle of an instruction is already undefined behavior because if I jump into a byte that happens to be BINARY_ADD on an empty stack it's undefined behavior. Jumping into the middle of an instruction thus falls under "Undefined behavior due to invalid bytecode" tl;dr There is no undefined behavior given correct bytecode layout, & we already have undefined behavior given incorrect bytecode layout. PREDICT concerns may be valid; the solution there may be to have a PEEKOPARG which we can then check the short value before splitting it if the opcode portion matches |
Are you talking about the exact C implementation, or the risk of unaligned memory accesses? I suggest to add an assertion to ensure that the pointer is aligned to 16-bits. |
I dislike this approach. I would prefer to use the unsigned short* type for next_instr, and put an assertion when next_instr is first assigned. |
There's some difficulty in changing next_instr to an unsigned short* unless pointer arithmetic converts it back to unsigned char*. Code relies on f_lasti to be a byte index more than it relies on f_lasti to be -1 at first. Should this change include converting jump offsets to word increments? This patch seeks to be a minimal change to achieve the desired aligned/batched memory read behavior of NEXTOP/NEXTARG. Perhaps a clear description of how deep the changes should be is in order It's also a bit hard to assert pointer alignment without failling back to stdint's uintptr_t. It may be sufficient to assert f_lasti is an even number |
Hmm, don't know why my patch was not uploaded first time. It contained exactly what Victor said. Yes, we need to multiply or divide by 2 when convert from pointer difference to byte offset, but this doesn't affect performance. I think the next step is the change of the meaning of jump offsets and instruction numbers. |
Casting the pointer is OK, explicitly typing the pointer is even better; then the arithmetic is even clearer and it is easy to add offsets in words! However what I'd prefer here as the size of the type is important, to use |
This is separate issue (see bpo-17884). |
ushort2.patch LGTM, but I added many small comments on the review ;-) |
Antti Haapala:
I'm not aware of a platform where sizeof(unsigned short) is not *exactly* 16-bits. At least, no platform supported by CPython. Include/unicodeobject.h is quite explicit: #if SIZEOF_SHORT == 2
typedef unsigned short Py_UCS2;
#else
#error "Could not find a proper typedef for Py_UCS2"
#endif Maybe we should use a similar code in ceval.c? #if SIZEOF_SHORT != 2
# error "Could not find a proper type for 16-bit pointer"
#endif |
Demur: "This patch [ ushort.patch ] seeks to be a minimal change to achieve the desired aligned/batched memory read behavior of NEXTOP/NEXTARG." We are concerned by undefined behaviours in CPython for portability reasons. Using a "unsigned short*" pointer avoids the undefined behaviour. I'm talking about unsigned char* => unsigned short* conversion followed by a dereference: +#define NEXTOPARG() (oparg = *(unsigned short*)next_instr, opcode = OPOF(oparg), oparg = ARGOF(oparg), next_instr += 2) With ushort2.patch, the risk is reduced at once place which is now "protected" by an assertion: + assert(_Py_IS_ALIGNED(PyBytes_AS_STRING(co->co_code), unsigned short)); |
Updated patch addresses Victor's comments. |
ushort3.patch LGTM. You might also add (copy) the added assertions in PyCode_New*(). |
New changeset eda3716d6425 by Serhiy Storchaka in branch 'default': |
Debug builds are broken after eda3716d6425: http://buildbot.python.org/all/builders/AMD64%20FreeBSD%209.x%203.x/builds/4332/steps/compile/logs/stdio |
New changeset b26d07812a8e by Serhiy Storchaka in branch 'default': |
Opps, sorry. Fixed now. Thank you Berker for fast response. |
Why not using sizeof(unsigned short) in the macro, rtbaer than 2? |
PREDICT could be optimized by having a HAS_ARG check on the constant op to decide whether we assign oparg = OPARG(word) |
Please focus this issue on documenting changes, and open new issues |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: