-
-
Notifications
You must be signed in to change notification settings - Fork 34k
bpo-18835: Add PyMem_AlignedAlloc() #4089
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
Conversation
Doc/c-api/memory.rst
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do people have to provide aligned_alloc and aligned_free? Or can they leave those pointers NULL and get a default implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, you must provide all allocator functions, included aligned_alloc and aligned_free.
Technically, we can implement a fallback, but I'm not sure that I want to do that :-)
|
AppVeyor crashed with a heap corruption (exception c0000374) on: |
|
TODO: _PyMem_DebugRawAlignedAlloc() is currently a copy of _PyMem_DebugRawAlloc(). Instead, it should trap such bug: PyMem_Free() called on a memory blocked allocated by PyMem_AlignedAlloc(). Maybe it should store the alignment as well, to dump it in a fatal error message? |
I rebuild Python from scratch in Debug|x64: I am unable to reproduce the bug. Strange. |
Objects/obmalloc.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I understand the man page, posix_memalign() can return other error codes, which are not necessarily negative. I suggest != 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
Objects/obmalloc.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like an accidental leftover from an earlier version. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The manual page doesn't explicitly say that posix_memalign() never returns NULL on success. Whereas NULL is documented as an error condition for aligned_alloc(), memalign(), valloc() et pvalloc(). Why does posix_memalign() return an integer if it cannot return NULL on success? In case of doubt, I chose to explicitly reject NULL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least, size=0 is replaced with size=1, as do in other Python memory allocators, to avoid returning NULL. posix_memalign() manual page:
"If size is 0, then the value placed in *memptr is either NULL, or a unique pointer value that can later be successfully passed to free(3)."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_memalign.html is clearer:
"Upon successful completion, posix_memalign() shall return zero; otherwise, an error number shall be returned to indicate the error."
Why does posix_memalign() return an integer if it cannot return NULL on success?
Someone just thought it would be a good idea to return ENOMEM etc. directly. Perhaps
just to avoid the errno dance.
The opengroup wording sounds like "success if and only if the return value is 0".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed: I replaced the test with an assertion ;-)
Done. PyMem_Free(PyMem_AlignedAlloc()) now fails as expected. I simply chose to use uppercase for the API identifier. I added an unit test to test exactly this. I also documented newly added functions. I'm not sure of my documentation on the alignment: Windows requires alignment to be "an integer power of 2". Maybe we should suggest to always use powers of 2 to best portability? I would be surprised if PyMem_AlignedAlloc() fails with an alignment equals to a power of 2. |
Objects/obmalloc.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the change. Still, why would the function return NULL on success if size is guaranteed to be > 0?
posix_memalign() would be spectacularly broken if it did that.
So I find the comment and assert() confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the comment but kept the assertion just in case.
I prefer an assertion to get an helpful error message rather than a crash in a random part of the code :-(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There would be no crash. People have to check the return value
of _PyMem_RawAlignedAlloc() for NULL in any case.
The thing is: Unusual asserts() have a cost for people reading the code. The reader wonders "does the Python core developer know something I don't".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"There would be no crash. People have to check the return value of _PyMem_RawAlignedAlloc() for NULL in any case."
If posix_memalign() returns NULL on success, but your code raises a MemoryError exception if the pointer is NULL, you leak memory and your fails whereas the allocation succeeded.
This case should never occur. I prefer to be super careful and make sure that posix_memalign() never returns NULL on success.
Unusual asserts() have a cost for people reading the code.
Do you want me to add a comment explaining the assertion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please add something like:
"This should never happen since we make sure that size > 0".
Also, pedantically, if the assert() is there then I'd use void *ptr = NULL; in the declaration. Because if we check "can never happen" conditions we might as well check that posix_memalign() sets the pointer at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
According to the discussion on https://bugs.python.org/issue18835 it seems like the feature (aligned memory allocations) is needed, so I finished my implementation:
The PR is now ready for a review. |
Doc/c-api/memory.rst
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we enforce C11?
http://en.cppreference.com/w/c/memory/aligned_alloc
"size - number of bytes to allocate. An integral multiple of alignment"
posix_memalign and _aligned_malloc don't care about the multiple though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took 17 years to support ISO C99 in CPython 3.6 :-D Python doesn't have to enforce anything. I prefer a thin wrapper on top of system allocator.
The question is more on the documentation.
posix_memalign and _aligned_malloc don't care about the multiple though.
In that case, I suggest to not say anything about the alignment of the size argument.
|
I asked a colleague who works on the glibc: posix_memalign() cannot return NULL on success in the glibc. Extract: By the way, posix_memalign() rejects alignment == 0 and alignement which is not a power of 2: Oh. PyObject_AlignedAlloc() doesn't reject allignement == 0. Should we reject it as well? (Return NULL in that case.) |
|
Oh wow, on Windows, _aligned_malloc() "simply" crash with abort() if called with alignment=0 or alignment=3 (not a power of 2)! |
|
I added a new commit to check alignment. New documentation: |
|
On Fri, Oct 27, 2017 at 05:59:09AM -0700, Victor Stinner wrote:
Oh wow, on Windows, _aligned_malloc() "simply" crash with abort() if called with alignment=0 or alignment=3 (not a power of 2)!
Haha, that's old school. :) I think it is not entirely unreasonable for a
high performance function.
I'd do all the Posix checks on Windows.
if (alignment % sizeof (void *) != 0
|| !powerof2 (alignment / sizeof (void *))
|| alignment == 0)
Then the Posix semantics are sort of official.
Object_AlignedAlloc() doesn't reject allignement == 0. Should we reject it as well? (Return NULL in that case.)
Yes, I think it should reject align==0 and !powerof2(align).
|
Oh. I checked and posix_memalign(2, 100) fails with EINVAL even if alignment (=2) is a power of 2. I modified my PR to also check that in check_alignment(). I propose to always check the alignment in Python even if the glibc posix_memalign() fails nicely with EINVAL, to get the same behaviour on all platforms. For example, with my current PR, PyMem_RawAlignedAlloc(0, 100) returns NULL in release mode, rather than crashing (on abort()), on Windows. |
|
@skrah: Does the PR look better with the new alignment checks? |
|
@pitrou: Would you mind to review this big PR please? |
Objects/obmalloc.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move check_alignment(alignment) only in this path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, it makes sense. Done.
I also merged master into this PR.
Objects/obmalloc.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we should trust posix_memalign. :)
The posix_memalign() function shall fail if:
[EINVAL]
The value of the alignment parameter is not a power of two multiple of sizeof( void *).
[ENOMEM]
There is insufficient memory available with the requested alignment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I'd put it in the Windows path.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
"I really think we should trust posix_memalign."
I didn't want to rely on the glibc. Since you confirmed that EINVAL is part of the POSIX standard, I agree to rely on it.
|
I wish the call chains were easier to understand. This one ends up calling the |
Objects/obmalloc.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alignment <= ALIGNMENT && size <= SMALL_REQUEST_THRESHOLD
should fix the call chain problem.
Wow, my code was completely wrong :-) Before fixing this bug, I wrote an unit test to make sure that PyMem_AlignedAlloc() respects the alignment constraint... Ooops again, _PyMem_DebugRawAlignedAlloc() was wrong too: it didn't align correctly the pointer... I fixed that too. Sometimes, it helps to write unit tests ;-) |
|
Oh ok, I understood the failure on Windows. _PyObject_AlignedAlloc() still fell back on the raw allocator rather than the aligned allocator, when pymalloc failed to allocated memory. I had to rewrite the pymalloc allocator to give control to the caller how to fall back on another allocator. |
|
This PR became too big, so I extracted two changes:
Once these PR will be merged, I will rebase this PR on top of master. |
|
Oh, the Travis CI failed again with https://bugs.python.org/issue31910 I restarted the job. |
* Add aligned_alloc and aligned_free fields to PyMemAllocatorEx * Rename PyMemAllocatorEx structure to PyMemAllocatorEx2 to make sure that C extensions are upgraded to fill the new aligned_alloc and aligned_free fields * Add 6 new functions: * PyMem_RawAlignedAlloc() * PyMem_RawAlignedFree() * PyMem_AlignedAlloc() * PyMem_AlignedFree() * PyObject_AlignedAlloc() * PyObject_AlignedFree()
|
I rebased my PR on top of master to get the fix for https://bugs.python.org/issue31910 |
|
https://bugs.python.org/issue18835 has been rejected, so I closed this PR. |
Add aligned_alloc and aligned_free fields to PyMemAllocatorEx
Rename PyMemAllocatorEx structure to PyMemAllocatorEx2 to make sure
that C extensions are upgraded to fill the new aligned_alloc and
aligned_free fields
Add 6 new functions:
https://bugs.python.org/issue18835