By now, everybody would have optimized it back to normal (or better!).
By now, everybody would have optimized it back to normal (or better!).
And doing it implicitly would require stupidly fine-grained locks on all objects, destroying any performance gains; there's no way to "optimize" that to GIL performance.
As an example take the multiprocessing.pool.ThreadPool.map function. Most of the use-cases of that function only read from the parent threads memory once to pass the function arguments. After that the thread may very well spend a lot of time only reading memory it reserved itself. It's rather wasteful to have that thread wait on the GIL.
Of course I haven't got an obvious solution either but it seems to me that for at least a subset of the uses of threading you could work around the GIL without breaking python too badly.
Perhaps split the GIL into a per-thread Thread-interpreter-lock. That way each object can simply be annotated with which lock it belongs to. That way you still get proper atomicity like you're used to in python, but a thread might also actually run concurrently a lot of the time if it only touches objects it created itself.
Subinterpreters gets you that (eventually; not in v1 apparently).
> Perhaps split the GIL into a per-thread Thread-interpreter-lock. That way each object can simply be annotated with which lock it belongs to. That way you still get proper atomicity like you're used to in python, but a thread might also actually run concurrently a lot of the time if it only touches objects it created itself.
I don't think this scheme would work especially well (or not better than subinterpreters). Message passing still requires copying, or some way to quiesce other threads and recursively change lock ownership of an object graph. To avoid deadlocks, at least one thread will need to drop its own lock to acquire the thread lock of objects owned by a 2nd thread. I'm not sure you could do that and preserve the legacy global atomicity behavior, and it would by definition be slower and bloatier on any individual thread than the existing GIL behavior.
There have been several attempts to bring new science to bear on solving the refcounting problem, I don't think any of them ever got far enough to be taken seriously
A significant chunk of Python performance is derived from optimizations made possible by the GIL - for example, so long as it is held, no additional locks are necessary to allocate a small object, unless that entails asking the system allocator for more memory
Considering that Python has some of the worst performance around, does it really matter? Other languages don't have this problem, and they're orders of magnitude faster.
I think the core team over-estimated the benefits of the change, hence people's reluctance to pay the cost. I suspect that "threading like Go" might have been a benefit more worth paying the cost.
I believe it's generally agreed that the cost of the transition was surprisingly high. Hindsight is 20/20.
> Removing the GIL would have increased the cost, but also the benefit.
The only attempts I'm aware of resulted in a significant (~50%) slowdown in single-threaded code execution due to the requirement of adding in so many more locks elsewhere (removing the GIL doesn't remove the need for the locks around the various critical sections). Sure maybe it would result in better performance for some programs, but I'm not even sure it would result in something better than a cleaner design. In my experience, the portions of code that could make use of the threading could just be moved to a C/C++-extension and make use of it there (though in 99% of cases I wouldn't go that far and just stick to e.g. numpy). The examples of the code that really should stay in python, but also should make use of concurrency seem to usually sit quite nicely in the pypy framework.
That's not to say that removing the GIL wouldn't be nice, but I think the need for it is often overstated.
I think that as time has passed, the inability to simply utilize more than one core becomes more of a problem. The transition to supporting async serves mostly to highlight its inability to use a thread pool like Go, as an example.
While I agree that C extensions (and especially, the standard extensions, like eg. socket) really help a lot to limit the impact of of the GIL, I think the usual advice to "just move it to a C extension" or "use multiple processes" are kinda ridiculous to anyone who's not used to the situation.
Unfortunately, fixing it will almost certainly require breaking the C API, and we've only just about recovered from the 2->3 breakage: I don't think the language can afford to do it again, perhaps ever.
Which is why I think it's a shame that the last transition didn't actually achieve more ...
Folks who delayed adoption until this year had a long and glorious period of stability, and are now waking up to a shitshow of incompatibility and half-adopted features.
To the extent that this isn’t wrong, it’s because they’re years behind on testing and upgrading from unsupported libraries. The switch isn’t hard on a relatively clean project.
But what I said about half-adopted features is due to my experience with rather large bodies of code -- they're not "relatively clean" in any sense, and I often find myself doing long-overdue modernization that hinders my productivity