Friday, January 7, 2011

Repeatedly Calling A Shell Program In Ruby

I'm writing a GA in Ruby just for fun, but as I ran it for the first time, I realized that my fitness function (a game simulation) was far too slow.  So I rewrote my fitness function in C and tried unsuccessfully to call it inline in Ruby using the RubyInline gem.  Instead, I compiled it as a separate program and called it from Ruby, using the backtick operator.

Of course, I have to call the program once for every individual in the population, and initializing a program in a tight loop is very CPU intensive.  What makes more sense is to move the loop to the C program, open a connection once in Ruby, and send a message to the C program telling it to run a simulation.  The C program runs, outputs the fitness, and waits for more input.  This way, initialization is only done once, reducing the overhead to create the address space for a new process every single time through the loop.

Here is how I accomplished this in Ruby:

#!/usr/bin/ruby
class Simulator
  def initialize
    @fh = IO.popen("./c_simulator", "w+")
  end


  def simulate(parameters)
    @fh.puts parameters
    @fh.gets.to_i
  end
end


...


sim = Simulator.new
population.each{|p|
  p.fitness = sim.simulate(p.getParameters)
}


IO.popen(prog, mode) opens a persistent connection to another process.  The mode "w+" means an IO stream is opened for reading and writing to/from the process.  You can then puts and gets to the handle just like you would from a file.  And then on the C side:

int main(int argc, char** argv) {
  char line[4096];
  char *cptr;
  do {
    cptr = fgets(line, 4096, stdin);
    if (cptr) {
      // Do processing, assign fitness to fitness
      printf("%d\n", fitness);
      fflush(stdout);
    }
  } while (cptr != NULL);
  return 0;
}


Fairly efficient way to call an external program from a Ruby loop, provided you are writing the external program yourself.

No comments:

Post a Comment