‹ Work with Mark: ShadowplayWork with Mark: Bottom-up Approach ›

Work with Mark: Array Primitives and Benchmarking the Framework

2006-09-18 13:40 research, supercollider

I spent a lot of time benchmarking the code for the agents in the different versions of the multi-agent frameworks I posted about here earlier. The best performance boost was (of course) when I ported some vital parts of the SuperCollider code to C. So, for example, one CPU-intensive task that the agents had to do a lot was to find out about their surroundings. And another 'heavy' task was to calculate the distance to other objects. Each operation wasn't very demanding on its own, but when hundreds of agents would do this at the same time, we really needed the speed of the C primitives.

Below is the C code I came up with. It replaces some of the computational heavy parts in the Surroundings and ALoaction SuperCollider classes. To try them out you'd need to download the SuperCollider source code from github.com/supercollider/supercollider, add my code to the file PyrArrayPrimitives.cpp and then recompile the whole application. To use these primitives you also need to edit the file extArrayedCollection.sc from the A4.zip package posted here earlier.

One issue we then had was to distribute this 'hack'. SuperCollider doesn't have an API for adding extensions like this to the language (but there's a nice plugin architecture for the server). So I had to build dedicated SuperCollider applications including this speed hack.

int prArrayAsALocationIndex(struct VMGlobals *g, int numArgsPushed)
{
  PyrSlot *a, *b;
  PyrObject *obj;
  int size, i;
  double asIndex, w, worldSize;
  a = g->sp - 1;
  b = g->sp;
  worldSize = b->ui;
  obj = a->uo;
  size = obj->size;
  asIndex= 0;
  for (i=0; i<size; ++i) {
    getIndexedDouble(obj, i, &w);
    asIndex= asIndex+(pow(worldSize, i)*w);
  }
  SetFloat(a, asIndex);
  return errNone;
}
int prArrayAsALocationRoundedIndex(struct VMGlobals *g, int numArgsPushed)
{
  PyrSlot *a, *b;
  PyrObject *obj;
  int size, i;
  double asIndex, w, worldSize;
  a = g->sp - 1;
  b = g->sp;
  worldSize = b->ui;
  obj = a->uo;
  size = obj->size;
  asIndex= 0;
  for (i=0; i<size; ++i) {
    getIndexedDouble(obj, i, &w);
    w= sc_round(w, 1.0);
    w= sc_clip(w, 0, worldSize-1);
    asIndex= asIndex+(pow(worldSize, i)*w);
  }
  SetFloat(a, asIndex);
  return errNone;
}
int prArrayDistance(struct VMGlobals *g, int numArgsPushed)
{
  PyrSlot *a, *b;
  PyrObject *obj1, *obj2;
  int size, i;
  double w1, w2, distance;
  a = g->sp - 1;
  b = g->sp;
  if (b->utag != tagObj || a->uo->classptr != b->uo->classptr) return errWrongType;
  obj1 = a->uo;
  obj2 = b->uo;
  size = obj1->size;
  distance= 0;
  for (i=0; i<size; ++i) {
    getIndexedDouble(obj1, i, &w1);
    getIndexedDouble(obj2, sc_mod(i, size), &w2);
    distance= distance+pow(w2-w1, 2);
  }
  SetFloat(a, fabs(sqrt(distance)));
  return errNone;
}
int prArraySurroundings(struct VMGlobals *g, int numArgsPushed)
{
  PyrSlot *a, *b, *c, *d, *areaArraySlots, *indexArraySlots, *outArraySlots;
  PyrObject *obj, *areaArray, *indexArray, *outArray;
  int areaSize, outSize, i, j, worldDim, area;
  double w;
  a = g->sp - 3;  //list
  b = g->sp - 2;  //worldDim
  c = g->sp - 1;  //area - as float possible later?
  d = g->sp;  //boolean - exclude/include fix later
  if (b->utag != tagInt) return errWrongType;
  if (c->utag != tagInt) return errWrongType;
  if (d->utag != tagTrue && d->utag != tagFalse) return errWrongType;
  obj = a->uo;
  worldDim = b->ui;
  area = c->ui;
  areaSize = area*2+1;
  indexArray = newPyrArray(g->gc, worldDim, 0, true);
  indexArraySlots = indexArray->slots;
  indexArray->size = worldDim;
  if (IsTrue(d)) {  //--build index array excluding
    areaArray = newPyrArray(g->gc, areaSize-1, 0, true);
    areaArraySlots = areaArray->slots;
    areaArray->size = areaSize-1;
    int j = 0;
    for (i=0; i<areaSize-1; ++i) {
      int temp = 0-area+i;
      if (temp==0) {j++;}
      areaArraySlots[i].ucopy = temp+j;
    }
    outSize = pow(areaSize, worldDim)-1;
  } else {  //--build index array including
    areaArray = newPyrArray(g->gc, areaSize, 0, true);
    areaArraySlots = areaArray->slots;
    areaArray->size = areaSize;
    for (i=0; i<areaSize; ++i) {
      areaArraySlots[i].ucopy = 0-area+i;
    }
    outSize = pow(areaSize, worldDim);
  }
  for (i=0; i<worldDim; ++i) {
    SetObject(indexArraySlots+i, areaArray);
  }
  //indexArray is here... [[-1, 0, 1]] or [[-1, 0, 1], [-1, 0, 1]] etc. for area=1
  //or [[-2, -1, 0, 1, 2]] or [[-2, -1, 0, 1, 2], [-2, -1, 0, 1, 2]] etc. for area=2
  //--all tuples
  outArray = newPyrArray(g->gc, outSize*sizeof(PyrObject), 0, true);
  outArraySlots = outArray->slots;
  outArray->size = outSize;
  for (i=0; i<outSize; ++i) {
    int k = i;
    PyrObject *tempArray = newPyrArray(g->gc, worldDim, 0, true);
    PyrSlot *tempArraySlots = tempArray->slots;
    tempArray->size = worldDim;
    for (j=worldDim-1; j>=0; --j) {
      tempArraySlots[j].ucopy = areaArraySlots[k%areaSize].ucopy;
      getIndexedDouble(obj, j, &w);
      tempArraySlots[j].ucopy += w;
      k /= areaSize;
    }
    SetObject(outArraySlots+i, tempArray);
  }
  a->uo = outArray;
  return errNone;
}

