Skip to content

Instantly share code, notes, and snippets.

@JobLeonard
Last active January 22, 2025 20:37
Show Gist options
  • Save JobLeonard/1b165435c816c6d298e6b800b4742568 to your computer and use it in GitHub Desktop.
Save JobLeonard/1b165435c816c6d298e6b800b4742568 to your computer and use it in GitHub Desktop.
Generator vs iterable "Duff's Device-based" coroutine
{"title":"Generator vs iterable \"Duff's Device-based\" coroutine","initialization":"// Idiomatic JS: use function* syntax, which creates\n// an object that follows the iterable protocol. See:\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*\n// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol\nfunction* actor() {\n const data = {x: 0, y: 0 };\n while (data.x < 10000) {\n data.x += 1;\n yield data;\n }\n while (data.y < 10000) {\n data.y += 1;\n yield data;\n }\n while (data.x > 0) {\n data.x -= 1;\n yield data;\n }\n while (data.y > 0) {\n data.y -= 1;\n yield data;\n }\n yield data;\n}\n\n// state + switch trick taken from:\n// https://www.chiark.greenend.org.uk/%7Esgtatham/coroutines.html\nclass Actor {\n #done;\n #value;\n #state;\n constructor() {\n this.#done = false;\n this.#value = { x: 0, y: 0 };\n this.#state = 0;\n }\n next() {\n let done = this.#done, value = this.#value;\n switch (this.#state) {\n case 0:\n value.x += 1;\n if (value.x >= 10000) this.#state = 1;\n return {done, value};\n case 1:\n value.y += 1;\n if (value.y >= 10000) this.#state = 2;\n return {done, value};\n case 2:\n value.x -= 1;\n if (value.x <= 0) this.#state = 3;\n return {done, value};\n case 3:\n value.y -= 1;\n if (value.y <= 0) {\n done = this.#done = true;\n this.#state = 4;\n }\n return {done, value};\n case 4:\n default:\n return {done, value};\n }\n }\n [Symbol.iterator]() {\n return this;\n }\n}\n\nclass ActorReturnSelf {\n done;\n value;\n #state;\n constructor() {\n this.done = false;\n this.value = { x: 0, y: 0 };\n this.#state = 0;\n }\n next() {\n const v = this.value;\n switch (this.#state) {\n case 0:\n v.x += 1;\n if (v.x >= 10000) this.#state = 1;\n return this;\n case 1:\n v.y += 1;\n if (v.y >= 10000) this.#state = 2;\n return this;\n case 2:\n v.x -= 1;\n if (v.x <= 0) this.#state = 3;\n return this;\n case 3:\n v.y -= 1;\n if (v.y <= 0) {\n this.done = true;\n this.#state = 4;\n }\n return this\n case 4:\n default:\n return this;\n }\n }\n [Symbol.iterator]() {\n return this;\n }\n}\n\nclass ActorNotIterable {\n constructor() {\n this.state = 0;\n this.data = { x: 0, y: 0 };\n this.done = false;\n }\n next() {\n const data = this.data;\n switch (this.state) {\n case 0:\n data.x += 1;\n if (data.x >= 10000) this.state = 1;\n return data;\n case 1:\n data.y += 1;\n if (data.y >= 10000) this.state = 2;\n return data;\n case 2:\n data.x -= 1;\n if (data.x <= 0) this.state = 3;\n return data;\n case 3:\n data.y -= 1;\n if (data.y <= 0) {\n this.state = 4;\n this.done = true;\n }\n case 4:\n default:\n return data;\n }\n }\n}\n\nclass ActorNotIterable2 {\n constructor() {\n this.state = 1;\n this.data = { x: 0, y: 0 };\n }\n next() {\n const data = this.data;\n switch (this.state) {\n case 1:\n data.x += 1;\n if (data.x >= 10000) this.state = 2;\n return data;\n case 2:\n data.y += 1;\n if (data.y >= 10000) this.state = 3;\n return data;\n case 3:\n data.x -= 1;\n if (data.x <= 0) this.state = 4;\n return data;\n case 4:\n data.y -= 1;\n if (data.y <= 0) this.state = 0;\n return data;\n case 0:\n default:\n return data;\n }\n }\n}","setup":"let ret = {x: 0, y: 0};","tests":[{"name":"function* based, for..of loop","code":"for (const k of actor()) {\n ret = k;\n}\n\n","results":{"aborted":false,"count":16,"cycles":2,"hz":196.50018734128025,"stats":{"moe":0.00007369884577568744,"rme":1.4481837001758704,"sem":0.000037601451926371144,"deviation":0.00028882223257793157,"mean":0.005089053672316385,"variance":8.341828203130081e-8,"numSamples":59},"times":{"cycle":0.08142485875706217,"elapsed":6.21,"period":0.005089053672316385,"timeStamp":1737578201392}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":30,"cycles":5,"hz":370.81339712918674,"stats":{"moe":0.000043321007308192565,"rme":1.6064009887009214,"sem":0.00002210255474907784,"deviation":0.00017403569013001608,"mean":0.002696774193548386,"variance":3.028842143903098e-8,"numSamples":62},"times":{"cycle":0.08090322580645158,"elapsed":6.13,"period":0.002696774193548386,"timeStamp":1737558228640}},"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":112,"cycles":7,"hz":1434.7325384293556,"stats":{"moe":0.0000028958499660087393,"rme":0.4154770172642282,"sem":0.0000014774744724534385,"deviation":0.00001200305935608099,"mean":0.0006969940202894748,"variance":1.4407343390560342e-10,"numSamples":66},"times":{"cycle":0.07806333027242118,"elapsed":6.239,"period":0.0006969940202894748,"timeStamp":1737558333219}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":16,"cycles":2,"hz":196.50018734128025,"stats":{"moe":0.00007369884577568744,"rme":1.4481837001758704,"sem":0.000037601451926371144,"deviation":0.00028882223257793157,"mean":0.005089053672316385,"variance":8.341828203130081e-8,"numSamples":59},"times":{"cycle":0.08142485875706217,"elapsed":6.21,"period":0.005089053672316385,"timeStamp":1737578201392}}}},{"name":"function* based, while(!res.done) loop","code":"const iter = actor();\nlet res = iter.next();\nwhile(!res.done) {\n ret = res.value;\n res = iter.next();\n}","results":{"aborted":false,"count":16,"cycles":3,"hz":199.6475149806132,"stats":{"moe":0.000011376126417458434,"rme":0.2271215369350882,"sem":0.000005804146131356344,"deviation":0.00004458249237687581,"mean":0.005008827683615822,"variance":1.9875986265341897e-9,"numSamples":59},"times":{"cycle":0.08014124293785316,"elapsed":5.851,"period":0.005008827683615822,"timeStamp":1737578207685}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":30,"cycles":4,"hz":367.1671435140245,"stats":{"moe":0.00003972975237155604,"rme":1.4587459690783768,"sem":0.00002027028182222247,"deviation":0.0001583159620325496,"mean":0.0027235552463364785,"variance":2.5063943834291683e-8,"numSamples":61},"times":{"cycle":0.08170665739009435,"elapsed":5.974,"period":0.0027235552463364785,"timeStamp":1737558234776}},"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":116,"cycles":7,"hz":1495.2202783545417,"stats":{"moe":0.000007302812886081854,"rme":1.0919313916298445,"sem":0.000003725924941878497,"deviation":0.000030269557320611923,"mean":0.0006687977781444208,"variance":9.162461003858109e-10,"numSamples":66},"times":{"cycle":0.07758054226475282,"elapsed":6.096,"period":0.0006687977781444208,"timeStamp":1737558339463}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":16,"cycles":3,"hz":199.6475149806132,"stats":{"moe":0.000011376126417458434,"rme":0.2271215369350882,"sem":0.000005804146131356344,"deviation":0.00004458249237687581,"mean":0.005008827683615822,"variance":1.9875986265341897e-9,"numSamples":59},"times":{"cycle":0.08014124293785316,"elapsed":5.851,"period":0.005008827683615822,"timeStamp":1737578207685}}}},{"name":"Class based, for..of loop","code":"for (const k of new Actor()) {\n ret = k;\n}","results":{"aborted":false,"count":71,"cycles":6,"hz":921.4454253620808,"stats":{"moe":0.000006390180765807789,"rme":0.5888202833890345,"sem":0.0000032602963090856067,"deviation":0.00002587779970266687,"mean":0.0010852514673965106,"variance":6.696605174513455e-10,"numSamples":63},"times":{"cycle":0.07705285418515224,"elapsed":6.1,"period":0.0010852514673965106,"timeStamp":1737578213572}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":133,"cycles":4,"hz":1680.4306045797514,"stats":{"moe":0.000009117161692733409,"rme":1.5320757535371352,"sem":0.000004651613108537454,"deviation":0.00003721290486829963,"mean":0.0005950855675174304,"variance":1.384800288737118e-9,"numSamples":64},"times":{"cycle":0.07914638047981824,"elapsed":6.038,"period":0.0005950855675174304,"timeStamp":1737558240755}},"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":439,"cycles":7,"hz":5626.6263070795585,"stats":{"moe":0.0000012304052027167747,"rme":0.6923030281973762,"sem":6.277577564881504e-7,"deviation":0.000005061144836301104,"mean":0.00017772639329926274,"variance":2.561518705401733e-11,"numSamples":65},"times":{"cycle":0.07802188665837634,"elapsed":6.028,"period":0.00017772639329926274,"timeStamp":1737558345564}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":71,"cycles":6,"hz":921.4454253620808,"stats":{"moe":0.000006390180765807789,"rme":0.5888202833890345,"sem":0.0000032602963090856067,"deviation":0.00002587779970266687,"mean":0.0010852514673965106,"variance":6.696605174513455e-10,"numSamples":63},"times":{"cycle":0.07705285418515224,"elapsed":6.1,"period":0.0010852514673965106,"timeStamp":1737578213572}}}},{"name":"Class based, while(!res.done) loop","code":"const iter = new Actor();\nlet res = iter.next();\nwhile(!res.done) {\n ret = res.value;\n res = iter.next();\n}\n","results":{"aborted":false,"count":65,"cycles":4,"hz":839.8532528045351,"stats":{"moe":0.0000035555845375199715,"rme":0.29861692394576567,"sem":0.0000018140737436326386,"deviation":0.000014512589949061109,"mean":0.001190684201865843,"variance":2.106152670295895e-10,"numSamples":64},"times":{"cycle":0.0773944731212798,"elapsed":6.136,"period":0.001190684201865843,"timeStamp":1737578219699}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":125,"cycles":4,"hz":1547.036830479031,"stats":{"moe":0.000008246441493004678,"rme":1.2757548710068725,"sem":0.0000042073681086758566,"deviation":0.000033128849616579874,"mean":0.000646397021905649,"variance":1.0975206769179646e-9,"numSamples":62},"times":{"cycle":0.08079962773820612,"elapsed":5.99,"period":0.000646397021905649,"timeStamp":1737558246799}},"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":370,"cycles":7,"hz":4779.2027051270215,"stats":{"moe":7.266262632177388e-7,"rme":0.3472694202786556,"sem":3.7072768531517286e-7,"deviation":0.0000030118059531622593,"mean":0.0002092399217399217,"variance":9.070975099503625e-12,"numSamples":66},"times":{"cycle":0.07741877104377103,"elapsed":6.051,"period":0.0002092399217399217,"timeStamp":1737558351597}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":65,"cycles":4,"hz":839.8532528045351,"stats":{"moe":0.0000035555845375199715,"rme":0.29861692394576567,"sem":0.0000018140737436326386,"deviation":0.000014512589949061109,"mean":0.001190684201865843,"variance":2.106152670295895e-10,"numSamples":64},"times":{"cycle":0.0773944731212798,"elapsed":6.136,"period":0.001190684201865843,"timeStamp":1737578219699}}}},{"name":"Class based, return self, for..of loop","code":"for (const k of new ActorReturnSelf()) {\n ret = k;\n}\n","results":{"aborted":false,"count":105,"cycles":3,"hz":1341.9699834554474,"stats":{"moe":0.000004040768622141378,"rme":0.5422590201002355,"sem":0.0000020616166439496825,"deviation":0.000016363574815927362,"mean":0.0007451731501662156,"variance":2.6776658075645224e-10,"numSamples":63},"times":{"cycle":0.07824318076745264,"elapsed":6.029,"period":0.0007451731501662156,"timeStamp":1737578225883}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":210,"cycles":4,"hz":2596.879879722361,"stats":{"moe":0.0000036122379908422322,"rme":0.9380548159186918,"sem":0.000001842978566756241,"deviation":0.000014511627746273644,"mean":0.00038507749542382095,"variance":2.105873398464191e-10,"numSamples":62},"times":{"cycle":0.0808662740390024,"elapsed":6.092,"period":0.00038507749542382095,"timeStamp":1737558252795}},"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":737,"cycles":5,"hz":9567.306055388133,"stats":{"moe":7.638612371830911e-7,"rme":0.7308094239978058,"sem":3.897251210117812e-7,"deviation":0.000003190037599542109,"mean":0.00010452263094863764,"variance":1.0176339886492382e-11,"numSamples":67},"times":{"cycle":0.07703317900914594,"elapsed":6.146,"period":0.00010452263094863764,"timeStamp":1737558357653}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":105,"cycles":3,"hz":1341.9699834554474,"stats":{"moe":0.000004040768622141378,"rme":0.5422590201002355,"sem":0.0000020616166439496825,"deviation":0.000016363574815927362,"mean":0.0007451731501662156,"variance":2.6776658075645224e-10,"numSamples":63},"times":{"cycle":0.07824318076745264,"elapsed":6.029,"period":0.0007451731501662156,"timeStamp":1737578225883}}}},{"name":"Class based, return self, while(!res.done) loop","code":"const iter = new ActorReturnSelf();\nlet res = iter.next();\nwhile(!res.done) {\n ret = res.value;\n res = iter.next();\n}","results":{"aborted":false,"count":84,"cycles":3,"hz":1080.3845071790101,"stats":{"moe":0.000003442619141858596,"rme":0.3719352384981926,"sem":0.0000017564383376829572,"deviation":0.000013941297104186384,"mean":0.0009255963903176454,"variance":1.9435976494719563e-10,"numSamples":63},"times":{"cycle":0.07775009678668221,"elapsed":5.937,"period":0.0009255963903176454,"timeStamp":1737578232000}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":976,"cycles":2,"hz":2226.4043341887655,"stats":{"moe":0.000013488691481591137,"rme":3.003128117714959,"sem":0.0000063297472930976714,"deviation":0.000025318989172390686,"mean":0.00044915471311475404,"variance":6.410512127116368e-10,"numSamples":16},"times":{"cycle":0.43837499999999996,"elapsed":7.176,"period":0.00044915471311475404,"timeStamp":1737558258893}},"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":977,"cycles":8,"hz":12637.171521235421,"stats":{"moe":2.9147512670340524e-7,"rme":0.3683421170324759,"sem":1.4871179933847206e-7,"deviation":0.0000012172585389253019,"mean":0.00007913163149836231,"variance":1.4817183505865608e-12,"numSamples":67},"times":{"cycle":0.07731160397389998,"elapsed":6.161,"period":0.00007913163149836231,"timeStamp":1737558363804}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":84,"cycles":3,"hz":1080.3845071790101,"stats":{"moe":0.000003442619141858596,"rme":0.3719352384981926,"sem":0.0000017564383376829572,"deviation":0.000013941297104186384,"mean":0.0009255963903176454,"variance":1.9435976494719563e-10,"numSamples":63},"times":{"cycle":0.07775009678668221,"elapsed":5.937,"period":0.0009255963903176454,"timeStamp":1737578232000}}}},{"name":"Class based, not iterable","code":"const iter = new ActorNotIterable()\nwhile(!iter.done) {\n ret = iter.next();\n}","results":{"aborted":false,"count":101,"cycles":4,"hz":1320.509564493025,"stats":{"moe":0.000002666772481839124,"rme":0.3521498568595365,"sem":0.000001360598205019961,"deviation":0.000010969493420743279,"mean":0.0007572834206497578,"variance":1.203297859077301e-10,"numSamples":65},"times":{"cycle":0.07648562548562554,"elapsed":6.055,"period":0.0007572834206497578,"timeStamp":1737578237973}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":902,"cycles":8,"hz":11599.380265638463,"stats":{"moe":2.466502486127027e-7,"rme":0.2860990026273004,"sem":1.2584196357790956e-7,"deviation":0.0000010145703459171051,"mean":0.00008621150243365671,"variance":1.0293529868143543e-12,"numSamples":65},"times":{"cycle":0.07776277519515835,"elapsed":6.108,"period":0.00008621150243365671,"timeStamp":1737558369971}},"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":224,"cycles":4,"hz":2761.7998941588253,"stats":{"moe":0.000005134489944244149,"rme":1.4180433784573045,"sem":0.0000026196377266551784,"deviation":0.000020792729849436608,"mean":0.00036208271356479824,"variance":4.3233761459165206e-10,"numSamples":63},"times":{"cycle":0.08110652783851481,"elapsed":6.023,"period":0.00036208271356479824,"timeStamp":1737558266074}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":101,"cycles":4,"hz":1320.509564493025,"stats":{"moe":0.000002666772481839124,"rme":0.3521498568595365,"sem":0.000001360598205019961,"deviation":0.000010969493420743279,"mean":0.0007572834206497578,"variance":1.203297859077301e-10,"numSamples":65},"times":{"cycle":0.07648562548562554,"elapsed":6.055,"period":0.0007572834206497578,"timeStamp":1737578237973}}}},{"name":"Class based, not iterable v2 (minifies slightly better)","code":"const iter = new ActorNotIterable2()\nwhile(iter.state) {\n ret = iter.next();\n}","results":{"aborted":false,"count":103,"cycles":4,"hz":1334.1428860554543,"stats":{"moe":0.000002550268774942032,"rme":0.340242294361827,"sem":0.0000013011575382357306,"deviation":0.000010409260305885845,"mean":0.0007495449029126214,"variance":1.0835270011569067e-10,"numSamples":64},"times":{"cycle":0.077203125,"elapsed":6.017,"period":0.0007495449029126214,"timeStamp":1737578244061}},"platforms":{"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36":{"aborted":false,"count":1336,"cycles":5,"hz":17302.00772109689,"stats":{"moe":7.503857776173063e-7,"rme":1.2983180518135926,"sem":3.82849886539442e-7,"deviation":0.0000031337613799966746,"mean":0.00005779676070660101,"variance":9.820460386758663e-12,"numSamples":67},"times":{"cycle":0.07721647230401894,"elapsed":6.04,"period":0.00005779676070660101,"timeStamp":1737558376085}},"Mozilla/5.0 (X11; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0":{"aborted":false,"count":224,"cycles":4,"hz":2745.535814143564,"stats":{"moe":0.000005161739961384577,"rme":1.4171741927277375,"sem":0.0000026335407966247842,"deviation":0.00002073652096915489,"mean":0.0003642276290291036,"variance":4.300033019042005e-10,"numSamples":62},"times":{"cycle":0.08158698890251921,"elapsed":6.01,"period":0.0003642276290291036,"timeStamp":1737558272101}},"Mozilla/5.0 (Android 11; Mobile; rv:134.0) Gecko/134.0 Firefox/134.0":{"aborted":false,"count":103,"cycles":4,"hz":1334.1428860554543,"stats":{"moe":0.000002550268774942032,"rme":0.340242294361827,"sem":0.0000013011575382357306,"deviation":0.000010409260305885845,"mean":0.0007495449029126214,"variance":1.0835270011569067e-10,"numSamples":64},"times":{"cycle":0.077203125,"elapsed":6.017,"period":0.0007495449029126214,"timeStamp":1737578244061}}}}]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment