Ruby「ファイバ」vs. Io「アクター」 スピード対決


平たく言うとコルーチン対決。ついでに Squeak Smalltalk もスレッドで参戦。環境は 1GHz PowerPC, OS X


まず、一個あたりの生成にかかる時間を計測。

Ruby

(1..10).each do |n|
  GC.start
  num_fs = (n * 1e3).to_i

  t0 = Time.new
  root = Fiber.current
  fs = (1..num_fs).collect do | i |
    Fiber.new{ loop{ root pass } }
  end
  time = (Time.now - t0) * 1e3
  fs = nil

  print "N = #{num_fs}, #{(time * 1.0e3 / num_fs).to_i} microseconds per fiber\n"
end


結果。

N = 1000, 173 microseconds per fiber
N = 2000, 251 microseconds per fiber
N = 3000, 361 microseconds per fiber
N = 4000, 426 microseconds per fiber
N = 5000, 609 microseconds per fiber
N = 6000, 677 microseconds per fiber
N = 7000, 785 microseconds per fiber
N = 8000, 839 microseconds per fiber
N = 9000, 905 microseconds per fiber
N = 10000, 1009 microseconds per fiber

Io はオブジェクトがアクターなので、オブジェクトの生成速度を計るかんじ。

Io

for(n, 1, 10,
   Collector collect
   numActors := n * 1e3
   root := Coroutine currentCoroutine
   Actor := Object clone do(
      pass := method( loop( yield ) )
   )
   actors := List clone
  
   time := Date secondsToRun(
      numActors repeat( actors push(Actor clone) )
   ) * 1e3
   actors = nil
   writeln("N = ", numActors, ", ", (time * 1e3 / numActors) round, " microseconds per actor")
)
N = 1000, 39 microseconds per actor
N = 2000, 33 microseconds per actor
N = 3000, 26 microseconds per actor
N = 4000, 23 microseconds per actor
N = 5000, 22 microseconds per actor
N = 6000, 20 microseconds per actor
N = 7000, 22 microseconds per actor
N = 8000, 19 microseconds per actor
N = 9000, 23 microseconds per actor
N = 10000, 23 microseconds per actor


これは Io の圧勝。


続いて、リングノードベンチ。

Ruby

class Node
  attr_accessor :neighbor

  def initialize(m)
    @num_msgs = m
    @fiber = Fiber.new{ @num_msgs.times{ @neighbor.ping } }
  end

  def ping
    @fiber.pass
  end
end

m = 100

[10, 100, 1000].each do |n|
  first = prev = Node.new(m)
  (2..n).each do
    prev.neighbor = (prev = Node.new(m))
  end
  prev.neighbor = first

  t0 = Time.now
  first.ping
  time = Time.now - t0

  print "N = #{n}, M = #{m}, #{(time * 1e3).to_i} milliseconds\n"
end
N = 10, M = 100, 6 milliseconds
N = 100, M = 100, 269 milliseconds
N = 1000, M = 100, 3711 milliseconds

Io

m := 100

Node := Object clone do(
   newSlot("neighbor", nil)
   numMsgs := m
   ping := method(
      // writeln(numMsgs)
      if(numMsgs > 0, numMsgs = numMsgs - 1; neighbor @@ping)
   )
)

list(10, 100, 500) foreach(n,
   first := prev := Node clone
   for(i, 2, n, prev setNeighbor(prev = Node clone))
   prev setNeighbor(first)

   Collector collect
   time := Date secondsToRun(first @@ping; yield) * 1e3

   writeln("N = ", n, ", M = ", m, ", ", time round, " milliseconds")
)
N = 10, M = 100, 1891 milliseconds
N = 100, M = 100, 16362 milliseconds
N = 500, M = 100, 93091 milliseconds


こちらは Ruby の圧勝。Ioぉ〜。がっかりだよっ!w



参考まで、Squeak Smalltalk の場合。

生成

| numProcess processes time |
(1 to: 10) do: [:nn |
   Smalltalk garbageCollect.
   numProcess := nn * 1e3.

   time := [processes := (1 to: numProcess) collect: [:idx |
      [[Processor yield] repeat] newProcess]] timeToRun.
   processes do: [:proc | proc terminate].

   World findATranscript: nil.
   Transcript cr; show: ('N = {1}, {2} microseconds per process'
      format:  {numProcess. time * 1.0e3 / numProcess roundTo: 0.1})]
N = 1000, 10.0 microseconds per process
N = 2000, 8.0 microseconds per process
N = 3000, 7.3 microseconds per process
N = 4000, 8.0 microseconds per process
N = 5000, 8.0 microseconds per process
N = 6000, 9.0 microseconds per process
N = 7000, 7.9 microseconds per process
N = 8000, 8.0 microseconds per process
N = 9000, 8.1 microseconds per process
N = 10000, 8.1 microseconds per process

リングノード

| m |
m := 100.

World findATranscript: nil.
#(10 100 1000) do: [:n |
   | sema firstMailbox myMailbox time |

   sema := Semaphore new.
   firstMailbox := myMailbox := SharedQueue new.
   1 to: n do: [:idx |
      | neighbor |
      neighbor := idx < n ifTrue: [SharedQueue new] ifFalse: [firstMailbox].
      [  | numMsgs |
         numMsgs := m.
         [numMsgs > 0] whileTrue: [
            "Transcript cr; show: numMsgs."
            numMsgs := numMsgs - 1.
            myMailbox next.
            neighbor nextPut: #ping].
         idx = n ifTrue: [sema signal]] fixTemps fork.
      myMailbox := neighbor].

   time := [firstMailbox nextPut: #ping. sema wait] timeToRun.

   Transcript cr; show: ('N = {1}, M = {2}, {3} milliseconds' format: {n. m. time})]
N = 10, M = 100, 27 milliseconds
N = 100, M = 100, 239 milliseconds
N = 1000, M = 100, 3704 milliseconds