Thicket data repository for the EEG
at main 8.6 kB view raw
1{ 2 "id": "https://lucasma8795.github.io/blog/2025/07/25/effects-scheduling-w04", 3 "title": "Effects-based scheduling for the OCaml compiler - w04", 4 "link": "https://lucasma8795.github.io/blog/2025/07/25/effects-scheduling-w04.html", 5 "updated": "2025-07-25T08:00:00", 6 "published": "2025-07-25T08:00:00", 7 "summary": "Now that I have a working prototype of a linear self-scheduling OCaml compiler, the next step was to dispatch compilation tasks in parallel. My idea was to have some sort of process (domain?) pool to submit compilation tasks to, so I got that done fairly quickly:", 8 "content": "<p>Now that I have a working prototype of a linear self-scheduling OCaml compiler, the next step was to dispatch compilation tasks in parallel. My idea was to have some sort of process (domain?) pool to submit compilation tasks to, so I got that done fairly quickly:</p>\n\n<div><div><pre><code><span>type</span> <span>!</span><span>'</span><span>a</span> <span>promise</span>\n<span>(** Type of a promise, representing an asynchronous return value that will\n eventually be available. *)</span>\n\n<span>val</span> <span>await</span> <span>:</span> <span>'</span><span>a</span> <span>promise</span> <span>-&gt;</span> <span>'</span><span>a</span>\n<span>(** [await p] blocks the calling domain until the promise [p] is resolved,\n returning the value if it was resolved, or re-raising the wrapped exception\n if it was rejected. *)</span>\n\n<span>module</span> <span>Pool</span> <span>:</span> <span>sig</span>\n <span>type</span> <span>t</span>\n <span>(** Type of a thread pool. *)</span>\n\n <span>val</span> <span>create</span> <span>:</span> <span>int</span> <span>-&gt;</span> <span>t</span>\n <span>(** [create n] creates a thread pool with [n] new domains. *)</span>\n\n <span>val</span> <span>submit</span> <span>:</span> <span>t</span> <span>-&gt;</span> <span>(</span><span>unit</span> <span>-&gt;</span> <span>'</span><span>a</span><span>)</span> <span>-&gt;</span> <span>'</span><span>a</span> <span>promise</span>\n <span>(** [submit pool task] submits a task to be executed by the thread pool. *)</span>\n\n <span>val</span> <span>join_and_shutdown</span> <span>:</span> <span>t</span> <span>-&gt;</span> <span>unit</span>\n <span>(** [join_and_shutdown pool] blocks the calling thread until all tasks are\n finished, then closes the thread pool. *)</span>\n<span>end</span>\n</code></pre></div></div>\n\n<p>Internally, this is done with an array of <code>Domain.t</code>, and a thread-safe task queue <code>(unit -&gt; unit) TSQueue.t</code>, which was nothing more than a wrapper around <code>'a Queue.t</code> from stdlib. I have identical worker loops that sit on each domain, checking the queue for tasks when one completes.</p>\n\n<p>Slight caveat: when a compilation task in the pool is waiting on another dependency to finish compiling, we certainly don\u2019t want to block the entire domain that the task sits on. I needed some way to yield control back to the pool, allow other tasks to run on our domain, then <em>continue</em> the task at the point the task was <em>suspended</em>. (sounds familiar?) This was done with a list of continuations, each paired with a <code>promise</code> that signals the dependency\u2019s completion. To suspend a task, I simply have it raise an effect.</p>\n\n<p>Back to actual compiler work: <a href=\"https://github.com/dra27\">David Allsopp</a> (my supervisor!) suggested that for a first prototype of my parallel scheduler, I should start with <code>Unix.create_process</code> instead of jumping straight into domains, just to cut down on the mutable compiler global state that I would have to initially deal with. The idea was to only have the main process compile <code>.ml</code> files, and have it spawn child processes in parallel to compile missing <code>.cmi</code> interfaces; if those missing <code>.cmi</code> interfaces have their set of missing dependencies, they are compiled linearly<a href=\"https://lucasma8795.github.io/blog/2025/07/25/effects-scheduling-w04.html#fn:1\">1</a>, i.e.: we block until its children are ready. The best way to explain this is with an example:</p>\n\n<div><div><pre><code><span>(* A.ml *)</span>\n<span>let</span> <span>foo</span> <span>=</span> <span>42</span>\n<span>let</span> <span>()</span> <span>=</span>\n <span>Printf</span><span>.</span><span>printf</span> <span>\"foo: %d, bar: %s, sum(baz): %d</span><span>\\n</span><span>\"</span> \n <span>foo</span> <span>(</span><span>B</span><span>.</span><span>bar</span><span>)</span> <span>(</span><span>List</span><span>.</span><span>fold_left</span> <span>(</span><span>+</span><span>)</span> <span>0</span> <span>C</span><span>.</span><span>baz</span><span>)</span>\n\n<span>(* B.ml *)</span>\n<span>let</span> <span>bar</span> <span>=</span> <span>\"Hello, world!\"</span>\n\n<span>(* C.ml *)</span>\n<span>let</span> <span>baz</span> <span>=</span> <span>[</span><span>1</span><span>;</span> <span>2</span><span>;</span> <span>3</span><span>;</span> <span>4</span><span>;</span> <span>5</span><span>]</span>\n</code></pre></div></div>\n\n<p>(insert <code>{A,B,C}.mli</code> files as appropriate!)</p>\n\n<p>When we invoke our custom <code>ocamlc</code> to compile <code>{A,B,C}.ml</code> (in this order), what then should happen chronologically is:</p>\n\n\n\n<img alt=\"Image 1\" src=\"https://lucasma8795.github.io/blog/public/images/effects_scheduling_1.jpeg\">\n \nImage 1: Effects-based parallel scheduling between compilation of three modules\n\n\n<ol>\n <li><code>A.ml</code> starts compiling. One of its dependencies <code>C.mli</code> is missing, which is discovered by our effect handler after an effect is raised somewhere to locate <code>C.mli</code> in the load path. We launch a child process to compile <code>C.mli</code>, then move on immediately.</li>\n <li><code>B.ml</code> starts compiling. Its only dependency <code>B.mli</code> is missing, so that gets compiled in parallel.</li>\n <li><code>C.ml</code> starts compiling. Its only dependency <code>C.mli</code> is missing, but we already launched a child process to compile it (represented as a dotted line), so we attach the suspended compilation to <code>C.cmi</code> and resume it only when it is ready.</li>\n <li>Suppose <code>C.cmi</code> is now ready. We can now resume the compilation of <code>C.ml</code> and it should complete successfully, since that was our only dependency.</li>\n <li><code>A.ml</code> was also waiting on <code>C.cmi</code>, so it can also be resumed. It now hits a second missing dependency <code>B.mli</code>, which we again compile in parallel.</li>\n</ol>\n\n<p>Steps 6 to 10 follow the same logic, as shown in the diagram above. We fold on the list of implementations <code>{A,B,C}.ml</code> until all of them compile successfully. I had most of the code down for this by the end of week.</p>\n\n<p>Finally, I took a couple of hours out of my weekend to make this website! I used <a href=\"https://jekyllrb.com/\">Jekyll</a>, a static site generator, which was surprisingly pleasant to set up and easy to work with. The source code is publicly available on <a href=\"https://github.com/lucasma8795/lucasma8795.github.io\">GitHub</a>.</p>\n\n<div>\n <ol>\n <li>\n <p>This is actually non-trivial, since the main process wants to launch child processes in parallel, but the child processes want to be linear. I did this by temporarily maintaining two branches of the compiler, one for the main process itself (with all this new fancy parallelism) and one that the main process launches (with our linear compiler from the start of week). I take my existing compiler and install it to some directory, but instead of using its executables directly, I create a new entry point to replace <code>driver/main.ml</code> and link against the <code>.cma</code> files in the installation to create the parallel compiler. This also doubles as a hack for me to use the <code>Unix</code> module in the compiler, since that originally depended on <code>ocamlc</code> to be built, which in turn depends on <code>ocamlcommon.cma</code>, which likely contains whatever that I need to modify, and I can\u2019t have those depend on <code>Unix</code>.\u00a0<a href=\"https://lucasma8795.github.io/blog/2025/07/25/effects-scheduling-w04.html#fnref:1\">&#8617;</a></p>\n </li>\n </ol>\n</div>", 9 "content_type": "html", 10 "author": { 11 "name": "", 12 "email": null, 13 "uri": null 14 }, 15 "categories": [ 16 "ocaml-effects-scheduling" 17 ], 18 "source": "https://lucasma8795.github.io/blog/feed/ocaml-effects-scheduling.xml" 19}