authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-02-18 12:09:38+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-29 05:48:58+02:00
logef7f828338604415549c4ad886358786fdf16c02
tree30e4312789ea7b38798172276359d4b47eada200
parent0569f1f6a779d8021cac1a63334564347f617605

std.sort.pdq: use block-based branchless partitioning

Implement BlockQuicksort (was a TODO item). This avoids branch mispredictions on random data, yielding ~2x speedup on large random arrays while leaving pre-sorted and equal-element fast paths unaffected.

1 files changed, 69 insertions(+), 8 deletions(-)

lib/std/sort/pdq.zig+69-8
...@@ -175,17 +175,78 @@ fn partition(a: usize, b: usize, pivot: *usize, context: anytype) bool {...@@ -175,17 +175,78 @@ fn partition(a: usize, b: usize, pivot: *usize, context: anytype) bool {
175 i += 1;175 i += 1;
176 j -= 1;176 j -= 1;
177177
178 while (true) {178 const block_size = 64;
179 while (i <= j and context.lessThan(i, a)) i += 1;179 var offsets_l: [block_size]u8 align(std.atomic.cache_line) = undefined;
180 while (i <= j and !context.lessThan(j, a)) j -= 1;180 var offsets_r: [block_size]u8 align(std.atomic.cache_line) = undefined;
181 if (i > j) break;181
182 var offsets_l_base = i;
183 var offsets_r_base = j;
184 var num_l: usize = 0;
185 var num_r: usize = 0;
186 var start_l: usize = 0;
187 var start_r: usize = 0;
188
189 while (i <= j) {
190 const num_unknown = j + 1 - i;
191 const left_split = if (num_l == 0)
192 @min(block_size, if (num_r == 0) num_unknown / 2 else num_unknown)
193 else
194 0;
195 const right_split = if (num_r == 0)
196 @min(block_size, num_unknown - left_split)
197 else
198 0;
199
200 for (0..left_split) |k| {
201 offsets_l[num_l] = @intCast(k);
202 num_l += @intFromBool(!context.lessThan(i + k, a));
203 }
204 i += left_split;
182205
183 context.swap(i, j);206 for (0..right_split) |k| {
184 i += 1;207 offsets_r[num_r] = @intCast(k);
185 j -= 1;208 num_r += @intFromBool(context.lessThan(j - k, a));
209 }
210 j -= right_split;
211
212 const num = @min(num_l, num_r);
213 for (0..num) |m| {
214 context.swap(
215 offsets_l_base + offsets_l[start_l + m],
216 offsets_r_base - offsets_r[start_r + m],
217 );
218 }
219 num_l -= num;
220 num_r -= num;
221 start_l += num;
222 start_r += num;
223
224 if (num_l == 0) {
225 start_l = 0;
226 offsets_l_base = i;
227 }
228 if (num_r == 0) {
229 start_r = 0;
230 offsets_r_base = j;
231 }
186 }232 }
187233
188 // TODO: Enable the BlockQuicksort optimization234 if (num_l > 0) {
235 while (num_l > 0) {
236 num_l -= 1;
237 context.swap(offsets_l_base + offsets_l[start_l + num_l], j);
238 j -= 1;
239 }
240 i = j + 1;
241 }
242 if (num_r > 0) {
243 while (num_r > 0) {
244 num_r -= 1;
245 context.swap(offsets_r_base - offsets_r[start_r + num_r], i);
246 i += 1;
247 }
248 j = i - 1;
249 }
189250
190 context.swap(j, a);251 context.swap(j, a);
191 pivot.* = j;252 pivot.* = j;