Vector Arithmetic – Primitives Examples

As an example I want to show, how I write primitives to enhance the speed of vector arithmetic.

I created a new subclass of OSFloat: MSKOSFloat64Vector, which should be used as a n-dimensional vector. I also created a special class for single-precision vectors … but that’s another story. I just want to tell the story for the double-precision vectors – because they also have the same precision as the Float instances within VASmalltalk.

The main method for vector addition is a pure Smalltalk one. It has to prepare all information needed for the primitive. As you notice: the code generates always a new vector for the result of the addition, but the primitives will be able to use the receiver or the parameter “aVector” as the target also. This will increase the speed of computation, because “+” generates this result very often during calculation and most of the generated objects are thrown away – and remember my last posting ? Object Creation can be very time consuming !

+ aVector
  | newVector |
  newVector := self class new: self mskDimension.

  ^self
       primAddVector: self
       with: aVector
       into: newVector
       dimension: self mskDimension

The lower level method does only one thing: it calls the primitive:

primAddVector: first with: second into: target dimension: anInteger
  primitive: 'mskvectorextension.dll':MSKOSFloat32VectorAddition
  ^self primitiveFailed

And we see the order of the parameters:
* first vector
* second vector
* third vector for the result
* dimension of the vectors

Now we look at the primitive code:

EsUserPrimitive(MSKOSFloat64VectorAddition)
{
  U_32     dimension;
  U_32     rc;
  double   *firstFloat64Vector;
  double   *secondFloat64Vector;
  double   *targetFloat64Vector;

  "We search for the base address of the first vector ... we use an additional function, because this is stupid c-work within Smalltalk structures"
  firstFloat64Vector =   baseAddressForOSPtrObjects(EsPrimVMContext, EsPrimArgument(1));
  if (firstFloat64Vector == (double *)NULL)
    EsPrimFail(1, 1);

  "Same stuff for the second operand"
  secondFloat64Vector = baseAddressForOSPtrObjects(EsPrimVMContext, EsPrimArgument(2));
  if (secondFloat64Vector == (double *)NULL)
    EsPrimFail(1, 2);

  "... and for the target vector"
  targetFloat64Vector = baseAddressForOSPtrObjects(EsPrimVMContext, EsPrimArgument(3));
  if (targetFloat64Vector == (double *)NULL)
    EsPrimFail(1, 3);

  "And we need the number of values, the vectors are containing. We do not check, if all vectors have the same dimension. We assume it. We convert the value of the fourth parameter to an unsigned integer"
  rc = EsIntegerToI32(EsPrimArgument(4), &dimension);
  if (rc != EsPrimErrNoError)
    EsPrimFail(1, 4);

  "now we have all c-structures to do the actual computation - in a different 'normal' function - which would be callable by platform function"
  doFloat64VectorAddition( firstFloat64Vector, secondFloat64Vector, targetFloat64Vector, (unsigned int) dimension);

  "We put the result into the third parameter and we return this object as the result of this primitive"
  EsPrimSucceed( EsPrimArgument(3));
}

Now some code to find the base address of the OSFloat64Vector, where we may store the floating values. Please remember, that the memory is stored within Smalltalk memory or OS memory. The function must care about this.

void * baseAddressForOSPtrObjects(EsVMContext EsPrimVMContext,EsObject anObject)
{
  double   *float64;
  EsObject reference;
  EsObject offsetAndReftype;
  U_32     rc;
  U_32     referenceValue;
  U_32     offsetAndReftypeValue;

  // var inst index = 1 means attribute 'reference' of OSPtr instances
  reference = EsInstVarAt( anObject, 1);

  // var inst index = 2 means attribute 'offsetAndReftype' of OSPtr instances
  offsetAndReftype = EsInstVarAt( anObject, 2);

  // First we have to find out: Smalltalk memory or OS-based memory ...
  rc = EsIntegerToU32( offsetAndReftype, &offsetAndReftypeValue);
  if (rc != EsPrimErrNoError)
    return (double *) NULL;

  // = 1 means Smalltalk based memory and ByteArray in the attribute 'reference'
  if (offsetAndReftypeValue == 1)
  {
    // reference now points to an instance of ByteArray ... storage
    // therefore is in Smalltalk memory and we look for the address of
    // reference variable - which seams to be the start of the byte array
    float64 = (double *) EsInstVarAddr(reference);
  }
  else
    if (offsetAndReftypeValue == 2)
    {
      // reference now contains address of an OS memory, where the value
      // is stored
      rc = EsIntegerToU32( reference, &referenceValue);
      if (rc != EsPrimErrNoError)
        return (double *) NULL;

     // Why that ? Well Instantiations does this on the Smalltalk side also ....
      float64 = (double *) (referenceValue + (offsetAndReftypeValue >> 8)) ;
    }
    else
      return (double *) NULL;

  // return the pointer to the base address for storage of double precision floats ...
  return (void *) float64;
}

The computation of the vector addition is plain c code:

void doFloat64VectorAddition(double * firstFloat64Vector, double * secondFloat64Vector, double * targetFloat64Vector, unsigned int dimension)
{
  unsigned int index;
  for (index = 0 ; index < dimension ; index++)
  {
    *targetFloat64Vector = *firstFloat64Vector + *secondFloat64Vector;
    targetFloat64Vector++;
    firstFloat64Vector++;
    secondFloat64Vector++;
  }
}

I hope you get an idea, what you have to do to write your own primitive. Debugging is pretty worse – because you must have a debugger sitting on the DLL-entry point and going active, when Smalltalk calls your primitive, but I have not managed things like this. And problem is, that the primitive dll is locked when you call it for the first time. You have to exit VASmalltalk, recompile the library, exchange the library and start VASmalltalk again.

This topic is very new fo me and there may be bugs within this code – if you have suggestions, please let me know.

The Smalltalk source code for this extension is available at the VAST Goodies site and is named MSKVectorExtension.

In addition to this method there is also a vector-scalar multiplication, vector-subtraction and the accessing methods are primitive-enhanced – additional work will be done in the future.

The source code for the dll and the whole project is available at www.schrievkrom.de

This entry was posted in Smalltalk. Bookmark the permalink.