| author | |
| committer | |
| log | 57ca3512e429f962edc15ea2b24a57f9c778320d |
| tree | 2ac7674c3bda934ba2c1261780000e197a38e75c |
| parent | 340cf85faadbac0c51e072f588b70bd77f7c6c7b |
our pdq implementation uses an explicit stack of `Range` entries to avoid recursion.
The size of this stack was set to `log2(maxInt(usize) + 1)`. on 32-bit
targets like `wasm32-freestanding`, this would hit the maximum size,
causing OOB. there were two issues:
- `max_limit`, which controls how many imbalanced partitions are allowed
before the algorithm falls back to heapsort, was computed as
`floorPowerOfTwo(n) + 1` instead of `log2(n)` as in the original c++
reference implementation. e.g. for `n = 1_000_000`, this allowed 524289
bad partitions before heapsort would kick in, instead of 19.
this meant deeply imbalanced partitions could accumulate stack pushes
essentially without limit. However, i didn't observe any meaningful
difference in benchmarks.
- the worst-case stack depth is bounded by `log2(n) + max_limit`,
which approaches `2 * log2(n)`. The reference c++ and go implementation
doesn't have this problem because they don't use explicit stack buffer.1 files changed, 2 insertions(+), 3 deletions(-)
lib/std/sort/pdq.zig+2-3| ... | ... | @@ -44,12 +44,11 @@ pub fn pdqContext(a: usize, b: usize, context: anytype) void { |
| 44 | 44 | // slices of up to this length get sorted using insertion sort. |
| 45 | 45 | const max_insertion = 24; |
| 46 | 46 | // number of allowed imbalanced partitions before switching to heap sort. |
| 47 | const max_limit = std.math.floorPowerOfTwo(usize, b - a) + 1; | |
| 47 | const max_limit = if (b > a) math.log2_int(usize, b - a) else 0; | |
| 48 | 48 | |
| 49 | 49 | // set upper bound on stack memory usage. |
| 50 | 50 | const Range = struct { a: usize, b: usize, limit: usize, leftmost: bool }; |
| 51 | const stack_size = math.log2(math.maxInt(usize) + 1); | |
| 52 | var stack: [stack_size]Range = undefined; | |
| 51 | var stack: [2 * @bitSizeOf(usize)]Range = undefined; | |
| 53 | 52 | var range = Range{ .a = a, .b = b, .limit = max_limit, .leftmost = true }; |
| 54 | 53 | var top: usize = 0; |
| 55 | 54 |