pi.rb                                                                          Page 1 of 3
    1:   #! /usr/bin/env jruby
    2:
    3:   # First things first.
    4:   # Load Java integration support
    5:   require "java"
    6:
    7:   # Add ’lib’ in the same directory as this file to the load path
    8:   $: << File.join(File.dirname(__FILE__), ’lib’)
    9:
   10:   # Load Java libraries
   11:   require ’scala-library’
   12:   require ’akka/akka-actor-1.1.2’
   13:
   14:   # Here, we import Java classes so that we don’t have to prefix them
   15:   # with Java::.
   16:   java_import ’akka.actor.Actors’
   17:   java_import ’akka.actor.ActorRef’
   18:   java_import ’akka.actor.UntypedActor’
   19:   java_import ’akka.actor.UntypedActorFactory’
   20:   java_import ’akka.routing.CyclicIterator’
   21:   java_import ’akka.routing.InfiniteIterator’
   22:   java_import ’akka.routing.Routing’
   23:   java_import ’akka.routing.UntypedLoadBalancer’
   24:   # Java’s built-in classes don’t need to be quoted (for a String)
   25:   java_import java.util.concurrent.CountDownLatch
   26:
   27:   # Convenience method for creating an Actor
   28:   def actorOf(&code)
   29:     Actors.actorOf(Class.new do
   30:                      include UntypedActorFactory
   31:                      define_method(:create) do |*args|
   32:                        code[*args]
   33:                      end
   34:                    end.new)
   35:   end
   36:
   37:   class Calculate; end
   38:   # Struct.new(...) creates an instance having the instance variables
   39:   # passed
   40:   class Work < Struct.new(:start, :nrOfElements); end
   41:   class Result < Struct.new(:value); end
   42:
   43:   class Worker < UntypedActor
   44:     # needed by actorOf
   45:     def self.create(*args)
   46:       new *args
   47:     end
   48:
   49:    # define the work
   50:    def calculatePiFor(start, nrOfElements)
   51:      # Here, we are using the identity
   52:      # Pi = 4 * sum_{k=0}^{infty} (-1)^k/(2 k + 1)
   53:      # We divide the work into chunks of nrOfElements
   54:      # Enumerable#inject may be a little foreign for Java programmers, but
   55:      # it can be thought of as a shorthand for looping and re-assigning
   56:      # the value of the block inside to the memo varialbe ("acc" in this case)
   57:      # "M...N" means a Range starting at M, ending *1 before* N.
   58:      ((start * nrOfElements)...((start + 1) * nrOfElements)).inject(0) do |acc,i|
   59:        acc + 4.0 * (1 - (i.modulo 2) * 2) / (2 * i + 1)
   60:      end
   61:    end
   62:
   63:    # message handler
   64:    def onReceive(message)
   65:      # examining the class of an object is not very Ruby-esque.
pi.rb                                                                         Page 2 of 3
   66:       # in generatl, Rubyists prefer duck typing
   67:       if message.kind_of? Work
   68:         work = message
   69:
   70:         # perform the work
   71:         result = calculatePiFor(work.start, work.nrOfElements)
   72:
   73:         # reply with the result
   74:         context.replyUnsafe(Result.new(result))
   75:
   76:       else
   77:         raise IllegalArgumentException.new "Unknown message [#{message + b}]"
   78:       end
   79:     end
   80:   end
   81:
   82:   class PiRouter < UntypedLoadBalancer
   83:     attr_reader :seq
   84:
   85:     def initialize(workers)
   86:       super() # make sure the underlying Java proxy is properly initialized
   87:       @seq = CyclicIterator.new(workers)
   88:     end
   89:   end
   90:
   91:   class Master < UntypedActor
   92:     def initialize(nrOfWorkers, nrOfMessages, nrOfElements, latch)
   93:       super()
   94:       @nrOfMessages, @nrOfElements, @latch = nrOfMessages, nrOfElements, latch
   95:       @nrOfResults, @pi = 0, 0.0
   96:
   97:       # create the workers
   98:       workers = java.util.ArrayList.new
   99:       nrOfWorkers.times { workers << Actors.actorOf(Worker).start }
  100:
  101:      # wrap them with a load-balancing router
  102:      @router = actorOf { PiRouter.new(workers) }.start
  103:    end
  104:
  105:    # message handler
  106:    def onReceive(message)
  107:      if message.kind_of? Calculate
  108:        # schedule work
  109:        @nrOfMessages.times do |start|
  110:          @router.sendOneWay(Work.new(start, @nrOfElements), context)
  111:        end
  112:
  113:        # send a PoisonPill to all workers telling them to shut down themselves
  114:        @router.sendOneWay(Routing::Broadcast.new(Actors.poisonPill))
  115:
  116:        # send a PoisonPill to the router, telling him to shut himself down
  117:        @router.sendOneWay Actors.poisonPill
  118:      elsif message.kind_of? Result # handle result from the worker
  119:        @pi += message.value
  120:        @nrOfResults += 1
  121:        context.stop if @nrOfResults == @nrOfMessages
  122:      else
  123:        raise IllegalArgumentException.new "Unknown message [#{message}]"
  124:      end
  125:    end
  126:
  127:    def preStart
  128:      @start = java.lang.System.currentTimeMillis
  129:    end
  130:
pi.rb                                                                         Page 3 of 3
  131:     def postStop
  132:       # tell the world that the calculation is complete
  133:       puts format("ntPi estimate: tt%sntCalculation time: t%s millis",
  134:           @pi, (java.lang.System.currentTimeMillis - @start))
  135:       @latch.countDown
  136:     end
  137:   end
  138:
  139:
  140:   class Pi
  141:     def self.calculate(nrOfWorkers, nrOfElements, nrOfMessages)
  142:       # this latch is only plumbing to know when the calculation is completed
  143:       latch = CountDownLatch.new(1)
  144:
  145:       # create the master
  146:       master = Actors.actorOf do
  147:         Master.new(nrOfWorkers.to_i, nrOfMessages.to_i, nrOfElements.to_i, latch)
  148:       end.start
  149:       master.sendOneWay(Calculate.new) # start the calculation
  150:       latch.await # wait for master to shut down
  151:     end
  152:   end
  153:
  154:   # Ready to do the real work
  155:   if (ARGV.length < 3)
  156:     exit "Usage: $0 num_workers num_elements num_messages"
  157:   end
  158:
  159:   Pi.calculate(*ARGV[0..2])

Pi

  • 1.
    pi.rb Page 1 of 3 1: #! /usr/bin/env jruby 2: 3: # First things first. 4: # Load Java integration support 5: require "java" 6: 7: # Add ’lib’ in the same directory as this file to the load path 8: $: << File.join(File.dirname(__FILE__), ’lib’) 9: 10: # Load Java libraries 11: require ’scala-library’ 12: require ’akka/akka-actor-1.1.2’ 13: 14: # Here, we import Java classes so that we don’t have to prefix them 15: # with Java::. 16: java_import ’akka.actor.Actors’ 17: java_import ’akka.actor.ActorRef’ 18: java_import ’akka.actor.UntypedActor’ 19: java_import ’akka.actor.UntypedActorFactory’ 20: java_import ’akka.routing.CyclicIterator’ 21: java_import ’akka.routing.InfiniteIterator’ 22: java_import ’akka.routing.Routing’ 23: java_import ’akka.routing.UntypedLoadBalancer’ 24: # Java’s built-in classes don’t need to be quoted (for a String) 25: java_import java.util.concurrent.CountDownLatch 26: 27: # Convenience method for creating an Actor 28: def actorOf(&code) 29: Actors.actorOf(Class.new do 30: include UntypedActorFactory 31: define_method(:create) do |*args| 32: code[*args] 33: end 34: end.new) 35: end 36: 37: class Calculate; end 38: # Struct.new(...) creates an instance having the instance variables 39: # passed 40: class Work < Struct.new(:start, :nrOfElements); end 41: class Result < Struct.new(:value); end 42: 43: class Worker < UntypedActor 44: # needed by actorOf 45: def self.create(*args) 46: new *args 47: end 48: 49: # define the work 50: def calculatePiFor(start, nrOfElements) 51: # Here, we are using the identity 52: # Pi = 4 * sum_{k=0}^{infty} (-1)^k/(2 k + 1) 53: # We divide the work into chunks of nrOfElements 54: # Enumerable#inject may be a little foreign for Java programmers, but 55: # it can be thought of as a shorthand for looping and re-assigning 56: # the value of the block inside to the memo varialbe ("acc" in this case) 57: # "M...N" means a Range starting at M, ending *1 before* N. 58: ((start * nrOfElements)...((start + 1) * nrOfElements)).inject(0) do |acc,i| 59: acc + 4.0 * (1 - (i.modulo 2) * 2) / (2 * i + 1) 60: end 61: end 62: 63: # message handler 64: def onReceive(message) 65: # examining the class of an object is not very Ruby-esque.
  • 2.
    pi.rb Page 2 of 3 66: # in generatl, Rubyists prefer duck typing 67: if message.kind_of? Work 68: work = message 69: 70: # perform the work 71: result = calculatePiFor(work.start, work.nrOfElements) 72: 73: # reply with the result 74: context.replyUnsafe(Result.new(result)) 75: 76: else 77: raise IllegalArgumentException.new "Unknown message [#{message + b}]" 78: end 79: end 80: end 81: 82: class PiRouter < UntypedLoadBalancer 83: attr_reader :seq 84: 85: def initialize(workers) 86: super() # make sure the underlying Java proxy is properly initialized 87: @seq = CyclicIterator.new(workers) 88: end 89: end 90: 91: class Master < UntypedActor 92: def initialize(nrOfWorkers, nrOfMessages, nrOfElements, latch) 93: super() 94: @nrOfMessages, @nrOfElements, @latch = nrOfMessages, nrOfElements, latch 95: @nrOfResults, @pi = 0, 0.0 96: 97: # create the workers 98: workers = java.util.ArrayList.new 99: nrOfWorkers.times { workers << Actors.actorOf(Worker).start } 100: 101: # wrap them with a load-balancing router 102: @router = actorOf { PiRouter.new(workers) }.start 103: end 104: 105: # message handler 106: def onReceive(message) 107: if message.kind_of? Calculate 108: # schedule work 109: @nrOfMessages.times do |start| 110: @router.sendOneWay(Work.new(start, @nrOfElements), context) 111: end 112: 113: # send a PoisonPill to all workers telling them to shut down themselves 114: @router.sendOneWay(Routing::Broadcast.new(Actors.poisonPill)) 115: 116: # send a PoisonPill to the router, telling him to shut himself down 117: @router.sendOneWay Actors.poisonPill 118: elsif message.kind_of? Result # handle result from the worker 119: @pi += message.value 120: @nrOfResults += 1 121: context.stop if @nrOfResults == @nrOfMessages 122: else 123: raise IllegalArgumentException.new "Unknown message [#{message}]" 124: end 125: end 126: 127: def preStart 128: @start = java.lang.System.currentTimeMillis 129: end 130:
  • 3.
    pi.rb Page 3 of 3 131: def postStop 132: # tell the world that the calculation is complete 133: puts format("ntPi estimate: tt%sntCalculation time: t%s millis", 134: @pi, (java.lang.System.currentTimeMillis - @start)) 135: @latch.countDown 136: end 137: end 138: 139: 140: class Pi 141: def self.calculate(nrOfWorkers, nrOfElements, nrOfMessages) 142: # this latch is only plumbing to know when the calculation is completed 143: latch = CountDownLatch.new(1) 144: 145: # create the master 146: master = Actors.actorOf do 147: Master.new(nrOfWorkers.to_i, nrOfMessages.to_i, nrOfElements.to_i, latch) 148: end.start 149: master.sendOneWay(Calculate.new) # start the calculation 150: latch.await # wait for master to shut down 151: end 152: end 153: 154: # Ready to do the real work 155: if (ARGV.length < 3) 156: exit "Usage: $0 num_workers num_elements num_messages" 157: end 158: 159: Pi.calculate(*ARGV[0..2])