- Notifications
You must be signed in to change notification settings - Fork 11k
[Python] gRPC AsyncIO: Improve CompletionQueue polling performance #39993
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
src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi Outdated Show resolved Hide resolved
src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi Outdated Show resolved Hide resolved
src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi Outdated Show resolved Hide resolved
cf2753b to 3d4d33c Compare src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi Outdated Show resolved Hide resolved
src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi Outdated Show resolved Hide resolved
src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi Outdated Show resolved Hide resolved
| self._loops[loop] = _BoundEventLoop(loop, self._read_socket, self._handle_events) | ||
| | ||
| cdef void _poll(self) nogil: | ||
| cdef int _poll(self) noexcept nogil: |
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 saw we call _handle_events below, which is aclassic python def method. Can we guarantee that it's not going to throw? Or noexcept only indicates that we explicitly raise from _poll itself? Not familiar with enough with Cython yet.
| # instead of delegate to any thread, the polling thread | ||
| # should handle the distribution of the event. | ||
| self._handle_events(None) | ||
| return 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.
Hm. I think I just found a better explanation to the normal compiler warning we're getting:
There’s a known performance pitfall when combining nogil and except * @cython.exceptval(check=True). In this case Cython must always briefly re-acquire the GIL after a function call to check if an exception has been raised. This can commonly happen with a function returning nothing (C void). Simple workarounds are to mark the function as noexcept if you’re certain that exceptions cannot be thrown, or to change the return type to int and just let Cython use the return value as an error flag (by default, -1 triggers the exception check).
— https://cython.readthedocs.io/en/3.1.x/src/userguide/language_basics.html
I think unlike the compile regular warning, this doc implies that we shouldn't do either one or another, not both. Compare with the compile warning warning:
Exception check after calling '_poll' will always require the GIL to be acquired. Possible solutions: 1. Declare '_poll' as 'noexcept' if you control the definition and you're sure you don't want the function to raise exceptions. 2. Use an 'int' return type on '_poll' to allow an error code to be returned. The important part is
or to change the return type to int and just let Cython use the return value as an error flag (by default, -1 triggers the exception check).
Do I understand this correctly? Here's how I interpret this:
If we JUST change the return type from void to int, we'll still be able to throw the exception as before - without changing anything other then the method signature and adding return 1 on this line to indicate success.
Then Cython doesn't have this performance penalty because it now translate the exception we raised to exception to return -1. So later it know to acquire gil and handle the exception because the return is -1.
@sreenithi Does this make sense? Again, I have virtually no experience with Cython, so please verify this for me
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.
ya this makes sense. We just tried it and verified that it works too. So he has changed the function accordingly. This also solves the problem with self._handle_events raising any errors. Because though it doesn't explicitly raise an error, there may be any run-time errors that might get missed out. Doing this will propagate that error successfully too.
Thanks for the suggestion!
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.
However, I was thinking return 0 still makes sense as the last return value in case of success. Both in C/C++ or shell scripts, 0 is the usual convention for a successful return code right. So I was thinking it makes sense to keep it 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.
I'm ok with returning 0 here, especially since we're not checking the return value. I'd argue what to return gets a bit ambiguous in the cython context, which is in between C and python.
| Removed the 'noexcept' and declared the function as 'except -1' with integer return type, which signals an error via a -1 return value without the unsafe GIL acquisition on the return path, also allowing Python exceptions to be directly raised within the _poll function |
…rpc#39993) Enable integer exception checking for performance improvement. This also resolves Asyncio segfaults with free-threaded Python. Takes care of Cython performance hint: ``` performance hint: src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi:126:22: Exception check after calling '_poll' will always require the GIL to be acquired. Possible solutions: 1. Declare '_poll' as 'noexcept' if you control the definition and you're sure you don't want the function to raise exceptions. 2. Use an 'int' return type on '_poll' to allow an error code to be returned. ``` Details in https://cython.readthedocs.io/en/3.1.x/src/userguide/language_basics.html#error-return-values: > There’s a known performance pitfall when combining `nogil` and `except *` `@cython.exceptval(check=True)`. In this case Cython must always briefly re-acquire the GIL after a function call to check if an exception has been raised. This can commonly happen with a function returning nothing (C `void`). Simple workarounds are to mark the function as `noexcept` if you’re certain that exceptions cannot be thrown, or to change the return type to `int` and just let Cython use the return value as an error flag (by default, `-1` triggers the exception check). Closes grpc#39993 COPYBARA_INTEGRATE_REVIEW=grpc#39993 from aditya-1112:aio-segfault-fix bdc3310 PiperOrigin-RevId: 796464140
…rpc#39993) Enable integer exception checking for performance improvement. This also resolves Asyncio segfaults with free-threaded Python. Takes care of Cython performance hint: ``` performance hint: src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi:126:22: Exception check after calling '_poll' will always require the GIL to be acquired. Possible solutions: 1. Declare '_poll' as 'noexcept' if you control the definition and you're sure you don't want the function to raise exceptions. 2. Use an 'int' return type on '_poll' to allow an error code to be returned. ``` Details in https://cython.readthedocs.io/en/3.1.x/src/userguide/language_basics.html#error-return-values: > There’s a known performance pitfall when combining `nogil` and `except *` `@cython.exceptval(check=True)`. In this case Cython must always briefly re-acquire the GIL after a function call to check if an exception has been raised. This can commonly happen with a function returning nothing (C `void`). Simple workarounds are to mark the function as `noexcept` if you’re certain that exceptions cannot be thrown, or to change the return type to `int` and just let Cython use the return value as an error flag (by default, `-1` triggers the exception check). Closes grpc#39993 COPYBARA_INTEGRATE_REVIEW=grpc#39993 from aditya-1112:aio-segfault-fix bdc3310 PiperOrigin-RevId: 796464140
…rpc#39993) Enable integer exception checking for performance improvement. This also resolves Asyncio segfaults with free-threaded Python. Takes care of Cython performance hint: ``` performance hint: src/python/grpcio/grpc/_cython/_cygrpc/aio/completion_queue.pyx.pxi:126:22: Exception check after calling '_poll' will always require the GIL to be acquired. Possible solutions: 1. Declare '_poll' as 'noexcept' if you control the definition and you're sure you don't want the function to raise exceptions. 2. Use an 'int' return type on '_poll' to allow an error code to be returned. ``` Details in https://cython.readthedocs.io/en/3.1.x/src/userguide/language_basics.html#error-return-values: > There’s a known performance pitfall when combining `nogil` and `except *` `@cython.exceptval(check=True)`. In this case Cython must always briefly re-acquire the GIL after a function call to check if an exception has been raised. This can commonly happen with a function returning nothing (C `void`). Simple workarounds are to mark the function as `noexcept` if you’re certain that exceptions cannot be thrown, or to change the return type to `int` and just let Cython use the return value as an error flag (by default, `-1` triggers the exception check). Closes grpc#39993 COPYBARA_INTEGRATE_REVIEW=grpc#39993 from aditya-1112:aio-segfault-fix bdc3310 PiperOrigin-RevId: 796464140
Enable integer exception checking for performance improvement. This also resolves Asyncio segfaults with free-threaded Python.
Takes care of Cython performance hint:
Details in https://cython.readthedocs.io/en/3.1.x/src/userguide/language_basics.html#error-return-values: