Project

General

Profile

Feature #17298

Updated by ko1 (Koichi Sasada) over 3 years ago

This ticket proposes send_basket/send_receive, yield_basket/take_basket APIs to make effective and flexible bridge ractors. 

 ## Background 

 When we want to send an object as a message, usually we need to copy it. 
 Copying is achieved by marshal protocol, and receiver load it immediately. 

 If we want to make a bridge ractor which receive a message and send it to another ractor, the immediate loading is not effective. 

 Receiver load 

 ```ruby 
 bridge = Ractor.new do 
   Ractor.yield Ractor.receive 
 end 

 consumer = Ractor.new bridge do |from| 
   obj = from.take 
   do_task(obj) 
 end 

 msg = [1, 2, 3] 
 bridge.send msg 
 ``` 

 In this case, the array (`[1, 2, 3]`) is 

 * (1) dumped at the first `bridge.send msg` 
 * (2) loaded at `Ractor.receive` 
 * (3) dumped again at `Ractor.yield` 
 * (4) laoded at `from.take` 

 Essentially we only need one dump/load pair, but now it needs 2 pairs. 

 Mixing "moving" is more complex. 
 Now there is no way to pass the "moving" status to the bridge ractors, we can not make a moving bridge. 

 ## Proposal 

 To make more effective and flexible bridge ractors, we propose new basket APIs 

 * `Ractor.receive_basket` 
 * `Ractor#send_basket` 
 * `Ractor.take_basket` 
 * `Ractor.yield_basket` 

 They receive a message, but remaining dumped state and send it without dumping again. 
 We can rewrite the above example with these APIs. 

 ```ruby 
 bridge = Ractor.new do 
   Ractor.yield_basket Ractor.receive_basket 
 end 

 consumer = Ractor.new bridge do |from| 
   obj = from.take 
   do_task(obj) 
 end 

 msg = [1, 2, 3] 
 bridge.send msg 
 ``` 

 In this case, 

 * (1) dumped at the first `bridge.send msg` 
 * (2) laoded at `from.take` 

 we only need one dump/load pair. 


 ## Implementation 

 https://github.com/ruby/ruby/pull/3725 

 ## Evaluation 

 The following program makes 4 type of bridges and pass an array as a message through them. 

 ```ruby 
 USE_BASKET = false 

 receive2yield = Ractor.new do 
   loop do 
     if USE_BASKET 
       Ractor.yield_basket Ractor.receive_basket 
     else 
       Ractor.yield Ractor.receive 
     end 
   end 
 end 

 receive2send = Ractor.new receive2yield do |r| 
   loop do 
     if USE_BASKET 
       r.send_basket Ractor.receive_basket 
     else 
       r.send Ractor.receive 
     end 
   end 
 end 

 take2yield = Ractor.new receive2yield do |from| 
   loop do 
     if USE_BASKET 
       Ractor.yield_basket from.take_basket 
     else 
       Ractor.yield from.take 
     end 
   end 
 end 

 take2send = Ractor.new take2yield, Ractor.current do |from, to| 
   loop do 
     if USE_BASKET 
       to.send_basket from.take_basket 
     else 
       to.send from.take 
     end 
   end 
 end 

 AN = 1_000 
 LN = 10_000 

 ary = Array.new(AN) # 1000 
 LN.times{ 
   receive2send << ary 
   Ractor.receive 
 } 

 # This program passes the message as: 
 #     main -> 
 #     receive2send -> 
 #     receive2yield -> 
 #     take2yield -> 
 #     take2send -> 
 #     main 
 ``` 

 The result is: 

 ``` 
 w/ basket API     0m2.056s 
 w/o basket API    0m5.974s 
 ``` 

 on my machine (=~ x3 faster). 

 (BTW, if we have a TVar, we can change the value `USE_BASKET` dynamically) 

 ## Discussion 

 ### naming 

 Of course, naming is an issue. Now, I named "_basket" because source code using this terminology. 
 There are other candidates: 

 * container metaphor 
   * package 
   * parcel 
   * box 
   * envelope 
   * packet (maybe bad idea because of confusion of networking) 
   * bundle (maybe bad idea because of confusion of bin/bundle) 
 * "don't touch the content" metaphor 
   * raw 
   * seal unseal 
   * unopened 

 I like "basket" because I like picnic. 

 ### feature 

 Now, basket is represented by "Ractor::Basket" and there is no methods. 
 We can add the following feature: 

 * `Ractor::Basket#sender` return the sending ractor. 
 * `Ractor::Basket#sender = a_ractor` change the sending ractor. 
 * `Ractor::Basket#value` returns the content. 

 There was another proposal `Ractor.recvfrom`, but we only need these APIs. 

Back