2014-11-13 0:18 GMT+01:00 Paul Sokolovsky <
pmi...@gmail.com>:
> (...) do you think that it might be possible to go step
> further and allow scheduling a new coroutine by just yielding a
> generator instance? In other words:
(I don't see how it is related to create_task. create_task is nothing
new, it's just an helper to not have to pass the loop as a parameter.)
"yield from" is more efficient because it requires less iterations in
the event loop.
In Trollius, you *can* (and must) write "yield From(coro)". From(obj)
just returns obj, it's just here to ease transition to asyncio later,
so in fact it's just" yield coro". Because of the syntax of coroutines
(yield vs yield-from), Trollius is less efficient.
Trollius calls _run_once() (one iteration of the event loop) at least
2x more times. The factor depends on the depth of your coroutine.
asyncio example:
---
import asyncio
class EventLoop(asyncio.SelectorEventLoop):
_iteration = 0
def _run_once(self):
self._iteration += 1
super(EventLoop, self)._run_once()
def func4():
# dummy generator
yield from []
def func3():
yield from func4()
def func2():
yield from func3()
def func1():
yield from func2()
yield from func2()
loop = EventLoop()
loop.run_until_complete(func1())
print("iteration# = %s" % loop._iteration)
---
Number of iterations:
- func4() -> 2
- func3() -> 2
- func2() -> 2
- func1() -> 2
Trollius example:
---
import trollius
class EventLoop(trollius.SelectorEventLoop):
_iteration = 0
def _run_once(self):
self._iteration += 1
super(EventLoop, self)._run_once()
def func4():
# dummy generator
yield None
def func3():
yield func4()
def func2():
yield func3()
def func1():
yield func2()
yield func2()
loop = EventLoop()
loop.run_until_complete(func1())
print("iteration# = %s" % loop._iteration)
---
Number of iterations:
- func4() -> 3
- func3() -> 5
- func2() -> 7
- func1() -> 16
Now imagine than calling _run_once() takes 1 ms. With asyncio, the
program will takes 2 ms. With Trollius, it takes between 3 and 16 ms,
depending on the "depth" of the coroutine.
To understand why the number of iterations has a cost, you also have
to know that Trollius creates a new task for each layer of coroutine.
For example func3() creates a task to execute func4(), and func2()
will indirectly creates 2 tasks.
When a coroutine waits for a new task, it has to register itself with
add_done_callback(). When the waited task is done, the _wakeup()
callback is scheduled. Later, _wakeup() calls _step(). etc.
Well, dig into Task._step() of the Trollius project to try to
understand how yield-from solved real issues and makes the code more
efficient and faster ;-)
It was a pain to write the code to support "yield" instead of
"yield-from" in Trollius...
Victor