title: C versus vDSP versus NEON - How could NEON be as slow as C?
body: <p>How could NEON be as slow as C?</p>
  
  <p>I have been trying to build a fast Histogram function that would bucket incoming values into ranges by assigning them a value - which is the range threshold they are closest to. This is something that would be applied to images so it would have to be fast (assume an image array of 640x480 so 300,000 elements) . The histogram range numbers are multiples (0,25,50,75,100) . Inputs would be float and final outputs would obviously be integers</p>
  
  <p>I tested the following versions on xCode by opening a new empty project (no app delegate) and just using the main.m file. I removed all linked libraries with the exception of Accelerate.</p>
  
  <p>Here is the C implementation: the older version was plenty of if then but here is the final optimized logic. it took 11s and 300ms.</p>
  
  <pre><code>int main(int argc, char *argv[])
  {
    NSLog(@"starting");
  
    int sizeOfArray=300000;
  
    float* inputArray=(float*) malloc(sizeof(float)*sizeOfArray);
    int* outputArray=(int*) malloc(sizeof(int)*sizeOfArray);
  
    for (int i=0; i&lt;sizeOfArray; ++i)
    {
      inputArray[i]=88.5;
    }
  
    //Assume range is [0,25,50,75,100]
    int lcd=25;
  
    for (int j=0; j&lt;1000; ++j)// just to get some good time interval
    {
      for (int i=0; i&lt;sizeOfArray; ++i)
      {
          //a 60.5 would give a 50. An 88.5 would give 100
          outputArray[i]=roundf(inputArray[i]/lcd)*lcd;
      }
    }
  NSLog(@"done");
  }
  </code></pre>
  
  <p>Here is the vDSP implementation. Even with some of the tedious floating to integer back and forth, it took only 6s! almost 50% improvement!</p>
  
  <pre><code>//vDSP implementation
   int main(int argc, char *argv[])
   {
     NSLog(@"starting");
  
     int sizeOfArray=300000;
  
     float* inputArray=(float*) malloc(sizeof(float)*sizeOfArray);
     float* outputArrayF=(float*) malloc(sizeof(float)*sizeOfArray);//vDSP requires matching of input output
     int* outputArray=(int*) malloc(sizeof(int)*sizeOfArray); //rounded value to the nearest integere
     float* finalOutputArrayF=(float*) malloc(sizeof(float)*sizeOfArray);
     int* finalOutputArray=(int*) malloc(sizeof(int)*sizeOfArray); //to compare apples to apples scenarios output
  
  
     for (int i=0; i&lt;sizeOfArray; ++i)
     {
       inputArray[i]=37.0; //this will produce an final number of 25. On the other hand 37.5 would produce 50.
     }
  
  
     for (int j=0; j&lt;1000; ++j)// just to get some good time interval
     {
       //Assume range is [0,25,50,75,100]
       float lcd=25.0f;
  
       //divide by lcd
       vDSP_vsdiv(inputArray, 1, &amp;lcd, outputArrayF, 1,sizeOfArray);
  
       //Round to nearest integer
       vDSP_vfixr32(outputArrayF, 1,outputArray, 1, sizeOfArray);
  
       // MUST convert int to float (cannot just cast) then multiply by scalar - This step has the effect of rounding the number to the nearest lcd.
      vDSP_vflt32(outputArray, 1, outputArrayF, 1, sizeOfArray);
      vDSP_vsmul(outputArrayF, 1, &amp;lcd, finalOutputArrayF, 1, sizeOfArray);
      vDSP_vfix32(finalOutputArrayF, 1, finalOutputArray, 1, sizeOfArray);
     }
    NSLog(@"done");
  }
  </code></pre>
  
  <p>Here is the Neon implementation. This is my first so play nice!  it was slower than vDSP and took 9 sec and 300ms which did not make sense to me. Either vDSP is better optimized than NEON or I am doing something wrong.</p>
  
  <pre><code>//NEON implementation
  int main(int argc, char *argv[])
  {
  NSLog(@"starting");
  
  int sizeOfArray=300000;
  
  float* inputArray=(float*) malloc(sizeof(float)*sizeOfArray);
  float* finalOutputArrayF=(float*) malloc(sizeof(float)*sizeOfArray);
  
  for (int i=0; i&lt;sizeOfArray; ++i)
  {
      inputArray[i]=37.0; //this will produce an final number of 25. On the other hand 37.5 would produce 50.
  }
  
  
  
  for (int j=0; j&lt;1000; ++j)// just to get some good time interval
  {
      float32x4_t c0,c1,c2,c3;
      float32x4_t e0,e1,e2,e3;
      float32x4_t f0,f1,f2,f3;
  
      //ranges of histogram buckets
      float32x4_t buckets0=vdupq_n_f32(0);
      float32x4_t buckets1=vdupq_n_f32(25);
      float32x4_t buckets2=vdupq_n_f32(50);
      float32x4_t buckets3=vdupq_n_f32(75);
      float32x4_t buckets4=vdupq_n_f32(100);
  
      //midpoints of ranges
      float32x4_t thresholds1=vdupq_n_f32(12.5);
      float32x4_t thresholds2=vdupq_n_f32(37.5);
      float32x4_t thresholds3=vdupq_n_f32(62.5);
      float32x4_t thresholds4=vdupq_n_f32(87.5);
  
  
      for (int i=0; i&lt;sizeOfArray;i+=16)
      {
          c0= vld1q_f32(&amp;inputArray[i]);//load
          c1= vld1q_f32(&amp;inputArray[i+4]);//load
          c2= vld1q_f32(&amp;inputArray[i+8]);//load
          c3= vld1q_f32(&amp;inputArray[i+12]);//load
  
  
          f0=buckets0;
          f1=buckets0;
          f2=buckets0;
          f3=buckets0;
  
          //register0
          e0=vcgtq_f32(c0,thresholds1);
          f0=vbslq_f32(e0, buckets1, f0);
  
          e0=vcgtq_f32(c0,thresholds2);
          f0=vbslq_f32(e0, buckets2, f0);
  
          e0=vcgtq_f32(c0,thresholds3);
          f0=vbslq_f32(e0, buckets3, f0);
  
          e0=vcgtq_f32(c0,thresholds4);
          f0=vbslq_f32(e0, buckets4, f0);
  
  
  
          //register1
          e1=vcgtq_f32(c1,thresholds1);
          f1=vbslq_f32(e1, buckets1, f1);
  
          e1=vcgtq_f32(c1,thresholds2);
          f1=vbslq_f32(e1, buckets2, f1);
  
          e1=vcgtq_f32(c1,thresholds3);
          f1=vbslq_f32(e1, buckets3, f1);
  
          e1=vcgtq_f32(c1,thresholds4);
          f1=vbslq_f32(e1, buckets4, f1);
  
  
          //register2
          e2=vcgtq_f32(c2,thresholds1);
          f2=vbslq_f32(e2, buckets1, f2);
  
          e2=vcgtq_f32(c2,thresholds2);
          f2=vbslq_f32(e2, buckets2, f2);
  
          e2=vcgtq_f32(c2,thresholds3);
          f2=vbslq_f32(e2, buckets3, f2);
  
          e2=vcgtq_f32(c2,thresholds4);
          f2=vbslq_f32(e2, buckets4, f2);
  
  
          //register3
          e3=vcgtq_f32(c3,thresholds1);
          f3=vbslq_f32(e3, buckets1, f3);
  
          e3=vcgtq_f32(c3,thresholds2);
          f3=vbslq_f32(e3, buckets2, f3);
  
          e3=vcgtq_f32(c3,thresholds3);
          f3=vbslq_f32(e3, buckets3, f3);
  
          e3=vcgtq_f32(c3,thresholds4);
          f3=vbslq_f32(e3, buckets4, f3);
  
  
          vst1q_f32(&amp;finalOutputArrayF[i], f0);
          vst1q_f32(&amp;finalOutputArrayF[i+4], f1);
          vst1q_f32(&amp;finalOutputArrayF[i+8], f2);
          vst1q_f32(&amp;finalOutputArrayF[i+12], f3);
      }
  }
  NSLog(@"done");
  }
  </code></pre>
  
  <p>PS: this is my first benchmarking on this scale so I tried to keep it simple (large loops, setup code constant, using NSlog to print start/end time, only accelerate framework linked). If any of these assumptions are significantly impacting the outcome, please critique.</p>
  
  <p>Thanks</p>