Edit extArrayedCollection.sc (from A4.zip posted in an earlier blog entry) to look like this...

+ ArrayedCollection {
  asALocationIndex {|size|
    _ArrayAsALocationIndex
    ^this.primitiveFailed;
  }
  asALocationRoundedIndex {|size|
    _ArrayAsALocationRoundedIndex
    ^this.primitiveFailed;
  }
  distance {|list|
    _ArrayDistance
    ^this.primitiveFailed;
  }
  surroundings {|dimensions= 2, area= 1, exclude= true|
    _ArraySurroundings
    ^this.primitiveFailed;
  }
}

SuperCollider code for benchmarking...

//speedtest
({
  var size= 100, cSize= 2, rule= 30;
  var world, agents, y= 0, dict;
  dict= ();  /*lookup dictionary for rules*/
  8.do{|i| dict.put(i.asBinaryDigits(3).join.asSymbol, rule.asBinaryDigits[7-i])};
  ACell.rules= dict;
  world= APattern(size);  /*create 1d world*/
  size.do{|i| ACell(ALocation(world, [i]))};  /*fill up 1d grid with agents*/
  world.get(ALocation(world, [(size/2).round])).value= 1;  /*middle agent value=1 as init*/
  agents= world.items;
  while({y<size}, {
    /*here update.  first all agents.sense then all agents.act*/
    agents.do{|a| a.sense};
    /*agents.do{|a| a.location.list_([(size/2).round])};*/
    /*agents.do{|a| a.location= ALocation(a.location.world, [(size/2).round])};*/
    agents.do{|a| a.act};
    y= y+1;
  });
}.bench)
‹ Work with Mark: ShadowplayWork with Mark: Bottom-up Approach ›