You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: _posts/2026-03-24-VectorAPI-control-flow.md
+98-1Lines changed: 98 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -127,9 +127,106 @@ impact if the mask entries are `true` or `false`.
127
127
On AVX512 we have some individual down-spikes.
128
128
I suspect this might be due to [(mis)alignment causing multimodal performance](https://eme64.github.io/blog/2026/01/12/Alignment-Performance.html).
129
129
130
+
What we learn from this example:
131
+
Generally, vectorization leads to speedups because of parallelization - but with a theoretical maximum of the vector length.
132
+
On NEON where we have 16 byte elements in a vector this would mean we could only gain at most a factor of 16.
133
+
But using masked vector operations, we can sometimes gain more performance:
134
+
especially if we can avoid the branch misprediction penalty that the scalar implementation suffers from
135
+
for some branch probabilities.
136
+
130
137
**Algorithm 2: pieceWise**
131
138
132
-
TODO
139
+
This example is a bit more contrived, but it shows a very interesting effect.
140
+
We want to apply a function `f(x)` to every input in the array `a`,
141
+
and store the results in the array `r`.
142
+
But the function `f` is piece-wise: for small input values we compute some multiplications, for high input values we compute some square roots.
143
+
Here a plot of the function:
144
+
145
+
<imgwidth="600"alt="piece-wise function f"src="https://github.com/user-attachments/assets/fb587786-9dba-4333-b8b4-119a1994db5c" />
146
+
147
+
It is important to say:
148
+
computing square roots is very expensive compared to multiplications.
149
+
150
+
Reference implementation:
151
+
```java
152
+
for (int i =0; i < a.length; i++) {
153
+
float ai = a[i];
154
+
if (ai <1f) {
155
+
float a2 = ai * ai;
156
+
float a4 = a2 * a2;
157
+
float a8 = a4 * a4;
158
+
r[i] = a8;
159
+
} else {
160
+
float s2 = (float)Math.sqrt(ai);
161
+
float s4 = (float)Math.sqrt(s2);
162
+
float s8 = (float)Math.sqrt(s4);
163
+
r[i] = s8;
164
+
}
165
+
}
166
+
```
167
+
168
+
Vector API implementation (v1), unconditionally compute both branches for all lanes:
169
+
```java
170
+
for (i =0; i <SPECIES_F.loopBound(a.length); i +=SPECIES_F.length()) {
171
+
var ai =FloatVector.fromArray(SPECIES_F, a, i);
172
+
var mask = ai.compare(VectorOperators.LT, 1f);
173
+
var a2 = ai.lanewise(VectorOperators.MUL, ai);
174
+
var a4 = a2.lanewise(VectorOperators.MUL, a2);
175
+
var a8 = a4.lanewise(VectorOperators.MUL, a4);
176
+
var s2 = ai.lanewise(VectorOperators.SQRT);
177
+
var s4 = s2.lanewise(VectorOperators.SQRT);
178
+
var s8 = s4.lanewise(VectorOperators.SQRT);
179
+
var v = s8.blend(a8, mask);
180
+
v.intoArray(r, i);
181
+
}
182
+
// omitting scalar cleanup
183
+
```
184
+
185
+
Vector API implementation (v2), unconditionally compute the multiplications for all lanes, but only compute the square roots if at least one lane needs it:
186
+
```java
187
+
for (i =0; i <SPECIES_F.loopBound(a.length); i +=SPECIES_F.length()) {
188
+
var ai =FloatVector.fromArray(SPECIES_F, a, i);
189
+
var mask = ai.compare(VectorOperators.LT, 1f);
190
+
var a2 = ai.lanewise(VectorOperators.MUL, ai);
191
+
var a4 = a2.lanewise(VectorOperators.MUL, a2);
192
+
var a8 = a4.lanewise(VectorOperators.MUL, a4);
193
+
var v = a8;
194
+
// SQRT is expensive, so only call if it necessary
195
+
if (!mask.allTrue()) {
196
+
var s2 = ai.lanewise(VectorOperators.SQRT);
197
+
var s4 = s2.lanewise(VectorOperators.SQRT);
198
+
var s8 = s4.lanewise(VectorOperators.SQRT);
199
+
v = s8.blend(a8, mask);
200
+
}
201
+
v.intoArray(r, i);
202
+
}
203
+
// omitting scalar cleanup
204
+
```
205
+
206
+
Running on an `x64 AVX512` and an `aarch64 NEON` machine:
0 commit comments