{"id":144,"date":"2015-11-30T16:35:18","date_gmt":"2015-12-01T00:35:18","guid":{"rendered":"http:\/\/www.howardklein.net\/?p=144"},"modified":"2015-11-30T16:35:18","modified_gmt":"2015-12-01T00:35:18","slug":"evaluating-python-coroutine-options","status":"publish","type":"post","link":"https:\/\/www.howardklein.net\/?p=144","title":{"rendered":"Evaluating Python Coroutine Options"},"content":{"rendered":"<p>A couple of previous posts (<a href=\"https:\/\/www.howardklein.net\/?p=41\">here <\/a>and <a href=\"https:\/\/www.howardklein.net\/?p=46\">here<\/a>) presented three Python-based alternatives for expressing process or agent-based behavior that takes place over (or blocks for) simulated time: generators, greenlets and tasklets.<\/p>\n<p>So which approach is best?\u00a0 There are (at least!) three factors to consider:<\/p>\n<ul>\n<li>Ease of use &#8211; ease-of-coding, expressiveness, maintainability.<\/li>\n<li>Eco-system factors \u2013 compatibility with the rest of my chosen Python tool chain, now and in the future.<\/li>\n<li>Performance.<\/li>\n<\/ul>\n<p>Let\u2019s have a look, starting with ease-of-use.<br \/>\n<!--more--><\/p>\n<h2><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1\" height=\"1\" class=\"alignnone size-full wp-image-145 \" src=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565cdc9e5bd08.gif?resize=1%2C1&#038;ssl=1\" alt=\"\" \/>Ease of Use<\/h2>\n<p>I\u2019ll start by considering a process-oriented model that simulates a single-server queuing system using greenlets or tasklets.\u00a0 We can implement a base <code>SimProcess<\/code> class with the following methods (see this <a href=\"https:\/\/www.howardklein.net\/?p=52\">post <\/a>for the gory details):<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef acquire(self, server): &amp;nbsp; &amp;nbsp; #&amp;nbsp;blocks until the server is available and assigned to the caller\r\ndef wait_for(self, delaytime): #&amp;nbsp;blocks for the specified amount of simulated time\r\n<\/pre>\n<p>Our single server model then just needs to implement a <code>SimProcess<\/code> subclass with attributes <code>server<\/code> and <code>servicetime<\/code>, and an <code>execute()<\/code> method that calls <code>acquire()<\/code> and <code>wait_for()<\/code>:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef execute(self):\r\n    self.acquire(self.server)\r\n    self.wait_for(self.servicetime)\r\n    self.server.release(self)\r\n<\/pre>\n<p>In contrast, for a generator-based implementation, execute() is a generator function, as are the base class acquire() and wait_for() functions. execute() would then look something like this:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ndef execute(self):\r\n    yield from self.acquire(self.server)\r\n    yield from self.wait_for(self.servicetime)\r\n    self.server.release(self)\r\n<\/pre>\n<p>The generator-based implementation is at least a bit messier-looking with the required <code>yield from<\/code> statements.\u00a0 Is that difference significant from a clarity and ease-of-coding perspective?\u00a0 Perhaps not, but the advantages of the greenlet\/tasklet approach do become more pronounced as model complexity increases.<\/p>\n<p>A greenlet\/tasklet process class allows us to create and call other methods (subprocesses) that can also block via <code>acquire()<\/code> and\/or <code>wait_for()<\/code> calls.\u00a0 A generator-based process class would have to use sub-generators instead, and then we would need to remember to call these using the<code> yield from<\/code> syntax \u2013 and so on down the entire calling tree (should there be sub-sub-processes).\u00a0 Forgetting to include <code>yield from<\/code> anywhere in that calling tree breaks the model, and it might do so silently.\u00a0 (Calling a generator function without <code>yield from<\/code> simply creates and returns the generator, without executing it.\u00a0 Is it possible to detect this error at run time?\u00a0 Perhaps, but I haven\u2019t figured out how.)<\/p>\n<p>We might also be tempted to encapsulate some non-blocking behaviors in regular methods or functions, called by the <code>execute()<\/code> generator.\u00a0 If we later add blocking behavior to one of those methods (directly or indirectly), that method becomes a generator, and we need to modify each and every caller of that method.<\/p>\n<p>Finally, I&#8217;ll note that while simple queuing models can be implemented with only these two defined blocking operations, we&#8217;ll inevitably add more to support different and more complex modeling scenarios &#8211; all of which must be generators in a generator-based framework.<\/p>\n<p>In short, the greenlet\/tasklet approach provides a cleaner and more natural mechanism for invoking blocking or potentially blocking operations.\u00a0 For relatively simple models, this might look like syntactic sugar, but the real benefits increase with the size and complexity of our models.<\/p>\n<p>I\u2019ve been discussing, over the course of a number of posts now, the various ways of implementing coroutines in Python, and how each of these mechanisms can be used to express \u201cbehavior over time\u201d in a discrete event simulation model. \u00a0To recap:<\/p>\n<ul>\n<li>I <a href=\"https:\/\/www.howardklein.net\/?p=41\">started <\/a>by presenting Python generators, followed by <a href=\"https:\/\/www.howardklein.net\/?p=46\">greenlets and tasklets<\/a>.<\/li>\n<li>I dove into the details through a <a href=\"https:\/\/www.howardklein.net\/?p=52\">discussion of the code<\/a> for a simple (M\/M\/1 queue), but complete simulation model \u2013 using each of those three options<\/li>\n<li>In my <a href=\"https:\/\/www.howardklein.net\/?p=76\">last post<\/a>, I compared the three approaches from the ease-of-use, expressiveness and maintainability perspective. Greenlets and tasklets came out on top, particularly as model complexity increases.<\/li>\n<\/ul>\n<h2>Ecosystem<\/h2>\n<p>Next, I\u2019ll look what I call (for lack of a better term) \u201cEcosystem\u201d factors.<\/p>\n<p>Up front, I\u2019ll have to acknowledge that I\u2019m going to simply ignore the \u201cPython 2 vs. Python 3\u201d debate.\u00a0 For some, I realize that this is a caveat so large that it renders the rest of the discussion moot &#8211; but it\u2019s also a quasi-religious issue that is well-covered by theologians far more qualified than myself.\u00a0 Suffice to say, I\u2019ve chosen Python 3.x. If you\u2019re a Python 2 devotee I certainly can understand why, but I\u2019ve decided to make my life easier by choosing one side of that fence, and 3.x is the side I&#8217;m choosing.<\/p>\n<p>With that aside, I\u2019m trying to address questions like:<\/p>\n<ul>\n<li>Is the approach (generator\/greenlet\/tasklet) available in or for the Python distribution I want to use?<\/li>\n<li>Is the approach compatible with other third-party packages or tools I wish to use?<\/li>\n<li>Is the approach future-proof? Or in other words, will it still be available to me as the Python ecosystem evolves?<\/li>\n<\/ul>\n<p>Let\u2019s look at these in turn\u2026<\/p>\n<h4>Is the approach available in or for the Python distribution I want to use?<\/h4>\n<p>Generators are the winner here, assuming that you are able and willing to use Python language version 3.3 or above, as that\u2019s when the <code>yield from<\/code> syntax was introduced.\u00a0 As a core language feature, every implementation of Python 3.3 and above includes generator and sub-generator support.<\/p>\n<p><a href=\"https:\/\/pypi.python.org\/pypi\/greenlet\" target=\"_blank\">Greenlets <\/a>are available as a third-party package for all relatively recent CPython distributions, including every Python 3.x distribution.\u00a0 Greenlets are also supported by <a href=\"http:\/\/pypy.org\/\" target=\"_blank\">PyPy<\/a>, a stand-alone Python distribution.\u00a0 As far as I know, greenlet support is not available for any other mainstream Python distribution (e.g. Jython or IronPython), either built-in or through a third-party package.<\/p>\n<p>Tasklets are available through two stand-alone distributions, <a href=\"http:\/\/www.stackless.com\/\" target=\"_blank\">Stackless Python<\/a> and <a href=\"http:\/\/pypy.org\/\" target=\"_blank\">PyPy<\/a>. There, as far as I know, no tasklet implementations available that are compatible with any other mainstream Python distribution.\u00a0 As far as language version is concerned, there are, as of this writing, Stackless Python distributions for each Python version through 3.4.2.\u00a0 PyPy is a bit further behind; as of this writing, it\u2019s most recent Python3-compatible release implements Python 3.2.5.<\/p>\n<h4>Is the approach compatible with other third-party packages or tools I wish to use?<\/h4>\n<p>Generators, being a core language feature, are pretty much by definition compatible with any and all third-party packages and tools.\u00a0 Provided you are using a CPython distribution, the same can be said of greenlets, since they are provided as an extension package to CPython.<\/p>\n<p>The answer is less clear-cut for tasklets, as their use implies a stand-alone Python distribution (either Stackless Python or PyPy).\u00a0 Most prominently, <a href=\"https:\/\/bitbucket.org\/pypy\/numpy\" target=\"_blank\">PyPy does not provide full\u00a0support for the heavily-used numpy module<\/a>.\u00a0 Stackless, being more closely related to the standard CPython distribution, generally seems (at least to me \u2013 I have to acknowledge I haven\u2019t looked terribly closely) to be more compatible with extension modules than PyPy.\u00a0 As far as tools go, some IDEs support Stackless Python better than others, as noted <a href=\"http:\/\/stackless.readthedocs.org\/en\/latest\/library\/stackless\/debugging.html\" target=\"_blank\">here<\/a>.\u00a0I can\u2019t speak to PyPy in this regard.<\/p>\n<h4>Is the approach future-proof?\u00a0 Or in other words, will it still be available to me as the Python ecosystem evolves?<\/h4>\n<p>As far as generators go, the answer is clearly \u201cyes\u201d.\u00a0 It is a core language feature that has recently been enhanced. A new (as of Python 3.4) extension module, <a href=\"https:\/\/docs.python.org\/3\/library\/asyncio.html\" target=\"_blank\">asyncio<\/a>, has been built on top of this enhanced generator functionality.\u00a0 It\u2019s not going away any time soon.<\/p>\n<p>As an independently maintained extension package, greenlet\u2019s future is not quite as clear-cut. There are other third-party extensions that make use of greenlet, but several of them focus on asynchronous I\/O and could conceivably be replaced by asyncio-based solutions at some point in the future.\u00a0 The greenlet package is certainly stable and has been continuously updated for new CPython releases.\u00a0 Will that continue indefinitely?\u00a0 That\u2019s a more difficult question to answer. If greenlets were the <em>only<\/em> third-party extension that I used, I might be concerned by that possibility. \u00a0But it isn&#8217;t, and I&#8217;m not.<\/p>\n<p>While reading the tea leaves is always a challenge, I get the general impression that much Stackless Python development effort and interest is <a href=\"http:\/\/stackless.readthedocs.org\/en\/latest\/stackless-python.html#history\" target=\"_blank\">moving to PyPy<\/a>\u00a0(and <a href=\"http:\/\/stackoverflow.com\/questions\/588958\/what-are-the-drawbacks-of-stackless-python\/590412#590412\" target=\"_blank\">here<\/a>).\u00a0PyPy is certainly an interesting option, particularly for performance-critical applications such as simulation.\u00a0 Is PyPy under active development?\u00a0 Certainly.\u00a0 Does it rival CPython in terms of user base?\u00a0 Not yet.\u00a0 Will it eventually?\u00a0 Your guess is as good as (and quite possibly better than) mine.<\/p>\n<p>In summary, there are absolutely no ecosystem- based concerns with simulation based on generators; while nothing is forever, they are likely to outlive me. There are no real concerns right now with greenlets, but its future comes with fewer guarantees.\u00a0 Tasklet support does require a separate Python distribution, which inevitably brings a number of ecosystem related issues into play.<\/p>\n<p>All of which leads to\u2026<\/p>\n<h2>Performance<\/h2>\n<p>When it comes to simulation \u2013 or, at least, a simulation framework \u2013 there\u2019s no such thing as \u201cfast enough\u201d.\u00a0 We always want to build larger, more complex models.\u00a0 We always want to run more experiments and more replications.\u00a0 In short, even if our current models and analyses run \u201cfast enough\u201d, we always want the ability to do more.\u00a0 Simulation applications will, sooner or later, consume all of the compute power made available to them.\u00a0 Performance isn\u2019t the only factor to be considered when choosing or building a simulation framework, but it most definitely does matter.\u00a0 So we can and should look at the performance implications of our three options.<\/p>\n<h3>Preliminaries<\/h3>\n<p>Let\u2019s start by looking at simple generators, greenlets and tasklets in isolation. Below is the code for a simple generator, along with a function that instantiates one of those generators and runs it to the first yield statement:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\"> \r\ndef generatorFunc(): \r\n    while True: \r\n        yield \r\n\r\ndef startGenerator(): \r\n    generator = generatorFunc() \r\n    next(generator) \r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>And the corresponding code for greenlets\u2026:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nmain_greenlet = greenlet.getcurrent()\r\n            \r\ndef greenletFunc():\r\n    while True:\r\n        main_greenlet.switch()\r\n        \r\ndef startGreenlet():\r\n    gr = greenlet(greenletFunc)        \r\n    gr.switch()\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>and tasklets:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nchannel = stackless.channel()\r\n\r\ndef taskletFunc():\r\n    while True:\r\n        channel.receive()\r\n        \r\ndef startTasklet():\r\n    tasklet = stackless.tasklet(taskletFunc)()\r\n    tasklet.run()\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>I timed <code>startGenerator()<\/code> and <code>startGreenlet()<\/code> on my (Windows) laptop (system specs: : i7-4600U, 2.1 GHz, 8Gb memory) using CPython 3.4.3.\u00a0 I also timed <code>startTasklet()<\/code> using Stackless Python 3.3.5.\u00a0 For what it\u2019s worth, the timings were generated using the <a href=\"https:\/\/docs.python.org\/3.5\/library\/timeit.html#module-timeit\" target=\"_blank\">timeit <\/a>module\u2019s <code>Timer.repeat()<\/code> method, with n = 100,000, using the minimum of the ten repetitions. The results are shown below (the right-hand vertical axis presents a normalized scale, with the generator time being 1.0):<\/p>\n<p id=\"MpgIMfv\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"481\" height=\"289\" class=\"size-full wp-image-109 aligncenter\" src=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565632752c82c.png?resize=481%2C289&#038;ssl=1\" alt=\"\" srcset=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565632752c82c.png?w=481&amp;ssl=1 481w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565632752c82c.png?resize=300%2C180&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565632752c82c.png?resize=150%2C90&amp;ssl=1 150w\" sizes=\"auto, (max-width: 481px) 100vw, 481px\" \/><\/p>\n<p>Next, I looked at the cost of subsequent calls on these three coroutine implementations, creating test objects:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\ntest_generator = generatorFunc() \r\ntest_greenlet =  greenlet(greenletFunc)\r\ntest_tasklet = stackless.tasklet(taskletFunc)()\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>and then timing each of the following calls:<\/p>\n<pre class=\"brush: python; title: ; notranslate\" title=\"\">\r\nnext(test_generator)\r\ntest_greenlet.switch()\r\nchannel.send(0)\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>The results are shown below, again in both microseconds\/call and with next(generator) calls normalized to 1.0.\u00a0 For entertainment purposes, I also timed and included a plain-old-function call (coming in as about seven times faster than a generator):<\/p>\n<p id=\"yyavLtH\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"481\" height=\"289\" class=\"size-full wp-image-110 aligncenter\" src=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656339e372eb.png?resize=481%2C289&#038;ssl=1\" alt=\"\" srcset=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656339e372eb.png?w=481&amp;ssl=1 481w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656339e372eb.png?resize=300%2C180&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656339e372eb.png?resize=150%2C90&amp;ssl=1 150w\" sizes=\"auto, (max-width: 481px) 100vw, 481px\" \/><\/p>\n<p>So far, it looks like generators are the clear winner. They are created and initially run almost twice as fast as tasklets, and more than four times faster than greenlets. Their relative performance on subsequent calls is even better \u2013 three times faster than tasklets, six times faster than greenlets.<\/p>\n<h3>Performance\u00a0of a\u00a0&#8220;Real&#8221; Simulation<\/h3>\n<p>But I\u2019m more interested in the performance of an actual simulation. Let\u2019s look at the performance of M\/M\/1 simulations (as described <a href=\"https:\/\/www.howardklein.net\/?p=52\">here<\/a>), with generator, greenlet and tasklet-based process implementations. (This time, each timing result is the minimum CPU from five simulation runs, each run for one million simulated time units):<\/p>\n<p id=\"mNFTpBw\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"481\" height=\"289\" class=\"size-full wp-image-113 aligncenter\" src=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635325bb99.png?resize=481%2C289&#038;ssl=1\" alt=\"\" srcset=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635325bb99.png?w=481&amp;ssl=1 481w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635325bb99.png?resize=300%2C180&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635325bb99.png?resize=150%2C90&amp;ssl=1 150w\" sizes=\"auto, (max-width: 481px) 100vw, 481px\" \/><\/p>\n<p>Out of curiosity (or perhaps just because of too much time on my hands), I ran the generator-based simulation using both CPython and Stackless.\u00a0 That model ran about 6% more slowly under Stackless, though I\u2019m not sure what, if any, general conclusions can be drawn from that result.\u00a0 (I also ran the simple generator timing tests under both CPython and Stackless, and the differences there were negligible.)\u00a0 The tasklet-based model was about 18% slower than the CPython generator-based model, while greenlet-based model was about 10% slower.<\/p>\n<h3>Scalability<\/h3>\n<p>What about larger models? Are there any differences (e.g. in memory footprint) that would change these results if we scale up the number of concurrent process objects in a simulation?\u00a0 I tried to evaluate that scenario by modifying my model to simulate multiple, concurrent M\/M\/1 queues.\u00a0 To simulate <em>n<\/em> queues, this simulation starts by creating <em>n<\/em> <code>Server<\/code> instances and <em>n<\/em> <code>ArrivalEvents<\/code> (one per server).\u00a0 I then ran simulations for up to 10,000 concurrent queues (with each simulation running for 10,000 simulated time units). \u00a0Again, I compared implementations using generators (both in CPython and Stackless), greenlets, and tasklets:<\/p>\n<p id=\"KiVPndm\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"481\" height=\"327\" class=\"size-full wp-image-114 aligncenter\" src=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635e0c468b.png?resize=481%2C327&#038;ssl=1\" alt=\"\" srcset=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635e0c468b.png?w=481&amp;ssl=1 481w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635e0c468b.png?resize=300%2C204&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_565635e0c468b.png?resize=150%2C102&amp;ssl=1 150w\" sizes=\"auto, (max-width: 481px) 100vw, 481px\" \/><\/p>\n<p>A normalized graph illustrates with a bit more precision:<\/p>\n<p id=\"pSgsWmM\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"481\" height=\"317\" class=\"size-full wp-image-115 aligncenter\" src=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656360e892bb.png?resize=481%2C317&#038;ssl=1\" alt=\"\" srcset=\"https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656360e892bb.png?w=481&amp;ssl=1 481w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656360e892bb.png?resize=300%2C198&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.howardklein.net\/wp-content\/uploads\/2015\/11\/img_5656360e892bb.png?resize=150%2C99&amp;ssl=1 150w\" sizes=\"auto, (max-width: 481px) 100vw, 481px\" \/><\/p>\n<p>Our previous results appear to scale pretty well; greenlet and tasklet-based implementations are generally 10-15% slower than generators, regardless of scale.\u00a0 Again I\u2019m not really sure what to make of the differences running identical generator-based code on two Python distributions (CPython and Stackless).<\/p>\n<h3>So&#8230;<\/h3>\n<p>What does this all mean?\u00a0 Generators are 2-6 times faster than greenlets and tasklets, but most of that advantage disappears in the M\/M\/1 simulations; clearly the bulk of the process time is being spent elsewhere.\u00a0 Throw in more extensive output data collection and other overhead activities of a \u201creal\u201d simulator, and I suspect that the generator-based performance advantage almost disappears completely \u2013 well into the single digit percentages, if I had to guess.<\/p>\n<p>Are there cases where the differences would be much more significant?\u00a0 I can\u2019t absolutely rule that out &#8211; but based on my rough scalability experiments, I believe they would be few and far between, at least on the types of models that I\u2019m thinking about.<\/p>\n<h2>Summary and Conclusions<\/h2>\n<p>To summarize:<\/p>\n<ul>\n<li>Greenlets and tasklets are essentially equivalent from an ease and power of use perspective; both have some clear advantages over generators, particularly as model logic becomes more complex.<\/li>\n<li>Tasklets are problematic due to their incompatibility with the standard CPython distribution; using them requires either <a href=\"http:\/\/www.stackless.com\" target=\"_blank\">Stackless Python<\/a> or <a href=\"http:\/\/pypy.org\/\" target=\"_blank\">PyPy<\/a>. <a href=\"https:\/\/pypi.python.org\/pypi\/greenlet\" target=\"_blank\">Greenlets<\/a>, implemented as an extension module, are compatible with CPython.\u00a0 Generators work everywhere Python (3.3 and above) does.<\/li>\n<li>The future of Stackless Python is a bit murky; while PyPy is under more active development, it arguably has not yet acquired a critical mass of mindshare. The Greenlet package is stable and appears to have a reasonable user base, but as a third-party package, future support can\u2019t be absolutely guaranteed. Generators are part of the core language, and I see no reason for that to change in the foreseeable future.<\/li>\n<li>Generator-based solutions are <em>very<\/em> slightly faster &#8211; there&#8217;s not likely to be enough difference to have any practical impact.<\/li>\n<\/ul>\n<p>So which way should I go?\u00a0 It comes down to generators vs. greenlets \u2013 the ecosystem issues that come with tasklets are too significant.\u00a0 For me, the ease-of-use and expressiveness of a greenlet-based framework make that the most attractive option, in spite of any worries about long term support for the package. \u00a0(I use a \u00a0number of third-party extension modules; like many developers, I find that the benefit of the functionality that they provide far outweighs the risk that one or more of them won&#8217;t outlive the core Python language.) So I\u2019m going with greenlets.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A couple of previous posts (here and here) presented three Python-based alternatives for expressing process or agent-based behavior that takes place over (or blocks for) simulated time: generators, greenlets and tasklets. So which approach is best?\u00a0 There are (at least!) three factors to consider: Ease of use &#8211; ease-of-coding, expressiveness, maintainability. Eco-system factors \u2013 compatibility&#8230;&nbsp;(<a href=\"https:\/\/www.howardklein.net\/?p=144\">read more<\/a>)<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[3,5,2],"tags":[],"class_list":["post-144","post","type-post","status-publish","format-standard","hentry","category-modeling","category-python","category-simulation"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/www.howardklein.net\/index.php?rest_route=\/wp\/v2\/posts\/144","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.howardklein.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.howardklein.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.howardklein.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.howardklein.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=144"}],"version-history":[{"count":9,"href":"https:\/\/www.howardklein.net\/index.php?rest_route=\/wp\/v2\/posts\/144\/revisions"}],"predecessor-version":[{"id":156,"href":"https:\/\/www.howardklein.net\/index.php?rest_route=\/wp\/v2\/posts\/144\/revisions\/156"}],"wp:attachment":[{"href":"https:\/\/www.howardklein.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=144"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.howardklein.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=144"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.howardklein.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=144"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}