Skip to content
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

Spawning greenlets with libuv 20x more time #1493

Closed
HelloZeroNet opened this issue Dec 12, 2019 · 0 comments · Fixed by #1495
Closed

Spawning greenlets with libuv 20x more time #1493

HelloZeroNet opened this issue Dec 12, 2019 · 0 comments · Fixed by #1495

Comments

@HelloZeroNet
Copy link

@HelloZeroNet HelloZeroNet commented Dec 12, 2019

  • gevent version: 1.5a3 (1.4.0 crashes with idle tests, but the result is the same)
  • Python version: cPython 3.7.3 downloaded from python.org
  • Operating System: Win10

Description:

Spawning high amount of greenlets with libuv takes ~20x more time compared to libev, but it's faster if there is a gevent.idle() in the called function.

The performance is pretty unpredictable:

  • 500 greenlets: 0.016s
  • 700 greenlets: 0.313s
  • 2000 greenlets: 0.616s
Loop: <class 'gevent.libuv.loop.loop'> Pool: gevent Done in 0.641s Calls: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: gevent Done in 0.125s Calls with idle: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: 1 Done in 2.375s Calls: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: 1 Done in 0.234s Calls with idle: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: 10 Done in 0.703s Calls: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: 10 Done in 0.125s Calls with idle: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: 100 Done in 0.828s Calls: 2000
Loop: <class 'gevent.libuv.loop.loop'> Pool: 100 Done in 0.125s Calls with idle: 2000

Adding gevent.config.loop = 'libev-cext' after gevent import:

Loop: <class 'gevent.libev.corecext.loop'> Pool: gevent Done in 0.031s Calls: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: gevent Done in 0.078s Calls with idle: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: 1 Done in 0.094s Calls: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: 1 Done in 0.078s Calls with idle: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: 10 Done in 0.063s Calls: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: 10 Done in 0.047s Calls with idle: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: 100 Done in 0.063s Calls: 2000
Loop: <class 'gevent.libev.corecext.loop'> Pool: 100 Done in 0.062s Calls with idle: 2000

What I've run:

import gevent
gevent.config.loop = 'libev-cext'
import gevent.monkey
import gevent.lock
import gevent.pool
import time

gevent.monkey.patch_all(thread=False, threading=False)

num_call = 0
def simple():
	global num_call
	num_call += 1
	return "Done!"

def simple_with_idle():
	global num_call
	gevent.idle()
	num_call += 1
	return "Done!"


for pool in [gevent, gevent.pool.Pool(1), gevent.pool.Pool(10), gevent.pool.Pool(100)]:
	num_call = 0
	pool_size = getattr(pool, "size", "gevent")
	s = time.time()
	gevent.joinall([pool.spawn(simple) for i in range(2000)])
	print("Loop:", gevent.config.loop, "Pool:", pool_size, "Done in %.3fs" % (time.time() - s), "Calls:", num_call)

	num_call = 0
	s = time.time()
	gevent.joinall([pool.spawn(simple_with_idle) for i in range(2000)])
	print("Loop:", gevent.config.loop, "Pool:", pool_size, "Done in %.3fs" % (time.time() - s), "Calls with idle:", num_call)
jamadden added a commit that referenced this issue Dec 17, 2019
Current results:

| Benchmark             | gevent-libev-cext | gevent-libev-cffi             | gevent-libuv-cffi               |
|-----------------------|-------------------|-------------------------------|---------------------------------|
| gevent spawn          | 12.5 us           | 13.7 us: 1.10x slower (+10%)  | 14.2 us: 1.14x slower (+14%)    |
| gevent sleep          | 2.09 us           | 3.16 us: 1.51x slower (+51%)  | 74.4 us: 35.64x slower (+3464%) |
| geventpool sleep      | 3.46 us           | 6.20 us: 1.79x slower (+79%)  | 151 us: 43.58x slower (+4258%)  |
| geventraw spawn       | 5.34 us           | 6.54 us: 1.22x slower (+22%)  | 6.38 us: 1.19x slower (+19%)    |
| geventraw sleep       | 949 ns            | 1.66 us: 1.75x slower (+75%)  | 43.9 us: 46.25x slower (+4525%) |
| geventpool join       | 1.57 us           | 3.43 us: 2.18x slower (+118%) | 88.2 us: 56.07x slower (+5507%) |
| gevent spawn kwarg    | 14.2 us           | 12.4 us: 1.15x faster (-13%)  | 14.8 us: 1.04x slower (+4%)     |
| geventraw spawn kwarg | 7.68 us           | 8.38 us: 1.09x slower (+9%)   | 8.47 us: 1.10x slower (+10%)    |
| none spawn kwarg      | 771 ns            | 738 ns: 1.04x faster (-4%)    | 734 ns: 1.05x faster (-5%)      |

Not significant (3): geventpool spawn; none spawn; geventpool spawn kwarg

Refs #1493
jamadden added a commit that referenced this issue Dec 18, 2019
…ntroduce delays processing large batches of callbacks.

Specifically, a .3 (idle signal timer) delay as UV_RUN_ONCE would pause if there was no other active timer or IO watcher.

Now, if there are still batches of callbacks to run, we explicitly use UV_RUN_NOWAIT to only poll for IO and queue callbacks without waiting at all.

This gets the time for the synthetic greenlet-launching benchmarks to match libev-cffi and be close to libuv-cext.

Fixes #1493
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.