views:

147

answers:

2

I have a c++ program which performs one function. It loads a large data-file into an array, receives an array of integers and performs a lookup in that array, returning a single integer. I am currently calling the program with each integer as an argument, like so:

$ ./myprogram 1 2 3 4 5 6 7

I also have a ruby script, and I would like this script to utilize the c++ program. Currently, I am doing this like so.

Ruby Code:

arguments = "1 2 3 4 5 6 7"
an_integer = %x{ ./myprogram #{arguemnts} }
puts "The program returned #{an_integer}" #=> The program returned 2283

This is all working properly, but my problem is that each time ruby makes this call, the c++ program has to reload the data-file (which is over 100mb) - very slow, and very inefficient.

How can I rewrite my c++ program load the file only once, allowing me to make many lookups via a ruby script without reloading the file each time. Would using sockets be a sensible approach? Writing the c++ program as a ruby extension?

Obviously I am not an experienced c++ programmer, so thanks for your help.

+1  A: 

A possible approach is to modify your C++ program so that it takes its input from the standard input stream (std::cin) instead of from the command line parameters, and returns its result through the standard ouput (std::cout) instead of as main's return value. Your Ruby script would then use popen to launch the C++ program.

Assuming the C++ program currently looks like:

// *pseudo* code
int main(int argc, char* argv[])
{
    large_data_file = expensive_operation();

    std::vector<int> input = as_ints(argc, argv);
    int result = make_the_computation(large_data_file, input);

    return result;
}

It would be transformed into something like:

// *pseudo* code
int main(int argc, char* argv[])
{
    large_data_file = expensive_operation();

    std::string input_line;
    // Read a line from standard input
    while(std:::getline(std::cin, input_line)){
        std::vector<int> input = tokenize_as_ints(input_line);
        int result = make_the_computation(large_data_file, input);

        //Write result on standard output
        std::cout << result << std::endl;
    }

    return 0;
}

And the Ruby script would look like

io = IO.popen("./myprogram", "rw")
while i_have_stuff_to_compute
    arguments = get_arguments()
    # Write arguments on the program's input stream
    IO.puts(arguments)
    # Read reply from the program's output stream
    result = IO.readline().to_i();
end

io.close()
Éric Malenfant
+3  A: 

Well,

You could go about this a number of different ways.

1) A simple, potentially ugly way to do this is to have your c++ run and intermittently check for a file, have your ruby script produce said file containing your arguments. Your C++ program would then use the contained arguments returning it's result to a result file which you could wait on within your ruby script... This is obviously HACK TASTIC but it's uber simple to implement and would work.

2) Expose your c++ code as a c extension to ruby. This is not as hard as it's sounds especially if you use RICE and would provide significantly less hackie solution.

3) If your c++ can be exposed through a c header file then it's almost trivial to construct a bridge using FFI. Jeremy Hinegardner gave a good lecture on constructing FFI interfaces at rubyconf heres the screencast

4) D-Bus provides an application communication bus, you could alter your C++ app to take advantage of said event bus and pass messages from your ruby using ruby-dbus

There are of course a thousand other routes... Maybe one or the other of these could prove viable :)

Cheers!

roja