|
GreenletProblems
greenlets are ugly - why do I think so
Short list
DetailsAMD64 ABI brokenAs you may know, a switch() implies saving and loading processor register values, so that execution continues in the other coroutine (context) as if nothing happened. What registers' values should be saved over such a switch is defined by the platform ABI. I'm looking at the x86_64 platform, also known as AMD64. Its formal definition can be found here. Let's compare glibc's swapcontext versus greenlet 0.3 one. greenlet-0.3:
eglibc-2.10.1:
As can be seen from the above, the glibc's swapcontext() preserves 6 more integer registers, the floating point registers and the signal mask. While the signal mask is not very relevant, not preserving all register values that compilers expect to be preserved according to the ABI leads to hard-to-catch bugs and incorrect results. 2x memcpy(), realloc() and free() at each switchFrom the comparison above, you can see that every switch to/from greenlet calls memcpy() twice, realloc() and free() once. The amount of data copied depends on application, I have seen it anywhere from 20 to 100+k. In an application handling tens to hundreds of requests per second, this adds up to a significant amount of copying, allocating and freeing memory. The swapcontext() on the other hand, copies zero data. Python internals are messed upPyThreadState is an internal Python structure, tracking, among other stuff, the exception currently raised. greenlet does not keep a PyThreadState per a greenlet. This means that if an exception is raised in one greenlet, and a switch occurs to another greenlet that also raises an exception, the first exception data will be lost. This basically means that a switch() in except: clause has undefined side-effects. PyThreadState also has other interesting fields: recursion_depth, frame, tracing and profiling-related ones.
No switching from within C extensionsWell, you can't switch from within C code to another greenlet. It does not export C API, and even if it did, the problems listed above will most likely make it unusable anyway. Corollary
Each of those points hurts performance due to introducing a huge number of Python-to-C calls with all associated data marshalling. ConclusionGreenlets have a huge number of potential side-effects. One can not rely on correct execution of code when greenlets are used. Their performance is also severely limited. |
Even though swapcontext() copies zero data, it saves/restores signal handlers, doing at least one syscall per switch. greenlet does zero syscalls.
On trivial tests at least, greenlet switches significantly faster: https://github.com/redbo/python-swapcontext.
Also, there's C API now: http://packages.python.org/greenlet/#c-api-reference
Regarding exception currently raised, it is a valid concern. In Gevent, before switching out from a greenlet we store exception value and type and clear the traceback. This brings a small annoyance
However, exceptions from one greenlet are not visible in the others and not leaked during switch.
If you would actually look at the gcc source code (gcc/config/i386/i386.h) then you would see that the only registers gcc considers as not used across calls (CALL_USED_REGISTERS) are %rbx, %rbp and %r12-%r15. Greenlet doesn't need to save %rbp because it's offsetting it, just like %rsp. What I don't know is why greenlet and stackless are trying to save %rdx, though. At least on Mac OS X (python uses llvm-gcc though) it's visible that compiler just ignores %rdx.
Also, I think swapcontext is entirely different beast altogether, it needs to save additional registers because it's a bit more low-level and maybe needs to support more complex use cases.