Oct82008

Running multiple Io VM's under OpenMP

Filed under: io openmp 

Io's concurrency is implemented as async cooperative coroutines. This gives great performance, but doesn't take advantage of multiple cores. Luckily the Io VM is threadsafe, so it shouldn't be too hard to write a program that can run multiple VM's, each in its own thread.

I decided to try OpenMP to implement this as it provides a nice way to do this sort of thing.

This example doesn't quite work just yet. It does sometimes, and other times it segfaults. I'm not sure if the problem is my code, Io, or OpenMP at this point (although I'm suspecting OpenMP due to some messages I've seen in forums).

[UPDATE: It turns out it was a static variable introduced into Io's coroutine library. It's fixed in git - thanks Steve!]

In any case, I'm posting it here in the hopes that it might inspire someone or maybe solicit some assistance.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <omp.h>
#include "io/IoState.h"

void usage ( char *program ) {
  fprintf ( stderr, "Usage: %s [-n threads] <script>\n", program );
  exit ( EXIT_FAILURE );
}

void do_io_state ( char *scriptname ) {
#define CMD "doFile(\"%s\")"
  char *cmd;
  IoState *self;

  cmd = (char *) alloca ( strlen ( scriptname ) + strlen ( CMD ) - 1 );
  sprintf ( cmd, CMD, scriptname );

  self = IoState_new ( );
  IoState_init ( self );
  IoState_doCString_ ( self, cmd );
  IoState_free ( self );
}

int main ( int argc, char **argv ) {
  int threadcount = 1;
  char *scriptname = NULL;
  int opt;

  while ( ( opt = getopt ( argc, argv, "n:" ) ) != -1 ) {
    switch ( opt ) {
    case 'n':
       threadcount = atoi ( optarg );
       break;
    default:
      usage ( argv [ 0 ] );
    }
  }

  if ( ! ( scriptname = argv [ optind ] ) )
    usage ( argv [ 0 ] );

  omp_set_num_threads ( threadcount );

#pragma omp parallel
  do_io_state ( scriptname );

  return 0;
}

To compile it, run this:

$ gcc -fopenmp test.c -o test -I/usr/local/include/io -lgomp -liovmall

You'll need to have the Io source headers in /usr/local/include/io (you have to copy them by hand from the source tree).

Then you can run it like so:

$ ./test -n 4 test.io

where test.io has a really useful bit of code such as:

"hello, world" println

You should then get the amazing output:

hello, world
hello, world
hello, world
hello, world

I haven't programmed in C for many years, so don't be too disappointed =)



0 comments Leave a comment


Dec292008

Multiple Io VM's under OpenMP revisited

Filed under: io openmp 

It came up the other day on the Io mailing list that managing multiple Io VM's (each with an HTTP server) was a bit too complicated. Since I'd done a little testing in this area, I decided to see what I could come up with.

First off, we need a bootstrap to launch our VM's. Again I'm utilizing OpenMP to this end:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <getopt.h>
#include <omp.h>
#include "IoState.h"

void usage ( char *program ) {
  fprintf ( stderr, "Usage: %s [-n threads] <script>\n", program );
  exit ( EXIT_FAILURE );
}

char *mmap_src ( char *script ) {
  int src;
  struct stat finfo;
  char *buffer;

  src = open ( script, O_RDONLY );
  fstat ( src, &finfo );
  buffer = mmap ( 0, finfo.st_size, PROT_READ, MAP_SHARED, src, 0 );
  close ( src );

  return buffer;
}

void spawn_vm ( char *buffer ) {
  IoState *self;

  self = IoState_new ( );
  IoState_init ( self );
  IoState_doCString_ ( self, buffer );
  IoState_free ( self );
}

int main ( int argc, char **argv ) {
  int threadcount = 0;
  char *script = NULL, *script_src;
  int opt;

  while ( ( opt = getopt ( argc, argv, "n:" ) ) != -1 ) {
    switch ( opt ) {
    case 'n':
      threadcount = atoi ( optarg );
      break;
    default:
      usage ( argv [ 0 ] );
    }
  }

  if ( ! ( script = argv [ optind ] ) )
    usage ( argv [ 0 ] );

  script_src = mmap_src ( script );

  if ( threadcount < 1 )
    threadcount = omp_get_num_procs ( );
  threadcount++;
  omp_set_num_threads ( threadcount );

#pragma omp parallel
  {
#pragma omp single nowait
    spawn_vm ( mmap_src ( "do_server.io" ) );

    spawn_vm ( script_src );
  }

  return 0;
}

This is called mio.c and is compiled with:

gcc -fopenmp -Wall -I/usr/local/include/io \
      -L/usr/local/lib/io -liovmall -lgomp -o mio mio.c

Next, we need a DistributedObjects server so we can centralize our configuration (and maybe use it for other stuff later on). We'll name this file do_server.io:

Shared := Object clone do (
    vars := Map clone
    ip_addr := "127.0.0.1"
    ip_port := 5000

    put := method ( identifier, value,
        vars atPut ( identifier, value )
        return value
    )

    get := method ( identifier,
        return vars at ( identifier )
    )

    addr := method ( return ip_addr )

    port := method (
        ip_port = ip_port + 1
        return ip_port
    )
)

doServer := DOServer clone do (
    setRootObject ( Shared clone )
    setPort ( 8456 )
    writeln ( "Starting DistributedObject Server at 8456" )
)

doServer start

And finally, our HTTP servers:

loop ( // until we have a connection to the DOServer
    System sleep ( 0.5 )
    shared := nil
    do_conn := DOConnection clone setHost ( "127.0.0.1" ) setPort ( 8456 ) connect
    e := try (
        shared = do_conn serverObject
    )
    if ( shared type == "DOProxy", break )
)

server := HttpServer clone do (
    addr := shared addr
    port := shared port
    setHost ( addr )
    setPort ( port )

    writeln ( "Starting HttpServer at #{addr}:#{port}" interpolate )

    renderResponse := method ( request, response,
        response body = (
            "<html><body>hello, world from #{addr}:#{port}</body></html>" interpolate
        )
    )
)

server start

We'll name this file runserver.io.

Now we can run our servers with something like:

./mio -n 4 runserver.io

Of course, it's not much use to run four separate servers on four separate ports, so we need a proxy. As usual, I'll utilize Nginx, which is extremely fast and lightweight and has built-in load-balancing features:

upstream io_server {
    server 127.0.0.1:5001;
    server 127.0.0.1:5002;
    server 127.0.0.1:5003;
    server 127.0.0.1:5004;
}

server {
    listen       80;
    server_name  _;

    location / {
        proxy_pass http://io_server;
    }
}

(obviously this config is just showing the relevant bits to our application).



0 comments Leave a comment




Copyright © 2007, Cliff Wells