https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112020-10-30T18:34:03ZRuby Issue Tracking SystemRuby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883092020-10-30T18:34:03Zko1 (Koichi Sasada)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/88309/diff?detail_id=58241">diff</a>)</li></ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883102020-10-30T18:34:21Zko1 (Koichi Sasada)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/88310/diff?detail_id=58242">diff</a>)</li></ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883112020-10-30T18:35:27Zko1 (Koichi Sasada)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/88311/diff?detail_id=58243">diff</a>)</li></ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883192020-10-31T10:47:53ZEregon (Benoit Daloze)
<ul></ul><p>For the first example, isn't <code>move: true</code> much simpler?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">bridge</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span>
<span class="k">end</span>
<span class="n">consumer</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="n">bridge</span> <span class="k">do</span> <span class="o">|</span><span class="n">from</span><span class="o">|</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">from</span><span class="p">.</span><span class="nf">take</span>
<span class="n">do_task</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">msg</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
<span class="n">bridge</span><span class="p">.</span><span class="nf">send</span> <span class="n">msg</span>
</code></pre>
<p>Having <code>move: true</code> on <code>Ractor.yield</code> means we need to wait to know which Ractor to move the object to, but that seems fine and transparent to the user.<br>
The bridge Ractor can ensure it will no longer refer to the message, so it's perfectly safe to move there.</p>
<p>Similar for the other 3 examples in the benchmark.</p>
<p>Adding 4 new methods for this seem heavy to me. Also, exposing the serialized representation seems bad.<br>
For instance, there might be an efficient deep_copy/dup/clone in the future, and then there is no reason to use Marshal anymore, and no serialized representation.</p>
<p>Explicitly deeply freezing the message (e.g., with Ractor.make_shareable, or some kwarg to freeze on send/yield) seems a good way too, and easier to reason about.<br>
Sending a copy of a mutable object seems useless to me, because mutations will not affect the sender, it only mutates a copy.<br>
So either freezing or moving seems more useful, and both avoid copying.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883202020-10-31T11:00:51ZEregon (Benoit Daloze)
<ul></ul><p>From the benchmark above, USE_BASKET=false takes for me <code>5.636s</code>.<br>
Adding <code>Ractor.make_shareable(ary)</code>, it takes <code>0.192s</code>.<br>
So that's ~10x faster than with <code>w/ basket API</code> on this benchmark, and does not require new APIs and concepts.</p>
<p>So I don't see the need for this API, better to freeze or to move, and both are more efficient.<br>
Copying is confusing anyway if mutated.</p>
<p>Maybe all messages should be made <code>Ractor.make_shareable</code> by <code>Ractor#send</code>/<code>Ractor.yield</code>, unless <code>move: true</code> is used?<br>
Then there would be no confusion about mutations, and there would be no hidden cost to sending a message (copying a big data structure can take a long time as we see here).<br>
Moving still has a cost proportional to the #objects in the object graph, which seems unavoidable, but at least it does not need to copy e.g. the array storage.</p>
<p>Do other languages have something similar to the basket API?</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883212020-10-31T11:05:54ZEregon (Benoit Daloze)
<ul></ul><p>In the example above, using <code>, move: true</code> for Ractor.yield and Ractor#send, instead of the <code>_basket</code> calls seems to give the same or better performance.<br>
(code at <a href="https://gist.github.com/eregon/092ea76534b46e227d9cbf5fd107de66" class="external">https://gist.github.com/eregon/092ea76534b46e227d9cbf5fd107de66</a>)<br>
It runs in 1.578s for me.<br>
And it requires less modifications than the <code>_basket</code> APIs which need both sides to know about it, not just the sending side.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883222020-10-31T19:05:44Zko1 (Koichi Sasada)
<ul></ul><p>Eregon (Benoit Daloze) wrote in <a href="#note-4">#note-4</a>:</p>
<blockquote>
<p>For the first example, isn't <code>move: true</code> much simpler?</p>
</blockquote>
<p>There are two problems:</p>
<ul>
<li>Now, many types are not support on <code>move: true</code>. I'll investigate more, but not sure we can support for all data types.</li>
<li>To move the object, we need traverse the object each time and make shallow copy for it. For example, we need to traverse a nesting array.</li>
</ul>
<blockquote>
<p>Having <code>move: true</code> on <code>Ractor.yield</code> means we need to wait to know which Ractor to move the object to, but that seems fine and transparent to the user.<br>
The bridge Ractor can ensure it will no longer refer to the message, so it's perfectly safe to move there.</p>
</blockquote>
<p>If we analyze cross-method escaping, it can be. But now there is no support yet.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">bridge</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="n">msg</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="n">msg</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span>
<span class="k">end</span>
</code></pre>
<blockquote>
<p>Adding 4 new methods for this seem heavy to me. Also, exposing the serialized representation seems bad.</p>
</blockquote>
<p>"serialized representation" is not exposed.<br>
It only says the bridge ractor doesn't touch the message.</p>
<blockquote>
<p>Explicitly deeply freezing the message (e.g., with Ractor.make_shareable, or some kwarg to freeze on send/yield) seems a good way too, and easier to reason about.<br>
Sending a copy of a mutable object seems useless to me, because mutations will not affect the sender, it only mutates a copy.<br>
So either freezing or moving seems more useful, and both avoid copying.</p>
</blockquote>
<p>I agree to add an option calling <code>make_shareable</code> to <code>send</code> and <code>yield</code>.<br>
It is already filed in my working memo.</p>
<p>However, it doesn't mean we can avoid copying.<br>
I believe sending copy is the most used option because there is no difficulties.<br>
Sender does not need to care about accessing to the sent object after sending.</p>
<hr>
<p>Eregon (Benoit Daloze) wrote in <a href="#note-5">#note-5</a>:</p>
<blockquote>
<p>From the benchmark above, USE_BASKET=false takes for me <code>5.636s</code>.<br>
Adding <code>Ractor.make_shareable(ary)</code>, it takes <code>0.192s</code>.<br>
So that's ~10x faster than with <code>w/ basket API</code> on this benchmark, and does not require new APIs and concepts.</p>
</blockquote>
<p>Of course, it is faster.</p>
<blockquote>
<p>So I don't see the need for this API, better to freeze or to move, and both are more efficient.<br>
Copying is confusing anyway if mutated.</p>
</blockquote>
<p>As I wrote, I believe copying should be the first option.</p>
<blockquote>
<p>Maybe all messages should be made <code>Ractor.make_shareable</code> by <code>Ractor#send</code>/<code>Ractor.yield</code>, unless <code>move: true</code> is used?<br>
Then there would be no confusion about mutations, and there would be no hidden cost to sending a message (copying a big data structure can take a long time as we see here).<br>
Moving still has a cost proportional to the #objects in the object graph, which seems unavoidable, but at least it does not need to copy e.g. the array storage.</p>
</blockquote>
<p>It can be one option, but I think it is introducing huge side-effect.</p>
<blockquote>
<p>Do other languages have something similar to the basket API?</p>
</blockquote>
<p>I don't have an idea. I don't have any</p>
<ul>
<li>copying is used for IPC</li>
<li>immutable data is used for traditional actor models</li>
<li>only reference (pointer) is used for shared-everything model</li>
</ul>
<p>I don't know our mixing model.</p>
<hr>
<p>Eregon (Benoit Daloze) wrote in <a href="#note-6">#note-6</a>:</p>
<blockquote>
<p>In the example above, using <code>, move: true</code> for Ractor.yield and Ractor#send, instead of the <code>_basket</code> calls seems to give the same or better performance.<br>
(code at <a href="https://gist.github.com/eregon/092ea76534b46e227d9cbf5fd107de66" class="external">https://gist.github.com/eregon/092ea76534b46e227d9cbf5fd107de66</a>)<br>
It runs in 1.578s for me.</p>
</blockquote>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">AN</span> <span class="o">=</span> <span class="mi">1_000</span>
<span class="no">LN</span> <span class="o">=</span> <span class="mi">100</span>
<span class="n">ary</span> <span class="o">=</span> <span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">AN</span><span class="p">){</span><span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">AN</span><span class="p">)}</span>
</code></pre>
<p>Changing the parameter, it takes:</p>
<pre><code>use basket: 0m6.047s
use move : 0m8.018s
</code></pre>
<p>There is a bit difference.</p>
<blockquote>
<p>And it requires less modifications than the <code>_basket</code> APIs which need both sides to know about it, not just the sending side.</p>
</blockquote>
<p>In general, proposed APIs should be hidden in framework, I think.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883232020-10-31T19:08:10Zko1 (Koichi Sasada)
<ul></ul><p>BTW, for <code>recvfrom</code> feature, we need more 2 APIs.<br>
This API can cover this feature (and it is more extendable), so I don't think "4" APIs are heavy.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883242020-11-01T03:57:56Zsawa (Tsuyoshi Sawada)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/88324/diff?detail_id=58249">diff</a>)</li></ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883282020-11-02T05:41:36Zko1 (Koichi Sasada)
<ul></ul><p>I rewrote copying logic without marshal protocol (<a href="https://github.com/ruby/ruby/pull/3728" class="external">https://github.com/ruby/ruby/pull/3728</a>).</p>
<p>Additional benchmark:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="no">Warning</span><span class="p">[</span><span class="ss">:experimental</span><span class="p">]</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">if</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'MODE'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'share'</span>
<span class="no">MODE</span> <span class="o">=</span> <span class="ss">:copy</span>
<span class="no">SHAREABLE</span> <span class="o">=</span> <span class="kp">true</span>
<span class="k">else</span>
<span class="no">MODE</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'MODE'</span><span class="p">].</span><span class="nf">to_sym</span>
<span class="no">SHAREABLE</span> <span class="o">=</span> <span class="kp">false</span>
<span class="k">end</span>
<span class="n">receive2yield</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="k">do</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="k">case</span> <span class="no">MODE</span>
<span class="k">when</span> <span class="ss">:basket</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield_basket</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive_basket</span>
<span class="k">when</span> <span class="ss">:move</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span>
<span class="k">when</span> <span class="ss">:copy</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span>
<span class="k">else</span>
<span class="k">raise</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">to</span> <span class="o">=</span> <span class="n">receive2yield</span>
<span class="n">receive2send</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="n">to</span> <span class="k">do</span> <span class="o">|</span><span class="n">to</span><span class="o">|</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="k">case</span> <span class="no">MODE</span>
<span class="k">when</span> <span class="ss">:basket</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive_basket</span>
<span class="n">to</span><span class="p">.</span><span class="nf">send_basket</span> <span class="n">obj</span>
<span class="k">when</span> <span class="ss">:move</span>
<span class="n">to</span><span class="p">.</span><span class="nf">send</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span>
<span class="k">when</span> <span class="ss">:copy</span>
<span class="n">to</span><span class="p">.</span><span class="nf">send</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">take2yield</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="n">receive2yield</span> <span class="k">do</span> <span class="o">|</span><span class="n">from</span><span class="o">|</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="k">case</span> <span class="no">MODE</span>
<span class="k">when</span> <span class="ss">:basket</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield_basket</span> <span class="n">from</span><span class="p">.</span><span class="nf">take_basket</span>
<span class="k">when</span> <span class="ss">:move</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="n">from</span><span class="p">.</span><span class="nf">take</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span>
<span class="k">when</span> <span class="ss">:copy</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">yield</span> <span class="n">from</span><span class="p">.</span><span class="nf">take</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">take2send</span> <span class="o">=</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span> <span class="n">take2yield</span><span class="p">,</span> <span class="no">Ractor</span><span class="p">.</span><span class="nf">current</span> <span class="k">do</span> <span class="o">|</span><span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="o">|</span>
<span class="kp">loop</span> <span class="k">do</span>
<span class="k">case</span> <span class="no">MODE</span>
<span class="k">when</span> <span class="ss">:basket</span>
<span class="n">to</span><span class="p">.</span><span class="nf">send_basket</span> <span class="n">from</span><span class="p">.</span><span class="nf">take_basket</span>
<span class="k">when</span> <span class="ss">:move</span>
<span class="n">to</span><span class="p">.</span><span class="nf">send</span> <span class="n">from</span><span class="p">.</span><span class="nf">take</span><span class="p">,</span> <span class="ss">move: </span><span class="kp">true</span>
<span class="k">when</span> <span class="ss">:copy</span>
<span class="n">to</span><span class="p">.</span><span class="nf">send</span> <span class="n">from</span><span class="p">.</span><span class="nf">take</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="no">AN</span> <span class="o">=</span> <span class="no">ENV</span><span class="p">[</span><span class="s1">'AN'</span><span class="p">].</span><span class="nf">to_i</span>
<span class="no">LN</span> <span class="o">=</span> <span class="mi">10_000</span>
<span class="n">obj</span> <span class="o">=</span> <span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="no">AN</span><span class="o">/</span><span class="mi">10</span><span class="p">){</span><span class="no">Array</span><span class="p">.</span><span class="nf">new</span><span class="p">{</span><span class="no">AN</span><span class="p">}}</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">make_shareable</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span> <span class="k">if</span> <span class="no">SHAREABLE</span>
<span class="no">LN</span><span class="p">.</span><span class="nf">times</span><span class="p">{</span>
<span class="n">receive2send</span><span class="p">.</span><span class="nf">send</span> <span class="n">obj</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">receive</span>
<span class="p">}</span>
<span class="cp">__END__
obj = Array.new(AN){}
user system total real
share/0 0.000235 0.000000 2.084210 ( 1.244361)
basket/0 0.000186 0.000000 2.157927 ( 1.309404)
move/0 0.000193 0.000000 2.117845 ( 1.294796)
copy/0 0.000164 0.000000 2.324445 ( 1.431408)
share/30 0.000113 0.000000 2.086155 ( 1.235768)
basket/30 0.000158 0.000000 2.197975 ( 1.335114)
move/30 0.000211 0.000000 2.190369 ( 1.352113)
copy/30 0.000171 0.000000 2.389695 ( 1.490143)
share/60 0.000206 0.000000 1.997523 ( 1.191689)
basket/60 0.000160 0.000000 2.237804 ( 1.356830)
move/60 0.000145 0.000000 2.268976 ( 1.397970)
copy/60 0.000185 0.000000 2.538057 ( 1.580106)
share/90 0.000168 0.000000 2.069107 ( 1.224712)
basket/90 0.000148 0.000000 2.186683 ( 1.327779)
move/90 0.000158 0.000000 2.154334 ( 1.330732)
copy/90 0.000139 0.000000 2.504655 ( 1.561725)
share/120 0.000189 0.000000 2.120684 ( 1.255453)
basket/120 0.000202 0.000000 2.270650 ( 1.375921)
move/120 0.000151 0.000000 2.416082 ( 1.491159)
copy/120 0.000156 0.000000 2.436406 ( 1.554357)
share/150 0.000216 0.000000 2.103629 ( 1.248967)
basket/150 0.000219 0.000000 2.323634 ( 1.408301)
move/150 0.000187 0.000000 2.123332 ( 1.325794)
copy/150 0.000000 0.000205 2.519390 ( 1.592612)
share/180 0.000000 0.000149 1.916865 ( 1.143092)
basket/180 0.000000 0.000229 2.339701 ( 1.416612)
move/180 0.000000 0.000188 2.384908 ( 1.497792)
copy/180 0.000000 0.000163 2.503268 ( 1.593776)
share/210 0.000000 0.000128 1.987748 ( 1.176819)
basket/210 0.000000 0.000167 2.228943 ( 1.353803)
move/210 0.000000 0.000177 2.431323 ( 1.519974)
copy/210 0.000000 0.000190 2.592614 ( 1.652112)
share/240 0.000000 0.000206 2.063813 ( 1.221907)
basket/240 0.000000 0.000222 2.266633 ( 1.383860)
move/240 0.000000 0.000140 2.291410 ( 1.442910)
copy/240 0.000000 0.000129 2.360580 ( 1.519305)
share/270 0.000000 0.000200 2.187821 ( 1.292601)
basket/270 0.000000 0.000141 2.186951 ( 1.335748)
move/270 0.000000 0.000175 2.331970 ( 1.462081)
copy/270 0.000000 0.000201 2.544173 ( 1.631321)
share/300 0.000000 0.000220 2.103491 ( 1.259986)
basket/300 0.000000 0.000208 2.470096 ( 1.533962)
move/300 0.000000 0.000177 2.477407 ( 1.575883)
copy/300 0.000000 0.000161 2.577555 ( 1.664693)
obj = Array.new(AN/10){Array.new{AN}}
user system total real
share/0 0.000178 0.000000 1.999250 ( 1.192009)
basket/0 0.000186 0.000000 2.113732 ( 1.276346)
move/0 0.000111 0.000000 2.079669 ( 1.272024)
copy/0 0.000261 0.000000 2.301534 ( 1.419984)
share/30 0.000141 0.000000 2.133833 ( 1.264703)
basket/30 0.000157 0.000000 2.314221 ( 1.400378)
move/30 0.000152 0.000000 2.349555 ( 1.461622)
copy/30 0.000180 0.000000 2.255883 ( 1.421459)
share/60 0.000217 0.000000 2.095241 ( 1.243948)
basket/60 0.000206 0.000000 2.239443 ( 1.372667)
move/60 0.000129 0.000000 2.477502 ( 1.550481)
copy/60 0.000298 0.000000 2.510642 ( 1.610342)
share/90 0.000159 0.000000 2.241600 ( 1.323923)
basket/90 0.000293 0.000000 2.184617 ( 1.339768)
move/90 0.000239 0.000000 2.524579 ( 1.590508)
copy/90 0.000157 0.000000 2.610485 ( 1.696873)
share/120 0.000130 0.000000 2.164260 ( 1.283117)
basket/120 0.000190 0.000000 2.250227 ( 1.383655)
move/120 0.000239 0.000000 2.299717 ( 1.459356)
copy/120 0.000220 0.000000 2.624845 ( 1.720889)
share/150 0.000175 0.000000 2.024881 ( 1.203234)
basket/150 0.000214 0.000000 2.213022 ( 1.367973)
move/150 0.000230 0.000000 2.351162 ( 1.508806)
copy/150 0.000261 0.000000 2.600007 ( 1.729760)
share/180 0.000128 0.000000 1.981322 ( 1.176595)
basket/180 0.000218 0.000000 2.202996 ( 1.368278)
move/180 0.000190 0.000000 2.684911 ( 1.733626)
copy/180 0.000176 0.000000 2.877426 ( 1.940715)
share/210 0.000162 0.000000 2.096774 ( 1.243471)
basket/210 0.000119 0.000000 2.227222 ( 1.387271)
move/210 0.000277 0.000000 2.654121 ( 1.725943)
copy/210 0.000198 0.000000 2.823281 ( 1.916791)
share/240 0.000166 0.000000 2.209040 ( 1.309859)
basket/240 0.000169 0.000000 2.367142 ( 1.475902)
move/240 0.000144 0.000000 2.600334 ( 1.702996)
copy/240 0.000164 0.000000 2.859249 ( 1.956904)
share/270 0.000212 0.000000 2.100495 ( 1.244568)
basket/270 0.000234 0.000000 2.346845 ( 1.464750)
move/270 0.000165 0.000000 2.695428 ( 1.770649)
copy/270 0.000184 0.000000 2.934659 ( 2.020429)
share/300 0.000169 0.000000 2.145326 ( 1.269930)
basket/300 0.000160 0.000000 2.286473 ( 1.426499)
move/300 0.000213 0.000000 2.706252 ( 1.789096)
copy/300 0.000270 0.000000 3.063131 ( 2.136394)
obj = "*" * (100 * AN)
user system total real
share/0 0.000000 0.000225 2.113924 ( 1.256272)
basket/0 0.000000 0.000234 2.103315 ( 1.275444)
move/0 0.000000 0.000170 2.270854 ( 1.393821)
copy/0 0.000000 0.000160 2.311942 ( 1.424493)
share/30 0.000000 0.000209 2.190677 ( 1.290621)
basket/30 0.000000 0.000126 2.201340 ( 1.338486)
move/30 0.000000 0.000133 2.362480 ( 1.438814)
copy/30 0.000000 0.000155 2.564007 ( 1.623138)
share/60 0.000000 0.000230 2.045274 ( 1.215421)
basket/60 0.000000 0.000175 2.240818 ( 1.358289)
move/60 0.000000 0.000230 2.241105 ( 1.366148)
copy/60 0.000147 0.000000 2.582220 ( 1.621733)
share/90 0.000181 0.000000 2.045982 ( 1.219508)
basket/90 0.000152 0.000000 2.259071 ( 1.368956)
move/90 0.000229 0.000000 2.215262 ( 1.353770)
copy/90 0.000193 0.000000 2.311903 ( 1.465815)
share/120 0.000189 0.000000 2.220438 ( 1.309290)
basket/120 0.000182 0.000000 2.337383 ( 1.415203)
move/120 0.000216 0.000000 2.185714 ( 1.342087)
copy/120 0.000174 0.000000 2.547647 ( 1.612203)
share/150 0.000175 0.000000 2.111274 ( 1.247957)
basket/150 0.000199 0.000000 2.352135 ( 1.436637)
move/150 0.000232 0.000000 2.341878 ( 1.440591)
copy/150 0.000119 0.000000 2.567323 ( 1.629583)
share/180 0.000176 0.000000 2.206774 ( 1.303557)
basket/180 0.000153 0.000000 2.123505 ( 1.295564)
move/180 0.000272 0.000000 2.295597 ( 1.406772)
copy/180 0.000201 0.000000 2.537667 ( 1.620875)
share/210 0.000236 0.000000 2.159986 ( 1.278420)
basket/210 0.000231 0.000000 2.357100 ( 1.449504)
move/210 0.000187 0.000000 2.434216 ( 1.514514)
copy/210 0.000273 0.000000 2.581147 ( 1.654714)
share/240 0.000220 0.000000 2.187199 ( 1.290690)
basket/240 0.000134 0.000000 2.125558 ( 1.308496)
move/240 0.000205 0.000000 2.289436 ( 1.411198)
copy/240 0.000158 0.000000 2.561948 ( 1.649240)
share/270 0.000184 0.000000 1.949823 ( 1.151623)
basket/270 0.000181 0.000000 2.305483 ( 1.408048)
move/270 0.000202 0.000000 2.097124 ( 1.294308)
copy/270 0.000139 0.000000 2.613040 ( 1.683508)
share/300 0.000264 0.000000 2.147143 ( 1.270971)
basket/300 0.000165 0.000000 2.260574 ( 1.377311)
move/300 0.000224 0.000000 2.186270 ( 1.345459)
copy/300 0.000280 0.000000 2.640306 ( 1.709781)
</span></code></pre>
<ul>
<li>Most of case, tuple or something small objects are used to communicate (<code>[command, param1, param2]</code> for example) and it is enough fast (on current inefficient implementation).</li>
</ul>
<pre><code>obj = Array.new(AN){}
user system total real
share/0 0.000235 0.000000 2.084210 ( 1.244361)
basket/0 0.000186 0.000000 2.157927 ( 1.309404)
move/0 0.000193 0.000000 2.117845 ( 1.294796)
copy/0 0.000164 0.000000 2.324445 ( 1.431408)
share/30 0.000113 0.000000 2.086155 ( 1.235768)
basket/30 0.000158 0.000000 2.197975 ( 1.335114)
move/30 0.000211 0.000000 2.190369 ( 1.352113)
copy/30 0.000171 0.000000 2.389695 ( 1.490143)
obj = Array.new(AN/10){Array.new{AN}}
user system total real
share/0 0.000178 0.000000 1.999250 ( 1.192009)
basket/0 0.000186 0.000000 2.113732 ( 1.276346)
move/0 0.000111 0.000000 2.079669 ( 1.272024)
copy/0 0.000261 0.000000 2.301534 ( 1.419984)
share/30 0.000141 0.000000 2.133833 ( 1.264703)
basket/30 0.000157 0.000000 2.314221 ( 1.400378)
move/30 0.000152 0.000000 2.349555 ( 1.461622)
copy/30 0.000180 0.000000 2.255883 ( 1.421459)
obj = "*" * (100 * AN)
user system total real
share/0 0.000000 0.000225 2.113924 ( 1.256272)
basket/0 0.000000 0.000234 2.103315 ( 1.275444)
move/0 0.000000 0.000170 2.270854 ( 1.393821)
copy/0 0.000000 0.000160 2.311942 ( 1.424493)
share/30 0.000000 0.000209 2.190677 ( 1.290621)
basket/30 0.000000 0.000126 2.201340 ( 1.338486)
move/30 0.000000 0.000133 2.362480 ( 1.438814)
copy/30 0.000000 0.000155 2.564007 ( 1.623138)
</code></pre>
<ul>
<li>On nested data structure, "basket" seems fine.</li>
</ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883342020-11-02T19:45:57Zmarcandre (Marc-Andre Lafortune)marcandre-ruby-core@marc-andre.ca
<ul></ul><p>The use-case seems unusual:</p>
<ul>
<li>Need to send very big object to a Ractor</li>
<li>That object can not be deep-frozen for some reason, or moved so it deep-copied</li>
<li>The receiving Ractor needs to send it to another Ractor</li>
</ul>
<p>Even given this "extreme" case, the numbers don't show a very big difference, so I would say it is probably not a good idea to introduce now with the limited knowledge we have now. We'll have a better idea of how Ractor is used and what the bottlenecks are after it gains traction.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883352020-11-02T20:17:49ZEregon (Benoit Daloze)
<ul></ul><p>marcandre (Marc-Andre Lafortune) wrote in <a href="#note-11">#note-11</a>:</p>
<blockquote>
<p>The use-case seems unusual:</p>
<ul>
<li>Need to send very big object to a Ractor</li>
<li>That object can not be deep-frozen for some reason, or moved so it deep-copied</li>
</ul>
</blockquote>
<p>That sounds like the perfect anti-pattern to me, so agreed this new APIs seem overkill for now and not worth it.</p>
<p>If it's a big object:</p>
<ul>
<li>If it can be frozen, freeze it.</li>
<li>If it must be mutable, then it should have its own Ractor that "encapsulates" it.</li>
<li>Copying doesn't seem to make sense if it's mutated (because it will only mutate one of the copies). But anyway one can still use the existing Ractor#send/yield APIs to copy it.</li>
</ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883362020-11-02T20:23:00ZEregon (Benoit Daloze)
<ul></ul><p>ko1 (Koichi Sasada) wrote in <a href="#note-10">#note-10</a>:</p>
<blockquote>
<p>I rewrote copying logic without marshal protocol (<a href="https://github.com/ruby/ruby/pull/3728" class="external">https://github.com/ruby/ruby/pull/3728</a>).</p>
</blockquote>
<p>That is great!</p>
<p>I remember doing something quite similar in <a href="https://eregon.me/thesis/mozart2slides.pdf" class="external">https://eregon.me/thesis/mozart2slides.pdf</a> (slides 28-35)</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883462020-11-03T16:35:53Zko1 (Koichi Sasada)
<ul></ul><p>Beside the performance, I want to use it for the features:</p>
<blockquote>
<p>Ractor::Basket#sender returns the sending ractor.<br>
Ractor::Basket#sender = a_ractor changes the sending ractor.<br>
Ractor::Basket#value returns the content.</p>
</blockquote> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883472020-11-03T20:20:59ZEregon (Benoit Daloze)
<ul></ul><p>ko1 (Koichi Sasada) wrote in <a href="#note-14">#note-14</a>:</p>
<blockquote>
<p>Beside the performance, I want to use it for the features:</p>
<blockquote>
<p>Ractor::Basket#sender returns the sending ractor.</p>
</blockquote>
</blockquote>
<p><code>Ractor#receive_and_sender</code> (aka <code>recvfrom</code> but with a proper name) and <code>Ractor.yield_and_sender</code> would be enough for that, right?</p>
<p>Of course, the sender Ractor can send <code>Ractor.current</code>, so this can already be emulated.</p>
<blockquote>
<blockquote>
<p>Ractor::Basket#sender = a_ractor changes the sending ractor.</p>
</blockquote>
</blockquote>
<p>Any concrete use-case?<br>
It sounds bad to me to use core types to maintain custom state.<br>
Also if this affects the first basket instance, it would expose mutable state and data races.<br>
The Basket instance should probably be deeply frozen anyway (if introduced).</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883492020-11-03T21:14:32Zko1 (Koichi Sasada)
<ul></ul><p>Eregon (Benoit Daloze) wrote in <a href="#note-15">#note-15</a>:</p>
<blockquote>
<p><code>Ractor#receive_and_sender</code> (aka <code>recvfrom</code> but with a proper name) and <code>Ractor.yield_and_sender</code> would be enough for that, right?</p>
</blockquote>
<p>Yes, it is enough. However, the name is too long and ambiguous (maybe <code>receive_value_and_its_sender</code>).</p>
<p>I like basket API for this purpose.<br>
Also we can extend the information, for example sending source location (for debugging purpose).</p>
<blockquote>
<blockquote>
<blockquote>
<p>Ractor::Basket#sender = a_ractor changes the sending ractor.</p>
</blockquote>
</blockquote>
<p>Any concrete use-case?</p>
</blockquote>
<p>Not sure now.</p>
<blockquote>
<p>It sounds bad to me to use core types to maintain custom state.<br>
Also if this affects the first basket instance, it would expose mutable state and data races.<br>
The Basket instance should probably be deeply frozen anyway (if introduced).</p>
</blockquote>
<p>No, Basket is copied every time on current implementation (one basket object is created on receiving timing).<br>
But I agree we can make it frozen and shareable.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=883572020-11-04T11:09:20ZEregon (Benoit Daloze)
<ul></ul><p>ko1 (Koichi Sasada) wrote in <a href="#note-16">#note-16</a>:</p>
<blockquote>
<p>Eregon (Benoit Daloze) wrote in <a href="#note-15">#note-15</a>:</p>
<blockquote>
<p><code>Ractor#receive_and_sender</code> (aka <code>recvfrom</code> but with a proper name) and <code>Ractor.yield_and_sender</code> would be enough for that, right?</p>
</blockquote>
</blockquote>
<p>Actually it would be <code>Ractor#receive_with_sender</code> and <code>Ractor#take_with_sender</code> (or some variation, but for receive+take), my mistake<br>
(I was thinking about Fiber.yield which also waits for a message back).<br>
We can only know the sender when "receiving a message".<br>
Knowing the sender when sending a message is useless, it's always Ractor.current.</p>
<blockquote>
<p>I like basket API for this purpose.<br>
Also we can extend the information, for example sending source location (for debugging purpose).</p>
</blockquote>
<p>I see, <code>Ractor#receive_basket</code> and <code>Ractor#take_basket</code> might be nicer to get more information when receiving a message.</p>
<p><code>send_basket</code> and <code>yield_basket</code> seem unnecessary, if <code>send/yield</code> already recognize it's a basket and extract the value out of it.</p>
<p>Or, if <code>send/yield</code> explicitly disallow sending a basket and require <code>ractor.yield basket.value</code> (since it's meaningless to pass the sender's sender there).<br>
That would not allow the optimization, but the API feels cleaner to me that way.<br>
A basket is then just a (value, sender) tuple, and not something that implicitly holds a serialized version of the object.<br>
Also serializing might not have exactly the same semantics as deep copying, so it seems a risk of inconsistency, for very small performance gains.</p> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=884732020-11-13T16:01:12ZDan0042 (Daniel DeLorme)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/88473/diff?detail_id=58286">diff</a>)</li></ul> Ruby master - Feature #17298: Ractor's basket communication APIshttps://bugs.ruby-lang.org/issues/17298?journal_id=884742020-11-13T16:16:55ZDan0042 (Daniel DeLorme)
<ul></ul><p>A comment about naming... my first impression was that a "basket" is a container with multiple objects to transmit between Ractors. Something like</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">basket</span> <span class="o">=</span> <span class="no">Ractor</span><span class="o">::</span><span class="no">Basket</span><span class="p">.</span><span class="nf">new</span>
<span class="n">basket</span><span class="p">.</span><span class="nf">to_move</span> <span class="o"><<</span> <span class="n">a</span>
<span class="n">basket</span><span class="p">.</span><span class="nf">to_copy</span> <span class="o"><<</span> <span class="n">b</span>
<span class="n">basket</span><span class="p">.</span><span class="nf">to_share</span> <span class="o"><<</span> <span class="n">c</span>
<span class="no">Ractor</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">basket</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">basket</span><span class="o">|</span>
<span class="o">...</span>
<span class="k">end</span>
</code></pre>
<p>I found it counterintuitive to understand this was not the proposal. Since it only contains a single value, I think "envelope" is a better metaphor. An envelope usually contains a single letter, whereas a basket usually contains several (picnic) items.</p>
<p>But from an OOP perspective I still don't understand why all those "basket" methods. Since there's already a Ractor::Basket object involved, why not send that via the existing methods? So rather than <code>Ractor.yield_basket Ractor.receive_basket</code> it feels more OO to have <code>Ractor.yield Basket.new(Ractor.receive)</code>. Actually in the case of the bridge ractor it would just be <code>Ractor.yield Ractor.receive</code>, and if the object being passed is a basket then it's efficient, otherwise it's not.</p>