2

I want to process a large array of floating-point numbers on the ARM processor, using Neon technology to calculate them four at a time. Everything's fine for operations like add and multiply, but what do I do if my calculation goes into an IF block? Example:

// In the non-vectorized original code, A is an array of many floating-point
// numbers, which are calculated one at a time.  Now they're packed 
// into a vector and processed four at a time

...calculate A...

if (A > 10.f)
{
    A = A+5.f;
}
else
{
    A = A+10.f;
}

Now, which IF branch do I execute? What if some of the values in the vector being processed are greater than 10 and some are less? Is it even possible to vectorize code like this?

user2084572
  • 331
  • 3
  • 12
  • See: [Is the preference with SIMD to always get rid of branching?](https://stackoverflow.com/q/8902911/253056) – Paul R Nov 16 '17 at 08:38
  • See: [How to use NEON comparison (greater than or equal to) instruction?](https://stackoverflow.com/q/3788380/253056) – Paul R Nov 16 '17 at 08:45
  • See: [Neon Comparison](https://stackoverflow.com/q/29984542/253056) – Paul R Nov 16 '17 at 08:48

3 Answers3

5

I'll add to the answers so far by describing how to code it in Neon intrinsics.

  1. In general, you don't do IF-block logic based on parallel register contents, because one value may require one branch of the IF block and a different value in the same register may require another. "Eager execution" means doing all the possible calculations first, and then deciding which results to actually use in which lanes. (Remember, you don't gain anything by doing a Neon calculation for only one lane of a register. Any computation that has to be done at all, gets done for all 2 or 4 lanes.)

  2. To do an IF-based computation, use Neon conditional intrinsics e.g. "greater than" to make a bitmask, and then a "select" function to populate the final result according to the bitmask

double aval[2] = {11.5, 9.5};

float64x2_t AA= vld1q_f64(aval);       // an array with two 64-bit double values

float64x2 TEN= vmovq_n_f64(10.f);      // load a constant into a different array
float64x2 FIVE= vmovq_n_f64(5.f);      // load a constant into a different array

// Do both of the computations
float64x2 VALIFTRUE = vaddq_f64(AA, TEN);  // {21.5, 19.5}
float64x2 VALIFFALSE = vaddq_f64(AA, FIVE);  // {16.5, 14.5}


uint64x2_t IF1 = vcgtq_f64 (AA, TEN);  // comparison "(if A > 10.)"

The return value of vcgtq_f64 is not a set of doubles but two 64-bit unsigned integers. They're actually a bit mask that can be used by "bitwise select" functions such as vbslq_f64. The first 64 bits of IF1 are all 1's (the greater-than condition was true) and the second 64 bits are all 0's.

AA = vbslq_f64(IF1, VALIFTRUE, VALIFFALSE);  // {21.5, 14.5}

...and each lane of AA is populated with either VALIFTRUE or VALIFFALSE for that lane, as appropriate.

  1. What if eager execution is just too slow--the computations in one branch are very costly in processor time and you want to avoid doing them at all if you can? You'd have to verify that that branch condition isn't true for any of the vector lanes and then skip over the computations with a proper "if" statement. Perhaps someone else can comment on how well this works out in practice.
user2084572
  • 331
  • 3
  • 12
  • Is there a way to detect whether all lanes of the mask are true or false ? What about whether any lane is true or false ? – gnzlbg Sep 05 '18 at 12:52
  • Those are integer or bitwise logic operations that don't require intrinsics. For instance, if all lanes of AA are false, then AA==0 and if at least one lane is true, then AA != 0 – user2084572 Sep 06 '18 at 16:19
  • So should I just cast the 64-bit or 128-bit wide vector to a 64-bit / 128-bit integer, and then compare it to 0, 0xffff..., etc ? – gnzlbg Sep 07 '18 at 16:29
1

If-else slaloms are a nightmare for virtually all CPUs, especially for vector machines such as NEON that doesn't have any conditional branch on its own.

Hence we apply "eager execution" on problems like this.

  • A boolean mask gets created
  • Both if and else blocks are computed
  • The "right" result gets selected by the mask

I think it won't be problem converting the aarch32 code below to intrinsics.

//aarch32
    vadd.f32    vecElse, vecA, vecTen // vecTen contains 10.0f
    vcgt.f32    vecMask, vecA, vecTen
    vadd.f32    vecA, vecA, vecFive
    vbif        vecA, vecElse, vecMask

//aarch64
    fadd    vecElse.4s, vecA.4s, vecTen.4s
    fcmgt   vecMask.4s, vecA.4s, vecTen.4s
    fadd    vecA.4s, vecA.4s, vecFive.4s
    bif     vecA.16b, vecElse.16b, vecMask.16b
Jake 'Alquimista' LEE
  • 6,197
  • 2
  • 17
  • 25
  • Not sure why this got a down-vote (without even the courtesy of a comment), but I'll add an up-vote to cancel it out... – Paul R Dec 05 '17 at 16:08
  • @PaulR Thanks! We seem to have some trolls here :-) – Jake 'Alquimista' LEE Dec 05 '17 at 16:12
  • 1
    Yes, I'm always puzzled by these random drive-by down-votes, particularly when there is nothing obviously wrong with a question or answer. What is wrong with people ? ;-) – Paul R Dec 05 '17 at 16:14
0

In general with SIMD branching logic you use a compare mask and then select alternate results accordingly. I'll give pseudo code for your example and you can convert this to intrinsics or asm as needed:

v5 = vector(5)              // set up some constant vectors
v10 = vector(10)
vMask = compare_gt(vA, v10) // generate mask for vector compare A > 10
va = add(vA, v10)           // vA = vA + 10 (all elements, unconditionally)
vtemp = and(v5, vMask)      // generate temp vector of 5 and 0 values based on mask
va = sub(vA, vTemp)         // subtract 5 from elements which are <= 10
Paul R
  • 208,748
  • 37
  • 389
  • 560