diff --git a/Changelog.md b/Changelog.md
new file mode 100644
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,8 @@
+# 0.2
+
+- Improve performance
+- Remove median guessing: remove `Guess` case from `MedianResult` type. Now custom median selection algorithms always have to pick existing value from array to be sorted (shouldn’t really be a limitation in practice)
+
+# 0.1
+
+- Initial release
diff --git a/Readme.md b/Readme.md
--- a/Readme.md
+++ b/Readme.md
@@ -1,591 +1,700 @@
-[![build](https://github.com/sergv/atomic-counter/actions/workflows/haskell-ci.yaml/badge.svg)](https://github.com/sergv/atomic-counter/actions/workflows/haskell-ci.yaml)
-
-# Synopsis
-
-This package features reasonable sort function that is a good default
-for sorting mutable vectors using well-known quicksort algorithm.
-During development one of the goals was making it perform on par with
-C++’s `std::sort`, i.e. fast.
-
-While providing reasonably good single-threaded default sorting, the
-algorithm in this package is also parallelisable and can provides ways
-to run on multiple cores using either threads or sparks, so bigger
-vectors can be sorted even faster.
-
-# Algorithm details
-
-Technically it’s an (introsort)[https://en.wikipedia.org/wiki/Introsort], i.e.
-pathological `O(N^2)` quicksort case will get delegated to heapsort when recursion
-gets too deep, thus making algorithm reliably `O(N * log(N))`.
-
-# Benchmarks
-
-The benchmark is to generate some random arrays of different length
-and sort them. For each array size N two versions are generated: one
-from range [0, N] (`with few duplicates` below),
-another from [0, N / 1000] (`with many duplicates` below) to
-introduce duplicates. And there’s one reasonably big array (around
-40,000 elements) with lots of duplicates that I managed to come by in
-the real world under `Sorting fuzzy matching scores vector` that comes
-from `test.txt` file in this repository. with
-
-Test setup: Intel i5-9600K CPU @ 3.70GHz, 6 cores, no hyperthreading, GHC 9.6.1.
-
-The default sort exported in `Data.Vector.Algorithms.Quicksort` is `Sequential` and `Median3or5`.
-
-```
-$ cabal run bench -- -j1 --time-mode wall --timeout 300
-All
-  Sorting fuzzy matching scores vector
-    C++:                           OK (0.54s)
-      377  μs ±  30 μs
-    Sequential ST Median3:         OK (0.36s)
-      315  μs ±  17 μs, 0.83x
-    Sequential IO Median3:         OK (0.31s)
-      288  μs ±  17 μs, 0.76x
-    ParStrategies ST Median3:      OK (0.15s)
-      285  μs ±  23 μs, 0.76x
-    ParStrategies IO Median3:      OK (0.16s)
-      306  μs ±  22 μs, 0.81x
-    Sequential ST Median3or5:      OK (0.28s)
-      261  μs ±  13 μs, 0.69x
-    Sequential IO Median3or5:      OK (0.28s)
-      264  μs ±  25 μs, 0.70x
-    ParStrategies ST Median3or5:   OK (0.14s)
-      268  μs ±  21 μs, 0.71x
-    ParStrategies IO Median3or5:   OK (0.15s)
-      272  μs ±  27 μs, 0.72x
-    Sequential ST AveragingMedian: OK (0.16s)
-      2.47 ms ± 191 μs, 6.55x
-    Threads IO Median3:            OK (0.31s)
-      295  μs ±  28 μs, 0.78x
-    Threads IO Median3or5:         OK (0.24s)
-      230  μs ±  12 μs, 0.61x
-    vector-algorithms heapsort:    OK (0.27s)
-      2.06 ms ±  97 μs, 5.45x
-    fallback heapsort:             OK (0.25s)
-      1.91 ms ± 106 μs, 5.07x
-  Sorting 10 random arrays of length 16 with few duplicates
-    C++:                           OK (0.36s)
-      654  ns ±  24 ns
-    Sequential ST Median3:         OK (0.31s)
-      554  ns ±  23 ns, 0.85x
-    Sequential IO Median3:         OK (0.16s)
-      559  ns ±  44 ns, 0.85x
-    ParStrategies ST Median3:      OK (0.16s)
-      578  ns ±  58 ns, 0.88x
-    ParStrategies IO Median3:      OK (0.32s)
-      578  ns ±  26 ns, 0.88x
-    Sequential ST Median3or5:      OK (0.16s)
-      569  ns ±  50 ns, 0.87x
-    Sequential IO Median3or5:      OK (0.16s)
-      571  ns ±  50 ns, 0.87x
-    ParStrategies ST Median3or5:   OK (0.17s)
-      580  ns ±  42 ns, 0.89x
-    ParStrategies IO Median3or5:   OK (0.16s)
-      577  ns ±  49 ns, 0.88x
-    Sequential ST AveragingMedian: OK (0.31s)
-      557  ns ±  27 ns, 0.85x
-    Threads IO Median3:            OK (0.24s)
-      1.71 μs ± 128 ns, 2.62x
-    Threads IO Median3or5:         OK (0.24s)
-      1.73 μs ± 120 ns, 2.64x
-    vector-algorithms heapsort:    OK (0.20s)
-      1.41 μs ± 117 ns, 2.16x
-    fallback heapsort:             OK (0.62s)
-      1.15 μs ±  29 ns, 1.76x
-  Sorting 10 random arrays of length 17 with few duplicates
-    C++:                           OK (0.21s)
-      758  ns ±  73 ns
-    Sequential ST Median3:         OK (0.38s)
-      701  ns ±  35 ns, 0.92x
-    Sequential IO Median3:         OK (0.20s)
-      728  ns ±  60 ns, 0.96x
-    ParStrategies ST Median3:      OK (0.22s)
-      773  ns ±  49 ns, 1.02x
-    ParStrategies IO Median3:      OK (0.21s)
-      766  ns ±  44 ns, 1.01x
-    Sequential ST Median3or5:      OK (0.39s)
-      727  ns ±  55 ns, 0.96x
-    Sequential IO Median3or5:      OK (0.20s)
-      724  ns ±  47 ns, 0.95x
-    ParStrategies ST Median3or5:   OK (0.22s)
-      793  ns ±  68 ns, 1.05x
-    ParStrategies IO Median3or5:   OK (0.22s)
-      791  ns ±  63 ns, 1.04x
-    Sequential ST AveragingMedian: OK (0.30s)
-      2.18 μs ±  92 ns, 2.88x
-    Threads IO Median3:            OK (0.14s)
-      1.89 μs ± 182 ns, 2.49x
-    Threads IO Median3or5:         OK (0.14s)
-      1.99 μs ± 176 ns, 2.63x
-    vector-algorithms heapsort:    OK (0.21s)
-      1.52 μs ± 114 ns, 2.01x
-    fallback heapsort:             OK (0.18s)
-      1.23 μs ± 116 ns, 1.62x
-  Sorting 10 random arrays of length 100 with few duplicates
-    C++:                           OK (0.16s)
-      4.33 μs ± 413 ns
-    Sequential ST Median3:         OK (0.38s)
-      5.56 μs ± 371 ns, 1.29x
-    Sequential IO Median3:         OK (0.20s)
-      5.55 μs ± 333 ns, 1.28x
-    ParStrategies ST Median3:      OK (0.21s)
-      6.03 μs ± 345 ns, 1.39x
-    ParStrategies IO Median3:      OK (0.50s)
-      7.24 μs ± 287 ns, 1.67x
-    Sequential ST Median3or5:      OK (0.20s)
-      5.57 μs ± 337 ns, 1.29x
-    Sequential IO Median3or5:      OK (0.20s)
-      5.59 μs ± 329 ns, 1.29x
-    ParStrategies ST Median3or5:   OK (0.22s)
-      6.28 μs ± 371 ns, 1.45x
-    ParStrategies IO Median3or5:   OK (0.22s)
-      6.52 μs ± 621 ns, 1.51x
-    Sequential ST AveragingMedian: OK (0.26s)
-      30.5 μs ± 2.8 μs, 7.05x
-    Threads IO Median3:            OK (0.13s)
-      7.28 μs ± 686 ns, 1.68x
-    Threads IO Median3or5:         OK (0.49s)
-      7.22 μs ± 296 ns, 1.67x
-    vector-algorithms heapsort:    OK (0.32s)
-      18.8 μs ± 1.4 μs, 4.35x
-    fallback heapsort:             OK (0.20s)
-      22.9 μs ± 1.5 μs, 5.30x
-  Sorting 10 random arrays of length 256 with few duplicates
-    C++:                           OK (0.19s)
-      44.7 μs ± 2.9 μs
-    Sequential ST Median3:         OK (0.45s)
-      54.6 μs ± 2.8 μs, 1.22x
-    Sequential IO Median3:         OK (0.20s)
-      46.9 μs ± 3.5 μs, 1.05x
-    ParStrategies ST Median3:      OK (0.22s)
-      51.3 μs ± 3.9 μs, 1.15x
-    ParStrategies IO Median3:      OK (0.23s)
-      53.7 μs ± 4.1 μs, 1.20x
-    Sequential ST Median3or5:      OK (0.21s)
-      48.1 μs ± 3.1 μs, 1.07x
-    Sequential IO Median3or5:      OK (0.21s)
-      47.8 μs ± 2.9 μs, 1.07x
-    ParStrategies ST Median3or5:   OK (0.23s)
-      54.0 μs ± 4.0 μs, 1.21x
-    ParStrategies IO Median3or5:   OK (0.24s)
-      54.3 μs ± 5.0 μs, 1.21x
-    Sequential ST AveragingMedian: OK (0.13s)
-      119  μs ±  11 μs, 2.67x
-    Threads IO Median3:            OK (0.24s)
-      56.3 μs ± 5.6 μs, 1.26x
-    Threads IO Median3or5:         OK (0.24s)
-      56.1 μs ± 4.6 μs, 1.25x
-    vector-algorithms heapsort:    OK (0.23s)
-      110  μs ± 6.6 μs, 2.47x
-    fallback heapsort:             OK (0.21s)
-      95.2 μs ± 6.5 μs, 2.13x
-  Sorting 10 random arrays of length 1,000 with few duplicates
-    C++:                           OK (0.15s)
-      274  μs ±  24 μs
-    Sequential ST Median3:         OK (0.13s)
-      471  μs ±  46 μs, 1.72x
-    Sequential IO Median3:         OK (0.23s)
-      428  μs ±  28 μs, 1.56x
-    ParStrategies ST Median3:      OK (0.19s)
-      350  μs ±  27 μs, 1.27x
-    ParStrategies IO Median3:      OK (0.24s)
-      451  μs ±  42 μs, 1.65x
-    Sequential ST Median3or5:      OK (0.18s)
-      333  μs ±  31 μs, 1.22x
-    Sequential IO Median3or5:      OK (0.12s)
-      428  μs ±  42 μs, 1.56x
-    ParStrategies ST Median3or5:   OK (0.25s)
-      460  μs ±  42 μs, 1.68x
-    ParStrategies IO Median3or5:   OK (0.19s)
-      363  μs ±  27 μs, 1.32x
-    Sequential ST AveragingMedian: OK (0.20s)
-      742  μs ±  64 μs, 2.71x
-    Threads IO Median3:            OK (0.25s)
-      469  μs ±  25 μs, 1.71x
-    Threads IO Median3or5:         OK (0.25s)
-      457  μs ±  37 μs, 1.67x
-    vector-algorithms heapsort:    OK (0.18s)
-      667  μs ±  65 μs, 2.43x
-    fallback heapsort:             OK (0.17s)
-      618  μs ±  46 μs, 2.25x
-  Sorting 10 random arrays of length 10,000 with few duplicates
-    C++:                           OK (0.29s)
-      4.50 ms ± 269 μs
-    Sequential ST Median3:         OK (0.19s)
-      6.00 ms ± 586 μs, 1.33x
-    Sequential IO Median3:         OK (0.28s)
-      4.31 ms ± 260 μs, 0.96x
-    ParStrategies ST Median3:      OK (0.18s)
-      5.73 ms ± 514 μs, 1.27x
-    ParStrategies IO Median3:      OK (0.20s)
-      6.06 ms ± 375 μs, 1.35x
-    Sequential ST Median3or5:      OK (0.14s)
-      4.35 ms ± 353 μs, 0.97x
-    Sequential IO Median3or5:      OK (0.18s)
-      5.57 ms ± 524 μs, 1.24x
-    ParStrategies ST Median3or5:   OK (0.15s)
-      4.62 ms ± 340 μs, 1.03x
-    ParStrategies IO Median3or5:   OK (0.38s)
-      5.89 ms ± 520 μs, 1.31x
-    Sequential ST AveragingMedian: OK (0.31s)
-      9.89 ms ± 437 μs, 2.20x
-    Threads IO Median3:            OK (0.60s)
-      4.65 ms ± 291 μs, 1.03x
-    Threads IO Median3or5:         OK (0.37s)
-      5.77 ms ± 183 μs, 1.28x
-    vector-algorithms heapsort:    OK (0.14s)
-      8.79 ms ± 718 μs, 1.95x
-    fallback heapsort:             OK (0.26s)
-      8.34 ms ± 568 μs, 1.85x
-  Sorting 10 random arrays of length 100,000 with few duplicates
-    C++:                           OK (0.17s)
-      55.6 ms ± 3.2 ms
-    Sequential ST Median3:         OK (0.24s)
-      78.1 ms ± 3.7 ms, 1.41x
-    Sequential IO Median3:         OK (0.21s)
-      69.3 ms ± 3.7 ms, 1.25x
-    ParStrategies ST Median3:      OK (0.18s)
-      59.5 ms ± 4.5 ms, 1.07x
-    ParStrategies IO Median3:      OK (3.66s)
-      56.6 ms ± 5.3 ms, 1.02x
-    Sequential ST Median3or5:      OK (0.16s)
-      54.0 ms ± 3.5 ms, 0.97x
-    Sequential IO Median3or5:      OK (0.21s)
-      67.3 ms ± 5.7 ms, 1.21x
-    ParStrategies ST Median3or5:   OK (0.19s)
-      62.3 ms ± 5.6 ms, 1.12x
-    ParStrategies IO Median3or5:   OK (0.45s)
-      65.3 ms ± 4.7 ms, 1.17x
-    Sequential ST AveragingMedian: OK (0.38s)
-      127  ms ± 4.5 ms, 2.28x
-    Threads IO Median3:            OK (0.34s)
-      48.3 ms ± 2.7 ms, 0.87x
-    Threads IO Median3or5:         OK (0.34s)
-      47.7 ms ± 2.4 ms, 0.86x
-    vector-algorithms heapsort:    OK (0.37s)
-      124  ms ± 8.9 ms, 2.23x
-    fallback heapsort:             OK (0.81s)
-      113  ms ± 5.4 ms, 2.04x
-  Sorting 10 random arrays of length 1,000,000 with few duplicates
-    C++:                           OK (2.05s)
-      682  ms ±  15 ms
-    Sequential ST Median3:         OK (2.18s)
-      727  ms ± 7.5 ms, 1.06x
-    Sequential IO Median3:         OK (2.44s)
-      812  ms ± 9.6 ms, 1.19x
-    ParStrategies ST Median3:      OK (1.44s)
-      204  ms ±  13 ms, 0.30x
-    ParStrategies IO Median3:      OK (0.81s)
-      268  ms ±  20 ms, 0.39x
-    Sequential ST Median3or5:      OK (2.00s)
-      664  ms ±  45 ms, 0.97x
-    Sequential IO Median3or5:      OK (1.95s)
-      645  ms ±  43 ms, 0.95x
-    ParStrategies ST Median3or5:   OK (1.49s)
-      209  ms ± 4.9 ms, 0.31x
-    ParStrategies IO Median3or5:   OK (1.46s)
-      208  ms ±  21 ms, 0.30x
-    Sequential ST AveragingMedian: OK (4.05s)
-      1.348 s ± 8.5 ms, 1.98x
-    Threads IO Median3:            OK (1.53s)
-      508  ms ± 7.1 ms, 0.74x
-    Threads IO Median3or5:         OK (1.36s)
-      452  ms ± 6.2 ms, 0.66x
-    vector-algorithms heapsort:    OK (3.94s)
-      1.309 s ±  53 ms, 1.92x
-    fallback heapsort:             OK (3.67s)
-      1.220 s ±  27 ms, 1.79x
-  Sorting 10 random arrays of length 10,000,000 with few duplicates
-    C++:                           OK (18.62s)
-      6.188 s ± 240 ms
-    Sequential ST Median3:         OK (25.26s)
-      8.422 s ±  49 ms, 1.36x
-    Sequential IO Median3:         OK (21.91s)
-      7.295 s ± 118 ms, 1.18x
-    ParStrategies ST Median3:      OK (6.01s)
-      2.010 s ± 110 ms, 0.32x
-    ParStrategies IO Median3:      OK (6.16s)
-      2.044 s ± 100 ms, 0.33x
-    Sequential ST Median3or5:      OK (21.85s)
-      7.284 s ± 4.9 ms, 1.18x
-    Sequential IO Median3or5:      OK (21.94s)
-      7.311 s ±  38 ms, 1.18x
-    ParStrategies ST Median3or5:   OK (6.05s)
-      2.010 s ±  72 ms, 0.32x
-    ParStrategies IO Median3or5:   OK (6.27s)
-      2.090 s ± 2.8 ms, 0.34x
-    Sequential ST AveragingMedian: OK (81.58s)
-      27.074 s ± 1.60 s, 4.38x
-    Threads IO Median3:            OK (15.86s)
-      5.283 s ±  29 ms, 0.85x
-    Threads IO Median3or5:         OK (12.85s)
-      4.283 s ± 7.3 ms, 0.69x
-    vector-algorithms heapsort:    OK (71.14s)
-      23.711 s ±  33 ms, 3.83x
-    fallback heapsort:             OK (96.39s)
-      32.157 s ± 355 ms, 5.20x
-  Sorting 10 random arrays of length 16 with many duplicates
-    C++:                           OK (0.37s)
-      673  ns ±  36 ns
-    Sequential ST Median3:         OK (0.17s)
-      576  ns ±  53 ns, 0.86x
-    Sequential IO Median3:         OK (0.16s)
-      560  ns ±  49 ns, 0.83x
-    ParStrategies ST Median3:      OK (0.17s)
-      576  ns ±  49 ns, 0.86x
-    ParStrategies IO Median3:      OK (0.17s)
-      575  ns ±  42 ns, 0.85x
-    Sequential ST Median3or5:      OK (0.17s)
-      575  ns ±  57 ns, 0.85x
-    Sequential IO Median3or5:      OK (0.17s)
-      568  ns ±  47 ns, 0.84x
-    ParStrategies ST Median3or5:   OK (0.33s)
-      596  ns ±  50 ns, 0.89x
-    ParStrategies IO Median3or5:   OK (0.33s)
-      593  ns ±  30 ns, 0.88x
-    Sequential ST AveragingMedian: OK (0.32s)
-      571  ns ±  26 ns, 0.85x
-    Threads IO Median3:            OK (0.24s)
-      1.71 μs ±  85 ns, 2.54x
-    Threads IO Median3or5:         OK (0.24s)
-      1.71 μs ± 118 ns, 2.53x
-    vector-algorithms heapsort:    OK (0.20s)
-      1.39 μs ±  90 ns, 2.06x
-    fallback heapsort:             OK (0.17s)
-      1.17 μs ±  83 ns, 1.73x
-  Sorting 10 random arrays of length 17 with many duplicates
-    C++:                           OK (0.22s)
-      763  ns ±  56 ns
-    Sequential ST Median3:         OK (0.41s)
-      752  ns ±  22 ns, 0.99x
-    Sequential IO Median3:         OK (0.21s)
-      744  ns ±  64 ns, 0.98x
-    ParStrategies ST Median3:      OK (0.23s)
-      798  ns ±  44 ns, 1.05x
-    ParStrategies IO Median3:      OK (0.22s)
-      782  ns ±  46 ns, 1.03x
-    Sequential ST Median3or5:      OK (0.21s)
-      738  ns ±  44 ns, 0.97x
-    Sequential IO Median3or5:      OK (0.21s)
-      749  ns ±  52 ns, 0.98x
-    ParStrategies ST Median3or5:   OK (0.22s)
-      786  ns ±  42 ns, 1.03x
-    ParStrategies IO Median3or5:   OK (0.44s)
-      796  ns ±  29 ns, 1.04x
-    Sequential ST AveragingMedian: OK (0.30s)
-      2.16 μs ±  88 ns, 2.83x
-    Threads IO Median3:            OK (0.14s)
-      1.92 μs ± 173 ns, 2.51x
-    Threads IO Median3or5:         OK (0.27s)
-      1.98 μs ± 116 ns, 2.59x
-    vector-algorithms heapsort:    OK (0.22s)
-      1.52 μs ±  97 ns, 1.99x
-    fallback heapsort:             OK (0.18s)
-      1.26 μs ±  85 ns, 1.65x
-  Sorting 10 random arrays of length 100 with many duplicates
-    C++:                           OK (0.19s)
-      5.12 μs ± 401 ns
-    Sequential ST Median3:         OK (0.21s)
-      5.85 μs ± 409 ns, 1.14x
-    Sequential IO Median3:         OK (0.21s)
-      5.85 μs ± 527 ns, 1.14x
-    ParStrategies ST Median3:      OK (0.22s)
-      6.26 μs ± 395 ns, 1.22x
-    ParStrategies IO Median3:      OK (0.28s)
-      7.89 μs ± 461 ns, 1.54x
-    Sequential ST Median3or5:      OK (0.21s)
-      5.71 μs ± 458 ns, 1.11x
-    Sequential IO Median3or5:      OK (0.20s)
-      5.70 μs ± 404 ns, 1.11x
-    ParStrategies ST Median3or5:   OK (0.23s)
-      6.45 μs ± 381 ns, 1.26x
-    ParStrategies IO Median3or5:   OK (0.23s)
-      6.47 μs ± 499 ns, 1.26x
-    Sequential ST AveragingMedian: OK (0.13s)
-      29.0 μs ± 2.6 μs, 5.65x
-    Threads IO Median3:            OK (0.14s)
-      7.34 μs ± 671 ns, 1.43x
-    Threads IO Median3or5:         OK (0.26s)
-      7.48 μs ± 455 ns, 1.46x
-    vector-algorithms heapsort:    OK (0.17s)
-      18.6 μs ± 1.3 μs, 3.63x
-    fallback heapsort:             OK (0.20s)
-      23.2 μs ± 2.2 μs, 4.52x
-  Sorting 10 random arrays of length 256 with many duplicates
-    C++:                           OK (0.20s)
-      46.3 μs ± 3.2 μs
-    Sequential ST Median3:         OK (0.25s)
-      55.6 μs ± 5.0 μs, 1.20x
-    Sequential IO Median3:         OK (0.42s)
-      49.5 μs ± 3.0 μs, 1.07x
-    ParStrategies ST Median3:      OK (0.23s)
-      53.0 μs ± 2.7 μs, 1.14x
-    ParStrategies IO Median3:      OK (0.13s)
-      55.7 μs ± 5.3 μs, 1.20x
-    Sequential ST Median3or5:      OK (0.22s)
-      49.2 μs ± 2.9 μs, 1.06x
-    Sequential IO Median3or5:      OK (0.42s)
-      49.3 μs ± 2.0 μs, 1.06x
-    ParStrategies ST Median3or5:   OK (0.24s)
-      55.2 μs ± 4.2 μs, 1.19x
-    ParStrategies IO Median3or5:   OK (0.47s)
-      55.0 μs ± 1.5 μs, 1.19x
-    Sequential ST AveragingMedian: OK (0.13s)
-      121  μs ±  11 μs, 2.60x
-    Threads IO Median3:            OK (0.13s)
-      56.1 μs ± 5.4 μs, 1.21x
-    Threads IO Median3or5:         OK (0.25s)
-      56.6 μs ± 3.4 μs, 1.22x
-    vector-algorithms heapsort:    OK (0.22s)
-      102  μs ± 5.2 μs, 2.21x
-    fallback heapsort:             OK (0.21s)
-      94.9 μs ± 5.4 μs, 2.05x
-  Sorting 10 random arrays of length 1,000 with many duplicates
-    C++:                           OK (0.30s)
-      279  μs ±  14 μs
-    Sequential ST Median3:         OK (0.20s)
-      367  μs ±  22 μs, 1.31x
-    Sequential IO Median3:         OK (0.18s)
-      335  μs ±  33 μs, 1.20x
-    ParStrategies ST Median3:      OK (0.19s)
-      344  μs ±  22 μs, 1.23x
-    ParStrategies IO Median3:      OK (0.19s)
-      359  μs ±  24 μs, 1.28x
-    Sequential ST Median3or5:      OK (0.18s)
-      336  μs ±  25 μs, 1.20x
-    Sequential IO Median3or5:      OK (0.18s)
-      336  μs ±  21 μs, 1.20x
-    ParStrategies ST Median3or5:   OK (0.38s)
-      358  μs ±  14 μs, 1.28x
-    ParStrategies IO Median3or5:   OK (0.19s)
-      363  μs ±  26 μs, 1.30x
-    Sequential ST AveragingMedian: OK (0.16s)
-      583  μs ±  43 μs, 2.09x
-    Threads IO Median3:            OK (0.19s)
-      357  μs ±  23 μs, 1.28x
-    Threads IO Median3or5:         OK (0.19s)
-      358  μs ±  28 μs, 1.28x
-    vector-algorithms heapsort:    OK (0.15s)
-      536  μs ±  42 μs, 1.92x
-    fallback heapsort:             OK (0.13s)
-      494  μs ±  46 μs, 1.77x
-  Sorting 10 random arrays of length 10,000 with many duplicates
-    C++:                           OK (0.21s)
-      3.25 ms ± 184 μs
-    Sequential ST Median3:         OK (0.14s)
-      4.23 ms ± 347 μs, 1.30x
-    Sequential IO Median3:         OK (0.24s)
-      3.65 ms ± 329 μs, 1.12x
-    ParStrategies ST Median3:      OK (0.12s)
-      3.76 ms ± 362 μs, 1.15x
-    ParStrategies IO Median3:      OK (0.13s)
-      3.98 ms ± 360 μs, 1.22x
-    Sequential ST Median3or5:      OK (0.24s)
-      3.72 ms ± 254 μs, 1.14x
-    Sequential IO Median3or5:      OK (0.12s)
-      3.71 ms ± 344 μs, 1.14x
-    ParStrategies ST Median3or5:   OK (0.13s)
-      3.96 ms ± 344 μs, 1.22x
-    ParStrategies IO Median3or5:   OK (0.27s)
-      4.10 ms ± 315 μs, 1.26x
-    Sequential ST AveragingMedian: OK (0.12s)
-      7.81 ms ± 724 μs, 2.40x
-    Threads IO Median3:            OK (0.13s)
-      3.92 ms ± 338 μs, 1.20x
-    Threads IO Median3or5:         OK (0.13s)
-      4.05 ms ± 347 μs, 1.24x
-    vector-algorithms heapsort:    OK (0.12s)
-      7.85 ms ± 674 μs, 2.41x
-    fallback heapsort:             OK (0.21s)
-      6.58 ms ± 434 μs, 2.02x
-  Sorting 10 random arrays of length 100,000 with many duplicates
-    C++:                           OK (0.24s)
-      33.0 ms ± 1.5 ms
-    Sequential ST Median3:         OK (0.12s)
-      38.8 ms ± 2.7 ms, 1.18x
-    Sequential IO Median3:         OK (0.10s)
-      33.1 ms ± 2.9 ms, 1.00x
-    ParStrategies ST Median3:      OK (0.23s)
-      32.9 ms ± 1.9 ms, 1.00x
-    ParStrategies IO Median3:      OK (0.13s)
-      41.5 ms ± 3.3 ms, 1.26x
-    Sequential ST Median3or5:      OK (0.23s)
-      32.8 ms ± 2.3 ms, 1.00x
-    Sequential IO Median3or5:      OK (0.10s)
-      33.4 ms ± 3.1 ms, 1.01x
-    ParStrategies ST Median3or5:   OK (0.23s)
-      32.9 ms ± 1.5 ms, 1.00x
-    ParStrategies IO Median3or5:   OK (0.23s)
-      33.0 ms ± 2.0 ms, 1.00x
-    Sequential ST AveragingMedian: OK (0.31s)
-      102  ms ± 7.6 ms, 3.08x
-    Threads IO Median3:            OK (0.48s)
-      32.2 ms ± 982 μs, 0.98x
-    Threads IO Median3or5:         OK (0.48s)
-      31.3 ms ± 1.6 ms, 0.95x
-    vector-algorithms heapsort:    OK (0.31s)
-      103  ms ± 5.2 ms, 3.12x
-    fallback heapsort:             OK (0.27s)
-      89.0 ms ± 4.7 ms, 2.70x
-  Sorting 10 random arrays of length 1,000,000 with many duplicates
-    C++:                           OK (1.03s)
-      339  ms ±  32 ms
-    Sequential ST Median3:         OK (1.16s)
-      387  ms ± 5.5 ms, 1.14x
-    Sequential IO Median3:         OK (0.99s)
-      329  ms ± 5.0 ms, 0.97x
-    ParStrategies ST Median3:      OK (0.59s)
-      198  ms ±  15 ms, 0.58x
-    ParStrategies IO Median3:      OK (0.60s)
-      198  ms ± 5.8 ms, 0.58x
-    Sequential ST Median3or5:      OK (0.99s)
-      329  ms ± 8.3 ms, 0.97x
-    Sequential IO Median3or5:      OK (1.00s)
-      331  ms ± 3.6 ms, 0.98x
-    ParStrategies ST Median3or5:   OK (1.39s)
-      199  ms ± 5.0 ms, 0.59x
-    ParStrategies IO Median3or5:   OK (0.60s)
-      199  ms ± 3.0 ms, 0.59x
-    Sequential ST AveragingMedian: OK (3.82s)
-      1.273 s ±  25 ms, 3.76x
-    Threads IO Median3:            OK (0.88s)
-      294  ms ±  27 ms, 0.87x
-    Threads IO Median3or5:         OK (1.92s)
-      274  ms ±  15 ms, 0.81x
-    vector-algorithms heapsort:    OK (11.77s)
-      1.664 s ± 6.3 ms, 4.91x
-    fallback heapsort:             OK (4.08s)
-      1.357 s ±  23 ms, 4.00x
-  Sorting 10 random arrays of length 10,000,000 with many duplicates
-    C++:                           OK (16.77s)
-      5.595 s ±  88 ms
-    Sequential ST Median3:         OK (15.18s)
-      5.059 s ± 9.0 ms, 0.90x
-    Sequential IO Median3:         OK (16.64s)
-      5.541 s ±  75 ms, 0.99x
-    ParStrategies ST Median3:      OK (4.50s)
-      1.499 s ± 4.0 ms, 0.27x
-    ParStrategies IO Median3:      OK (4.60s)
-      1.534 s ±  14 ms, 0.27x
-    Sequential ST Median3or5:      OK (12.89s)
-      4.287 s ± 114 ms, 0.77x
-    Sequential IO Median3or5:      OK (12.81s)
-      4.267 s ±  46 ms, 0.76x
-    ParStrategies ST Median3or5:   OK (4.19s)
-      1.399 s ±  70 ms, 0.25x
-    ParStrategies IO Median3or5:   OK (4.28s)
-      1.430 s ±  43 ms, 0.26x
-    Sequential ST AveragingMedian: OK (72.72s)
-      24.214 s ± 367 ms, 4.33x
-    Threads IO Median3:            OK (10.38s)
-      3.450 s ± 125 ms, 0.62x
-    Threads IO Median3or5:         OK (8.70s)
-      2.886 s ± 191 ms, 0.52x
-    vector-algorithms heapsort:    OK (68.36s)
-      22.767 s ± 265 ms, 4.07x
-    fallback heapsort:             OK (65.38s)
-      21.792 s ± 5.9 ms, 3.90x
+[![build](https://github.com/sergv/vector-quicksort/actions/workflows/haskell-ci.yaml/badge.svg)](https://github.com/sergv/vector-quicksort/actions/workflows/haskell-ci.yaml)
+
+# Synopsis
+
+This package features reasonable sort function that is a good default
+for sorting mutable vectors using well-known quicksort algorithm.
+During development one of the goals was making it perform on par with
+C++’s `std::sort`, i.e. fast.
+
+While providing reasonably good single-threaded default sorting, the
+algorithm in this package is also parallelisable and can provide ways
+to run on multiple cores using either threads or sparks, so bigger
+vectors can be sorted even faster.
+
+# Algorithm details
+
+Technically it’s an [introsort](https://en.wikipedia.org/wiki/Introsort), i.e.
+pathological `O(N^2)` quicksort case will get delegated to heapsort when recursion
+gets too deep, thus making algorithm reliably `O(N * log(N))`.
+
+# Benchmarks
+
+The benchmark is to generate some random arrays of different length
+and sort them. For each array size N two versions are generated: one
+from range [0, N] (`with few duplicates` below),
+another from [0, N / 1000] (`with many duplicates` below) to
+introduce duplicates. And there’s one reasonably big array (around
+40,000 elements) with lots of duplicates that I managed to come by in
+the real world under `Sorting fuzzy matching scores vector` that comes
+from `test.txt` file in this repository. with
+
+Test setup: AMD Ryzen 9 7950X3D 16-Core Processor, Intel i5-9600K CPU @ 3.70GHz, GHC 9.10.1.
+
+The default sort exported in `Data.Vector.Algorithms.Quicksort` is
+`Sequential` and `Median3or5`.
+
+NB `vector-algorithms` benchmark is not included below (but it’s still
+there and will run for you locally) because it doesn’t specialise by
+default and in that case takes around 100 times more time than C++.
+That’s not representative, when this library doesn’t specialise it
+suffers similar slowdown (please refer to the docs on how to alleviate
+that).
+
+```
+$ cabal run bench -- -j1 --time-mode wall --timeout 300 -p '!/vector-algorithms heapsort/'
+All
+  Sorting fuzzy matching scores vector
+    C++:                              OK
+      228  μs ±  22 μs
+    Sequential ST Median3:            OK
+      104  μs ± 6.1 μs, 0.45x
+    Sequential IO Median3:            OK
+      104  μs ± 6.0 μs, 0.46x
+    ParStrategies ST Median3:         OK
+      102  μs ± 5.6 μs, 0.45x
+    ParStrategies IO Median3:         OK
+      104  μs ± 5.6 μs, 0.46x
+    Sequential ST Median3or5:         OK
+      94.6 μs ± 5.7 μs, 0.41x
+    Sequential IO Median3or5:         OK
+      96.7 μs ± 5.5 μs, 0.42x
+    ParStrategies ST Median3or5:      OK
+      95.0 μs ± 5.6 μs, 0.42x
+    ParStrategies IO Median3or5:      OK
+      95.9 μs ± 5.9 μs, 0.42x
+    Threads IO Median3:               OK
+      104  μs ± 8.2 μs, 0.45x
+    Threads IO Median3or5:            OK
+      98.0 μs ± 5.5 μs, 0.43x
+    fallback heapsort:                OK
+      1.53 ms ±  97 μs, 6.70x
+  Int64
+    Sorting 10 random arrays of length 16 with few duplicates
+      C++:                            OK
+        673  ns ±  65 ns
+      Sequential ST Median3:          OK
+        442  ns ±  21 ns, 0.66x
+      Sequential IO Median3:          OK
+        432  ns ±  37 ns, 0.64x
+      ParStrategies ST Median3:       OK
+        437  ns ±  38 ns, 0.65x
+      ParStrategies IO Median3:       OK
+        450  ns ±  21 ns, 0.67x
+      Sequential ST Median3or5:       OK
+        439  ns ± 7.4 ns, 0.65x
+      Sequential IO Median3or5:       OK
+        439  ns ±  26 ns, 0.65x
+      ParStrategies ST Median3or5:    OK
+        460  ns ±  43 ns, 0.68x
+      ParStrategies IO Median3or5:    OK
+        454  ns ±  35 ns, 0.67x
+      Threads IO Median3:             OK
+        1.23 μs ± 103 ns, 1.83x
+      Threads IO Median3or5:          OK
+        1.22 μs ±  95 ns, 1.81x
+      fallback heapsort:              OK
+        911  ns ±  66 ns, 1.35x
+    Sorting 10 random arrays of length 17 with few duplicates
+      C++:                            OK
+        688  ns ±  66 ns
+      Sequential ST Median3:          OK
+        561  ns ±  45 ns, 0.82x
+      Sequential IO Median3:          OK
+        566  ns ±  55 ns, 0.82x
+      ParStrategies ST Median3:       OK
+        690  ns ±  55 ns, 1.00x
+      ParStrategies IO Median3:       OK
+        679  ns ±  62 ns, 0.99x
+      Sequential ST Median3or5:       OK
+        588  ns ±  38 ns, 0.85x
+      Sequential IO Median3or5:       OK
+        590  ns ±  48 ns, 0.86x
+      ParStrategies ST Median3or5:    OK
+        646  ns ±  64 ns, 0.94x
+      ParStrategies IO Median3or5:    OK
+        645  ns ±  24 ns, 0.94x
+      Threads IO Median3:             OK
+        1.45 μs ± 133 ns, 2.11x
+      Threads IO Median3or5:          OK
+        1.40 μs ± 129 ns, 2.03x
+      fallback heapsort:              OK
+        989  ns ±  59 ns, 1.44x
+    Sorting 10 random arrays of length 100 with few duplicates
+      C++:                            OK
+        4.48 μs ± 368 ns
+      Sequential ST Median3:          OK
+        5.05 μs ± 444 ns, 1.13x
+      Sequential IO Median3:          OK
+        5.02 μs ± 367 ns, 1.12x
+      ParStrategies ST Median3:       OK
+        5.49 μs ± 464 ns, 1.23x
+      ParStrategies IO Median3:       OK
+        5.72 μs ± 171 ns, 1.28x
+      Sequential ST Median3or5:       OK
+        4.86 μs ± 485 ns, 1.08x
+      Sequential IO Median3or5:       OK
+        5.20 μs ± 350 ns, 1.16x
+      ParStrategies ST Median3or5:    OK
+        5.62 μs ± 442 ns, 1.25x
+      ParStrategies IO Median3or5:    OK
+        5.64 μs ± 473 ns, 1.26x
+      Threads IO Median3:             OK
+        6.15 μs ± 413 ns, 1.37x
+      Threads IO Median3or5:          OK
+        6.27 μs ± 217 ns, 1.40x
+      fallback heapsort:              OK
+        8.02 μs ± 409 ns, 1.79x
+    Sorting 10 random arrays of length 256 with few duplicates
+      C++:                            OK
+        14.9 μs ± 1.2 μs
+      Sequential ST Median3:          OK
+        16.5 μs ± 1.4 μs, 1.11x
+      Sequential IO Median3:          OK
+        16.5 μs ± 1.6 μs, 1.11x
+      ParStrategies ST Median3:       OK
+        17.4 μs ± 1.4 μs, 1.17x
+      ParStrategies IO Median3:       OK
+        18.1 μs ± 1.7 μs, 1.22x
+      Sequential ST Median3or5:       OK
+        15.6 μs ± 1.4 μs, 1.05x
+      Sequential IO Median3or5:       OK
+        16.4 μs ± 1.5 μs, 1.10x
+      ParStrategies ST Median3or5:    OK
+        17.4 μs ± 1.5 μs, 1.17x
+      ParStrategies IO Median3or5:    OK
+        18.2 μs ± 1.6 μs, 1.22x
+      Threads IO Median3:             OK
+        17.6 μs ± 1.7 μs, 1.18x
+      Threads IO Median3or5:          OK
+        18.1 μs ± 1.4 μs, 1.21x
+      fallback heapsort:              OK
+        24.8 μs ± 1.5 μs, 1.67x
+    Sorting 10 random arrays of length 1,000 with few duplicates
+      C++:                            OK
+        109  μs ± 5.4 μs
+      Sequential ST Median3:          OK
+        89.8 μs ± 2.7 μs, 0.83x
+      Sequential IO Median3:          OK
+        131  μs ± 7.5 μs, 1.21x
+      ParStrategies ST Median3:       OK
+        125  μs ±  11 μs, 1.15x
+      ParStrategies IO Median3:       OK
+        163  μs ±  15 μs, 1.50x
+      Sequential ST Median3or5:       OK
+        120  μs ± 2.6 μs, 1.10x
+      Sequential IO Median3or5:       OK
+        118  μs ± 4.2 μs, 1.09x
+      ParStrategies ST Median3or5:    OK
+        132  μs ± 6.4 μs, 1.22x
+      ParStrategies IO Median3or5:    OK
+        181  μs ±  18 μs, 1.67x
+      Threads IO Median3:             OK
+        126  μs ± 7.8 μs, 1.16x
+      Threads IO Median3or5:          OK
+        133  μs ±  11 μs, 1.23x
+      fallback heapsort:              OK
+        360  μs ±  26 μs, 3.31x
+    Sorting 10 random arrays of length 10,000 with few duplicates
+      C++:                            OK
+        3.02 ms ± 170 μs
+      Sequential ST Median3:          OK
+        3.69 ms ± 337 μs, 1.22x
+      Sequential IO Median3:          OK
+        3.64 ms ± 356 μs, 1.21x
+      ParStrategies ST Median3:       OK
+        3.73 ms ± 336 μs, 1.23x
+      ParStrategies IO Median3:       OK
+        3.87 ms ± 354 μs, 1.28x
+      Sequential ST Median3or5:       OK
+        3.63 ms ± 350 μs, 1.20x
+      Sequential IO Median3or5:       OK
+        3.64 ms ± 350 μs, 1.20x
+      ParStrategies ST Median3or5:    OK
+        3.76 ms ± 336 μs, 1.24x
+      ParStrategies IO Median3or5:    OK
+        3.80 ms ± 367 μs, 1.26x
+      Threads IO Median3:             OK
+        3.67 ms ± 339 μs, 1.21x
+      Threads IO Median3or5:          OK
+        3.68 ms ± 358 μs, 1.22x
+      fallback heapsort:              OK
+        5.09 ms ± 391 μs, 1.68x
+    Sorting 10 random arrays of length 100,000 with few duplicates
+      C++:                            OK
+        36.8 ms ± 1.6 ms
+      Sequential ST Median3:          OK
+        45.3 ms ± 3.4 ms, 1.23x
+      Sequential IO Median3:          OK
+        44.6 ms ± 2.8 ms, 1.21x
+      ParStrategies ST Median3:       OK
+        39.3 ms ± 2.6 ms, 1.07x
+      ParStrategies IO Median3:       OK
+        42.4 ms ± 3.8 ms, 1.15x
+      Sequential ST Median3or5:       OK
+        44.3 ms ± 2.7 ms, 1.20x
+      Sequential IO Median3or5:       OK
+        44.4 ms ± 2.8 ms, 1.21x
+      ParStrategies ST Median3or5:    OK
+        42.5 ms ± 3.5 ms, 1.15x
+      ParStrategies IO Median3or5:    OK
+        42.0 ms ± 2.7 ms, 1.14x
+      Threads IO Median3:             OK
+        27.6 ms ± 1.8 ms, 0.75x
+      Threads IO Median3or5:          OK
+        27.1 ms ± 2.1 ms, 0.74x
+      fallback heapsort:              OK
+        65.0 ms ± 2.9 ms, 1.77x
+    Sorting 10 random arrays of length 1,000,000 with few duplicates
+      C++:                            OK
+        449  ms ±  35 ms
+      Sequential ST Median3:          OK
+        544  ms ±  11 ms, 1.21x
+      Sequential IO Median3:          OK
+        540  ms ±  11 ms, 1.20x
+      ParStrategies ST Median3:       OK
+        191  ms ±  15 ms, 0.42x
+      ParStrategies IO Median3:       OK
+        193  ms ±  10 ms, 0.43x
+      Sequential ST Median3or5:       OK
+        535  ms ±  11 ms, 1.19x
+      Sequential IO Median3or5:       OK
+        537  ms ± 8.4 ms, 1.20x
+      ParStrategies ST Median3or5:    OK
+        190  ms ±  12 ms, 0.42x
+      ParStrategies IO Median3or5:    OK
+        197  ms ±  20 ms, 0.44x
+      Threads IO Median3:             OK
+        249  ms ±  16 ms, 0.55x
+      Threads IO Median3or5:          OK
+        245  ms ±  13 ms, 0.55x
+      fallback heapsort:              OK
+        851  ms ±  30 ms, 1.90x
+    Sorting 10 random arrays of length 16 with many duplicates
+      C++:                            OK
+        690  ns ±  22 ns
+      Sequential ST Median3:          OK
+        452  ns ±  33 ns, 0.66x
+      Sequential IO Median3:          OK
+        446  ns ±  43 ns, 0.65x
+      ParStrategies ST Median3:       OK
+        450  ns ±  42 ns, 0.65x
+      ParStrategies IO Median3:       OK
+        457  ns ±  29 ns, 0.66x
+      Sequential ST Median3or5:       OK
+        442  ns ±  42 ns, 0.64x
+      Sequential IO Median3or5:       OK
+        443  ns ±  42 ns, 0.64x
+      ParStrategies ST Median3or5:    OK
+        458  ns ±  45 ns, 0.66x
+      ParStrategies IO Median3or5:    OK
+        459  ns ±  43 ns, 0.67x
+      Threads IO Median3:             OK
+        1.23 μs ± 109 ns, 1.79x
+      Threads IO Median3or5:          OK
+        1.22 μs ±  92 ns, 1.77x
+      fallback heapsort:              OK
+        904  ns ±  52 ns, 1.31x
+    Sorting 10 random arrays of length 17 with many duplicates
+      C++:                            OK
+        689  ns ±  57 ns
+      Sequential ST Median3:          OK
+        593  ns ±  28 ns, 0.86x
+      Sequential IO Median3:          OK
+        603  ns ±  41 ns, 0.87x
+      ParStrategies ST Median3:       OK
+        656  ns ±  48 ns, 0.95x
+      ParStrategies IO Median3:       OK
+        724  ns ±  59 ns, 1.05x
+      Sequential ST Median3or5:       OK
+        599  ns ±  53 ns, 0.87x
+      Sequential IO Median3or5:       OK
+        606  ns ±  51 ns, 0.88x
+      ParStrategies ST Median3or5:    OK
+        657  ns ±  46 ns, 0.95x
+      ParStrategies IO Median3or5:    OK
+        664  ns ±  44 ns, 0.96x
+      Threads IO Median3:             OK
+        1.40 μs ±  92 ns, 2.02x
+      Threads IO Median3or5:          OK
+        1.39 μs ±  85 ns, 2.02x
+      fallback heapsort:              OK
+        985  ns ±  51 ns, 1.43x
+    Sorting 10 random arrays of length 100 with many duplicates
+      C++:                            OK
+        4.31 μs ± 357 ns
+      Sequential ST Median3:          OK
+        4.93 μs ± 361 ns, 1.15x
+      Sequential IO Median3:          OK
+        4.94 μs ± 390 ns, 1.15x
+      ParStrategies ST Median3:       OK
+        5.27 μs ± 416 ns, 1.22x
+      ParStrategies IO Median3:       OK
+        5.47 μs ± 363 ns, 1.27x
+      Sequential ST Median3or5:       OK
+        4.61 μs ± 436 ns, 1.07x
+      Sequential IO Median3or5:       OK
+        4.93 μs ± 419 ns, 1.15x
+      ParStrategies ST Median3or5:    OK
+        5.33 μs ± 329 ns, 1.24x
+      ParStrategies IO Median3or5:    OK
+        5.45 μs ± 352 ns, 1.27x
+      Threads IO Median3:             OK
+        5.79 μs ± 360 ns, 1.34x
+      Threads IO Median3or5:          OK
+        5.87 μs ± 384 ns, 1.36x
+      fallback heapsort:              OK
+        8.00 μs ± 331 ns, 1.86x
+    Sorting 10 random arrays of length 256 with many duplicates
+      C++:                            OK
+        15.1 μs ± 1.2 μs
+      Sequential ST Median3:          OK
+        16.9 μs ± 1.7 μs, 1.12x
+      Sequential IO Median3:          OK
+        17.0 μs ± 1.5 μs, 1.13x
+      ParStrategies ST Median3:       OK
+        17.9 μs ± 795 ns, 1.19x
+      ParStrategies IO Median3:       OK
+        18.4 μs ± 1.6 μs, 1.22x
+      Sequential ST Median3or5:       OK
+        15.6 μs ± 1.4 μs, 1.04x
+      Sequential IO Median3or5:       OK
+        16.7 μs ± 1.4 μs, 1.11x
+      ParStrategies ST Median3or5:    OK
+        17.8 μs ± 1.7 μs, 1.18x
+      ParStrategies IO Median3or5:    OK
+        18.6 μs ± 1.6 μs, 1.24x
+      Threads IO Median3:             OK
+        17.9 μs ± 670 ns, 1.19x
+      Threads IO Median3or5:          OK
+        18.7 μs ± 1.6 μs, 1.24x
+      fallback heapsort:              OK
+        24.9 μs ± 1.3 μs, 1.65x
+    Sorting 10 random arrays of length 1,000 with many duplicates
+      C++:                            OK
+        60.7 μs ± 1.3 μs
+      Sequential ST Median3:          OK
+        162  μs ± 3.1 μs, 2.67x
+      Sequential IO Median3:          OK
+        130  μs ± 4.7 μs, 2.13x
+      ParStrategies ST Median3:       OK
+        179  μs ± 7.1 μs, 2.94x
+      ParStrategies IO Median3:       OK
+        159  μs ± 3.8 μs, 2.62x
+      Sequential ST Median3or5:       OK
+        126  μs ±  12 μs, 2.07x
+      Sequential IO Median3or5:       OK
+        139  μs ± 7.3 μs, 2.29x
+      ParStrategies ST Median3or5:    OK
+        127  μs ± 8.9 μs, 2.09x
+      ParStrategies IO Median3or5:    OK
+        151  μs ± 6.1 μs, 2.49x
+      Threads IO Median3:             OK
+        86.0 μs ± 5.6 μs, 1.42x
+      Threads IO Median3or5:          OK
+        83.4 μs ± 7.7 μs, 1.37x
+      fallback heapsort:              OK
+        352  μs ±  25 μs, 5.80x
+    Sorting 10 random arrays of length 10,000 with many duplicates
+      C++:                            OK
+        2.69 ms ± 173 μs
+      Sequential ST Median3:          OK
+        3.09 ms ± 276 μs, 1.15x
+      Sequential IO Median3:          OK
+        2.98 ms ± 190 μs, 1.11x
+      ParStrategies ST Median3:       OK
+        3.06 ms ± 212 μs, 1.14x
+      ParStrategies IO Median3:       OK
+        3.19 ms ± 186 μs, 1.19x
+      Sequential ST Median3or5:       OK
+        3.02 ms ± 191 μs, 1.12x
+      Sequential IO Median3or5:       OK
+        3.01 ms ± 186 μs, 1.12x
+      ParStrategies ST Median3or5:    OK
+        3.11 ms ± 187 μs, 1.16x
+      ParStrategies IO Median3or5:    OK
+        3.14 ms ± 192 μs, 1.17x
+      Threads IO Median3:             OK
+        3.05 ms ± 171 μs, 1.14x
+      Threads IO Median3or5:          OK
+        3.08 ms ± 270 μs, 1.15x
+      fallback heapsort:              OK
+        4.97 ms ± 337 μs, 1.85x
+    Sorting 10 random arrays of length 100,000 with many duplicates
+      C++:                            OK
+        26.6 ms ± 1.4 ms
+      Sequential ST Median3:          OK
+        27.7 ms ± 1.3 ms, 1.04x
+      Sequential IO Median3:          OK
+        27.2 ms ± 1.4 ms, 1.02x
+      ParStrategies ST Median3:       OK
+        26.0 ms ± 1.1 ms, 0.98x
+      ParStrategies IO Median3:       OK
+        27.2 ms ± 2.5 ms, 1.02x
+      Sequential ST Median3or5:       OK
+        27.1 ms ± 1.6 ms, 1.02x
+      Sequential IO Median3or5:       OK
+        27.6 ms ± 1.9 ms, 1.04x
+      ParStrategies ST Median3or5:    OK
+        26.7 ms ± 2.1 ms, 1.00x
+      ParStrategies IO Median3or5:    OK
+        26.2 ms ± 2.1 ms, 0.98x
+      Threads IO Median3:             OK
+        18.2 ms ± 900 μs, 0.68x
+      Threads IO Median3or5:          OK
+        17.9 ms ± 1.3 ms, 0.67x
+      fallback heapsort:              OK
+        64.5 ms ± 2.8 ms, 2.42x
+    Sorting 10 random arrays of length 1,000,000 with many duplicates
+      C++:                            OK
+        281  ms ±  25 ms
+      Sequential ST Median3:          OK
+        274  ms ± 2.9 ms, 0.98x
+      Sequential IO Median3:          OK
+        269  ms ± 5.3 ms, 0.96x
+      ParStrategies ST Median3:       OK
+        189  ms ± 8.9 ms, 0.67x
+      ParStrategies IO Median3:       OK
+        194  ms ±  11 ms, 0.69x
+      Sequential ST Median3or5:       OK
+        263  ms ± 3.2 ms, 0.94x
+      Sequential IO Median3or5:       OK
+        266  ms ± 2.9 ms, 0.95x
+      ParStrategies ST Median3or5:    OK
+        188  ms ±  14 ms, 0.67x
+      ParStrategies IO Median3or5:    OK
+        194  ms ±  11 ms, 0.69x
+      Threads IO Median3:             OK
+        151  ms ± 7.5 ms, 0.54x
+      Threads IO Median3or5:          OK
+        147  ms ±  12 ms, 0.53x
+      fallback heapsort:              OK
+        784  ms ±  11 ms, 2.79x
+  (Double, Double, Int64)
+    Sorting 10 random arrays of length 16 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        841  ns ±  57 ns
+      C++ three vectors:              OK
+        2.35 μs ± 176 ns
+      Sequential ST Median3 Unbox:    OK
+        1.34 μs ± 128 ns
+      Sequential ST Median3or5 Unbox: OK
+        1.36 μs ±  91 ns
+      Sequential ST Median3 Prim:     OK
+        1.23 μs ± 103 ns
+      Sequential ST Median3or5 Prim:  OK
+        1.22 μs ± 112 ns
+    Sorting 10 random arrays of length 17 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        901  ns ±  26 ns
+      C++ three vectors:              OK
+        2.60 μs ± 207 ns
+      Sequential ST Median3 Unbox:    OK
+        1.59 μs ± 126 ns
+      Sequential ST Median3or5 Unbox: OK
+        1.70 μs ± 167 ns
+      Sequential ST Median3 Prim:     OK
+        1.44 μs ± 103 ns
+      Sequential ST Median3or5 Prim:  OK
+        1.46 μs ± 113 ns
+    Sorting 10 random arrays of length 100 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        7.72 μs ± 581 ns
+      C++ three vectors:              OK
+        14.5 μs ± 1.1 μs
+      Sequential ST Median3 Unbox:    OK
+        10.4 μs ± 959 ns
+      Sequential ST Median3or5 Unbox: OK
+        10.2 μs ± 745 ns
+      Sequential ST Median3 Prim:     OK
+        12.3 μs ± 809 ns
+      Sequential ST Median3or5 Prim:  OK
+        12.5 μs ± 795 ns
+    Sorting 10 random arrays of length 256 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        14.6 μs ± 682 ns
+      C++ three vectors:              OK
+        42.7 μs ± 4.2 μs
+      Sequential ST Median3 Unbox:    OK
+        34.3 μs ± 1.7 μs
+      Sequential ST Median3or5 Unbox: OK
+        33.5 μs ± 1.4 μs
+      Sequential ST Median3 Prim:     OK
+        30.7 μs ± 2.6 μs
+      Sequential ST Median3or5 Prim:  OK
+        32.0 μs ± 2.1 μs
+    Sorting 10 random arrays of length 1,000 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        69.4 μs ± 4.4 μs
+      C++ three vectors:              OK
+        212  μs ±  16 μs
+      Sequential ST Median3 Unbox:    OK
+        265  μs ±  23 μs
+      Sequential ST Median3or5 Unbox: OK
+        294  μs ±  26 μs
+      Sequential ST Median3 Prim:     OK
+        299  μs ±  27 μs
+      Sequential ST Median3or5 Prim:  OK
+        305  μs ±  23 μs
+    Sorting 10 random arrays of length 10,000 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        3.27 ms ± 195 μs
+      C++ three vectors:              OK
+        3.62 ms ± 352 μs
+      Sequential ST Median3 Unbox:    OK
+        5.11 ms ± 338 μs
+      Sequential ST Median3or5 Unbox: OK
+        5.17 ms ± 398 μs
+      Sequential ST Median3 Prim:     OK
+        5.70 ms ± 369 μs
+      Sequential ST Median3or5 Prim:  OK
+        5.64 ms ± 370 μs
+    Sorting 10 random arrays of length 100,000 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        40.7 ms ± 1.6 ms
+      C++ three vectors:              OK
+        43.8 ms ± 1.4 ms
+      Sequential ST Median3 Unbox:    OK
+        62.2 ms ± 2.7 ms
+      Sequential ST Median3or5 Unbox: OK
+        62.9 ms ± 3.0 ms
+      Sequential ST Median3 Prim:     OK
+        70.7 ms ± 1.8 ms
+      Sequential ST Median3or5 Prim:  OK
+        69.7 ms ± 3.4 ms
+    Sorting 10 random arrays of length 1,000,000 with few duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        510  ms ± 3.0 ms
+      C++ three vectors:              OK
+        542  ms ±  18 ms
+      Sequential ST Median3 Unbox:    OK
+        752  ms ±  14 ms
+      Sequential ST Median3or5 Unbox: OK
+        757  ms ±  35 ms
+      Sequential ST Median3 Prim:     OK
+        848  ms ±  73 ms
+      Sequential ST Median3or5 Prim:  OK
+        823  ms ±  20 ms
+    Sorting 10 random arrays of length 16 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        876  ns ±  54 ns
+      C++ three vectors:              OK
+        2.40 μs ± 181 ns
+      Sequential ST Median3 Unbox:    OK
+        1.45 μs ± 132 ns
+      Sequential ST Median3or5 Unbox: OK
+        1.44 μs ± 115 ns
+      Sequential ST Median3 Prim:     OK
+        1.39 μs ± 102 ns
+      Sequential ST Median3or5 Prim:  OK
+        1.41 μs ±  94 ns
+    Sorting 10 random arrays of length 17 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        889  ns ±  57 ns
+      C++ three vectors:              OK
+        2.63 μs ± 181 ns
+      Sequential ST Median3 Unbox:    OK
+        1.64 μs ± 120 ns
+      Sequential ST Median3or5 Unbox: OK
+        1.65 μs ± 130 ns
+      Sequential ST Median3 Prim:     OK
+        1.47 μs ± 107 ns
+      Sequential ST Median3or5 Prim:  OK
+        1.47 μs ± 119 ns
+    Sorting 10 random arrays of length 100 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        7.62 μs ± 597 ns
+      C++ three vectors:              OK
+        13.8 μs ± 693 ns
+      Sequential ST Median3 Unbox:    OK
+        10.8 μs ± 790 ns
+      Sequential ST Median3or5 Unbox: OK
+        10.5 μs ± 839 ns
+      Sequential ST Median3 Prim:     OK
+        12.7 μs ± 814 ns
+      Sequential ST Median3or5 Prim:  OK
+        12.9 μs ± 910 ns
+    Sorting 10 random arrays of length 256 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        14.8 μs ± 978 ns
+      C++ three vectors:              OK
+        38.7 μs ± 2.7 μs
+      Sequential ST Median3 Unbox:    OK
+        34.0 μs ± 3.4 μs
+      Sequential ST Median3or5 Unbox: OK
+        34.0 μs ± 3.0 μs
+      Sequential ST Median3 Prim:     OK
+        31.6 μs ± 3.1 μs
+      Sequential ST Median3or5 Prim:  OK
+        32.5 μs ± 2.3 μs
+    Sorting 10 random arrays of length 1,000 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        69.2 μs ± 4.9 μs
+      C++ three vectors:              OK
+        151  μs ±  11 μs
+      Sequential ST Median3 Unbox:    OK
+        256  μs ±  17 μs
+      Sequential ST Median3or5 Unbox: OK
+        291  μs ±  15 μs
+      Sequential ST Median3 Prim:     OK
+        303  μs ±  26 μs
+      Sequential ST Median3or5 Prim:  OK
+        292  μs ± 7.8 μs
+    Sorting 10 random arrays of length 10,000 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        2.98 ms ± 193 μs
+      C++ three vectors:              OK
+        3.48 ms ± 182 μs
+      Sequential ST Median3 Unbox:    OK
+        4.30 ms ± 345 μs
+      Sequential ST Median3or5 Unbox: OK
+        4.50 ms ± 446 μs
+      Sequential ST Median3 Prim:     OK
+        4.80 ms ± 339 μs
+      Sequential ST Median3or5 Prim:  OK
+        4.87 ms ± 407 μs
+    Sorting 10 random arrays of length 100,000 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        30.2 ms ± 1.8 ms
+      C++ three vectors:              OK
+        41.1 ms ± 1.8 ms
+      Sequential ST Median3 Unbox:    OK
+        40.8 ms ± 1.9 ms
+      Sequential ST Median3or5 Unbox: OK
+        41.6 ms ± 1.7 ms
+      Sequential ST Median3 Prim:     OK
+        43.7 ms ± 1.5 ms
+      Sequential ST Median3or5 Prim:  OK
+        43.6 ms ± 3.0 ms
+    Sorting 10 random arrays of length 1,000,000 with many duplicates
+      Sanity:                         OK
+      C++ single vector:              OK
+        337  ms ±  11 ms
+      C++ three vectors:              OK
+        420  ms ± 5.0 ms
+      Sequential ST Median3 Unbox:    OK
+        389  ms ± 3.3 ms
+      Sequential ST Median3or5 Unbox: OK
+        398  ms ± 3.4 ms
+      Sequential ST Median3 Prim:     OK
+        452  ms ±  27 ms
+      Sequential ST Median3or5 Prim:  OK
+        428  ms ± 2.9 ms
+
 ```
 
diff --git a/bench/BenchMain.hs b/bench/BenchMain.hs
--- a/bench/BenchMain.hs
+++ b/bench/BenchMain.hs
@@ -12,7 +12,7 @@
 
 {-# OPTIONS_GHC -Wno-orphans #-}
 
-module BenchmarkMain (main) where
+module BenchMain (main) where
 
 import Prelude hiding (pi, last)
 
@@ -22,8 +22,9 @@
 import Control.Monad
 import Control.Monad.ST
 import Data.ByteString.Char8 qualified as C8
+import Data.Coerce
+import Data.Foldable
 import Data.Int
-import Data.Ord
 import Data.Text (Text)
 import Data.Text qualified as T
 import Data.Text.Builder.Linear qualified as TBL
@@ -33,11 +34,20 @@
 import Data.Vector.Primitive.Mutable qualified as PM
 import Data.Vector.Storable qualified as S
 import Data.Vector.Storable.Mutable qualified as SM
+import Data.Vector.Unboxed qualified as U
+import Data.Vector.Unboxed.Mutable qualified as UM
+import Data.Word
 import Foreign.C.Types
+import System.Exit
 import System.Random.Stateful
 
+import Test.Tasty
 import Test.Tasty.Bench
+import Test.Tasty.HUnit
+import Test.Tasty.Ingredients.ConsoleReporter (MinDurationToReport(MinDurationToReport))
+import Test.Tasty.Options
 import Test.Tasty.Patterns.Printer (printAwkExpr)
+import Test.Tasty.Runners
 
 import ForeignSorting
 
@@ -47,7 +57,6 @@
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelStrategiesMedian3ST
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelStrategiesMedian3or5IO
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelStrategiesMedian3or5ST
-import Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialAveragingMedianST
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialMedian3IO
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialMedian3ST
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialMedian3or5IO
@@ -56,13 +65,12 @@
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelMedian3IO
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelMedian3or5IO
 
-newtype SortKey v = SortKey { _unSortKey :: (Int32, Int, v) }
 
-instance Eq (SortKey v) where
-  SortKey (a, b, _) == SortKey (a', b', _) = a == a' && b == b'
-
-instance Ord (SortKey v) where
-  SortKey (a, b, _) `compare` SortKey (a', b', _) = Down a `compare` Down a' <> b `compare` b'
+import Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+import Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3ST
+import Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3or5ST
+import Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3ST
+import Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3or5ST
 
 {-# NOINLINE qsortSeq3 #-}
 qsortSeq3 :: P.Vector Int64 -> ST s (PM.MVector s Int64)
@@ -99,13 +107,6 @@
   sortPIntSequentialMedian3or5ST ys
   pure ys
 
-{-# NOINLINE qsortSeqAvgST #-}
-qsortSeqAvgST :: P.Vector Int64 -> ST s (PM.MVector s Int64)
-qsortSeqAvgST xs = do
-  ys <- P.thaw xs
-  sortPIntSequentialAveragingMedianST ys
-  pure ys
-
 {-# NOINLINE qsortSeq3or5IO #-}
 qsortSeq3or5IO :: P.Vector Int64 -> IO (PM.MVector RealWorld Int64)
 qsortSeq3or5IO xs = do
@@ -162,6 +163,68 @@
   SM.unsafeWith ys $ \ptr -> cppSortInt64 ptr (CInt (fromIntegral (SM.length ys)))
   pure ys
 
+{-# NOINLINE cppStorableTriple #-}
+cppStorableTriple :: S.Vector CPoint -> IO (SM.MVector RealWorld CPoint)
+cppStorableTriple xs = do
+  ys <- S.thaw xs
+  SM.unsafeWith ys $ \ptr -> cppSortCPoint ptr (CInt (fromIntegral (SM.length ys)))
+  pure ys
+
+{-# NOINLINE cppStorableTripleUnpacked #-}
+cppStorableTripleUnpacked
+  :: (S.Vector CDouble, S.Vector CDouble, S.Vector Word64)
+  -> IO (SM.MVector RealWorld CDouble, SM.MVector RealWorld CDouble, SM.MVector RealWorld Word64)
+cppStorableTripleUnpacked (as, bs, cs) = do
+  as' <- S.thaw as
+  bs' <- S.thaw bs
+  cs' <- S.thaw cs
+  SM.unsafeWith as' $ \as'' ->
+    SM.unsafeWith bs' $ \bs'' ->
+      SM.unsafeWith cs' $ \cs'' -> cppSortCPointUnboxed as'' bs'' cs'' (CInt (fromIntegral (SM.length as')))
+  pure (as', bs', cs')
+
+{-# NOINLINE qsortSeq3STTripleUnbox #-}
+qsortSeq3STTripleUnbox
+  :: forall s.
+     U.Vector (Double, Double, Word64)
+  -> ST s (UM.MVector s (Double, Double, Word64))
+qsortSeq3STTripleUnbox xs = do
+  (ys :: UM.MVector s (Double, Double, Word64)) <- U.thaw xs
+  sortUTripleSequentialMedian3ST (coerce ys :: UM.MVector s SortTriple)
+  pure ys
+
+{-# NOINLINE qsortSeq3or5STTripleUnbox #-}
+qsortSeq3or5STTripleUnbox
+  :: forall s.
+     U.Vector (Double, Double, Word64)
+  -> ST s (UM.MVector s (Double, Double, Word64))
+qsortSeq3or5STTripleUnbox xs = do
+  (ys :: UM.MVector s (Double, Double, Word64)) <- U.thaw xs
+  sortUTripleSequentialMedian3or5ST (coerce ys :: UM.MVector s SortTriple)
+  pure ys
+
+
+{-# NOINLINE qsortSeq3STTriplePrim #-}
+qsortSeq3STTriplePrim
+  :: forall s.
+     P.Vector SortTriple
+  -> ST s (PM.MVector s SortTriple)
+qsortSeq3STTriplePrim xs = do
+  (ys :: PM.MVector s SortTriple) <- P.thaw xs
+  sortPTripleSequentialMedian3ST ys
+  pure ys
+
+{-# NOINLINE qsortSeq3or5STTriplePrim #-}
+qsortSeq3or5STTriplePrim
+  :: forall s.
+     P.Vector SortTriple
+  -> ST s (PM.MVector s SortTriple)
+qsortSeq3or5STTriplePrim xs = do
+  (ys :: PM.MVector s SortTriple) <- P.thaw xs
+  sortPTripleSequentialMedian3or5ST ys
+  pure ys
+
+
 main :: IO ()
 main = do
   (fuzzyMatchScores :: P.Vector Int64) <-
@@ -169,38 +232,77 @@
 
   putStrLn $ "P.length fuzzyMatchScores = " ++ show (P.length fuzzyMatchScores)
 
-  let generate :: Int -> Int -> IOGenM StdGen -> IO (P.Vector Int64)
-      generate n k g = P.fromList <$> replicateM n (uniformRM (1 :: Int64, fromIntegral k) g)
+  let generateInt64 :: Int -> Int -> IOGenM StdGen -> IO (P.Vector Int64)
+      generateInt64 n k g = P.replicateM n (uniformRM (1 :: Int64, fromIntegral k) g)
 
+  let generateTriple :: Int -> Int -> IOGenM StdGen -> IO (U.Vector (Double, Double, Word64))
+      generateTriple n k g =
+        U.replicateM n $
+          (,,)
+            <$> uniformDouble01M g
+            <*> uniformDouble01M g
+            <*> uniformRM (1 :: Word64, fromIntegral k) g
+
   gen       <- newIOGenM $ mkStdGen 1
   let sizes :: [(Int, Int)]
-      sizes = map (10, ) [16, 17, 100, 256, 1000, 10_000, 100_000, 1_000_000, 10_000_000]
+      sizes = map (10, ) [16, 17, 100, 256, 1000, 10_000, 100_000, 1_000_000] -- [, 10_000_000]
 
-  xsssNoDup <- traverse (\(n, k) -> replicateM n $ generate k k gen) sizes
-  xsssDup   <- traverse (\(n, k) -> replicateM n $ generate k (max 1000 (k `quot` 1000)) gen) sizes
+  (xsssNoDup :: [[P.Vector Int64]]) <- traverse (\(n, k) -> replicateM n $ generateInt64 k k gen) sizes
+  (xsssDup   :: [[P.Vector Int64]]) <- traverse (\(n, k) -> replicateM n $ generateInt64 k (max 1000 (k `quot` 1000)) gen) sizes
 
+  (ysssNoDup :: [[U.Vector (Double, Double, Word64)]]) <- traverse (\(n, k) -> replicateM n $ generateTriple k k gen) sizes
+  (ysssDup   :: [[U.Vector (Double, Double, Word64)]]) <- traverse (\(n, k) -> replicateM n $ generateTriple k (max 1000 (k `quot` 1000)) gen) sizes
+
   evaluate $ rnf xsssNoDup
   evaluate $ rnf xsssDup
+  evaluate $ rnf ysssNoDup
+  evaluate $ rnf ysssDup
 
-  defaultMain $
-    [ mkBenches "Sorting fuzzy matching scores vector" (MkSolo fuzzyMatchScores)
-    ] ++
-    [ mkBenches ("Sorting " ++ show (length xss) ++ " random arrays of length " ++ T.unpack (formatNumber (P.length (head xss))) ++ " with few duplicates") xss
-    | xss <- xsssNoDup
-    ] ++
-    [ mkBenches ("Sorting " ++ show (length xss) ++ " random arrays of length " ++ T.unpack (formatNumber (P.length (head xss))) ++ " with many duplicates") xss
-    | xss <- xsssDup
-    ]
+  let ingredients = benchIngredients
 
+  let benchmark = bgroup "All" $ map (localOption WallTime) $
+        [ mkBenchesInt64 "Sorting fuzzy matching scores vector" (MkSolo fuzzyMatchScores)
+        ] ++
+        [ bgroup "Int64" $
+          [ mkBenchesInt64 ("Sorting " ++ show (length xss) ++ " random arrays of length " ++ T.unpack (formatNumber (P.length (head' xss))) ++ " with few duplicates") xss
+          | xss <- xsssNoDup
+          ] ++
+          [ mkBenchesInt64 ("Sorting " ++ show (length xss) ++ " random arrays of length " ++ T.unpack (formatNumber (P.length (head' xss))) ++ " with many duplicates") xss
+          | xss <- xsssDup
+          ]
+        , bgroup "(Double, Double, Int64)" $
+          [ mkBenchesTriple ("Sorting " ++ show (length yss) ++ " random arrays of length " ++ T.unpack (formatNumber (U.length (head' yss))) ++ " with few duplicates") yss
+          | yss <- ysssNoDup
+          ] ++
+          [ mkBenchesTriple ("Sorting " ++ show (length yss) ++ " random arrays of length " ++ T.unpack (formatNumber (U.length (head' yss))) ++ " with many duplicates") yss
+          | yss <- ysssDup
+          ]
+        ]
+
+  installSignalHandlers
+  opts <- parseOptions ingredients benchmark
+  let opts' = setOption (MinDurationToReport 1000000000000) $ setOption (NumThreads 1) opts
+
+  case tryIngredients ingredients opts' benchmark of
+    Nothing -> die
+      "No ingredients agreed to run. Something is wrong either with your ingredient set or the options."
+    Just act -> do
+      ok <- act
+      if ok then exitSuccess else exitFailure
+
+head' :: [a] -> a
+head' (x:_) = x
+head' []     = error "oops, empty head"
+
 instance NFData (TVar a) where
   rnf x = x `seq` ()
 
-{-# INLINE mkBenches #-}
-mkBenches
+{-# INLINE mkBenchesInt64 #-}
+mkBenchesInt64
   :: forall f. (Traversable f, forall a. NFData a => NFData (f a))
   => String -> f (P.Vector Int64) -> Benchmark
-mkBenches name xssPrim = mapLeafBenchmarks addCompare $ bgroup name
-  [ bench cppBenchName                    $ nfAppIO (traverse cppUnboxedInt64) xsStorable
+mkBenchesInt64 name xssPrim = mapLeafBenchmarks addCompare $ bgroup name
+  [ bench cppBenchName                    $ nfAppIO (traverse cppUnboxedInt64) xssStorable
 
   , bench "Sequential ST Median3"         $ nfAppIO (stToIO . traverse qsortSeq3) xssPrim
   , bench "Sequential IO Median3"         $ nfAppIO (traverse qsortSeqIO3) xssPrim
@@ -212,8 +314,6 @@
   , bench "ParStrategies ST Median3or5"   $ nfAppIO (stToIO . traverse qsortParStrategies3or5ST) xssPrim
   , bench "ParStrategies IO Median3or5"   $ nfAppIO (traverse qsortParStrategies3or5IO) xssPrim
 
-  , bench "Sequential ST AveragingMedian" $ nfAppIO (stToIO . traverse qsortSeqAvgST) xssPrim
-
   , bench "Threads IO Median3"            $ nfAppIO (traverse qsortParallel3IO) xssPrim
   , bench "Threads IO Median3or5"         $ nfAppIO (traverse qsortParallel3or5IO) xssPrim
 
@@ -221,8 +321,76 @@
   , bench "fallback heapsort"             $ nfAppIO (stToIO . traverse fallbackHeapsortInt64) xssPrim
   ]
   where
-    xsStorable :: f (S.Vector Int64)
-    xsStorable = P.convert <$> xssPrim
+    xssStorable :: f (S.Vector Int64)
+    xssStorable = P.convert <$> xssPrim
+
+isSorted :: Ord a => [a] -> Bool
+isSorted = \case
+  []     -> True
+  x : xs -> go x xs
+  where
+    go _ []       = True
+    go x (y : ys) = x <= y && go y ys
+
+assertSorted :: (Show a, Ord a) => String -> [a] -> IO ()
+assertSorted label xs =
+  assertBool (label ++ " not sorted: " ++ show xs) $ isSorted xs
+
+{-# INLINE mkBenchesTriple #-}
+mkBenchesTriple
+  :: forall f. (Traversable f, forall a. NFData a => NFData (f a))
+  => String -> f (U.Vector (Double, Double, Word64)) -> Benchmark
+mkBenchesTriple name xssUnbox = bgroup name
+  [ testCase "Sanity" $
+      for_ xssUnbox $ \xs -> do
+        xsUnboxed  <- U.unsafeFreeze =<< stToIO (qsortSeq3STTripleUnbox xs)
+        xsStorable <- S.unsafeFreeze =<< cppStorableTriple (mkStorable xs)
+        xsUnpacked <-
+          (\(a, b, c) -> (,,) <$> S.unsafeFreeze a <*> S.unsafeFreeze b <*> S.unsafeFreeze c) =<<
+          cppStorableTripleUnpacked (mkUnpacked xs)
+        xsPrim     <- P.unsafeFreeze =<< stToIO (qsortSeq3STTriplePrim (mkPrim xs))
+        assertSorted "Unboxed"   $ map (\(_, _, c) -> c) $ U.toList xsUnboxed
+        assertSorted "Storabale" $ map (\(CPoint _ _ z) -> z) $ S.toList xsStorable
+        assertSorted "Unpacked"  $ S.toList $ (\(_, _, c) -> c) xsUnpacked
+        assertSorted "Prim"      $ map (\(SortTriple _ _ c) -> c) $ P.toList $ xsPrim
+
+  , bench "C++ single vector"              $ nfAppIO (traverse cppStorableTriple) xssStorable
+  , bench "C++ three vectors"              $ nfAppIO (traverse cppStorableTripleUnpacked) xssUnpacked
+
+  , bench "Sequential ST Median3 Unbox"    $ nfAppIO (stToIO . traverse qsortSeq3STTripleUnbox) xssUnbox
+  , bench "Sequential ST Median3or5 Unbox" $ nfAppIO (stToIO . traverse qsortSeq3or5STTripleUnbox) xssUnbox
+
+  , bench "Sequential ST Median3 Prim"     $ nfAppIO (stToIO . traverse qsortSeq3STTriplePrim) xssPrim
+  , bench "Sequential ST Median3or5 Prim"  $ nfAppIO (stToIO . traverse qsortSeq3or5STTriplePrim) xssPrim
+  ]
+  where
+    mkStorable
+      :: U.Vector (Double, Double, Word64)
+      -> S.Vector CPoint
+    mkStorable = S.fromList . map (\(a, b, c) -> CPoint (CDouble a) (CDouble b) c) . U.toList
+
+    mkPrim
+      :: U.Vector (Double, Double, Word64)
+      -> P.Vector SortTriple
+    mkPrim = U.convert . U.map (\(a, b, c) -> SortTriple a b c)
+
+    mkUnpacked
+      :: U.Vector (Double, Double, Word64)
+      -> (S.Vector CDouble, S.Vector CDouble, S.Vector Word64)
+    mkUnpacked xs =
+      ( S.fromList . map (\(a, _, _) -> CDouble a) . U.toList $ xs
+      , S.fromList . map (\(_, b, _) -> CDouble b) . U.toList $ xs
+      , U.convert . U.map (\(_, _, c) -> c) $ xs
+      )
+
+    xssStorable :: f (S.Vector CPoint)
+    xssStorable = mkStorable <$> xssUnbox
+
+    xssUnpacked :: f (S.Vector CDouble, S.Vector CDouble, S.Vector Word64)
+    xssUnpacked = mkUnpacked <$> xssUnbox
+
+    xssPrim :: f (P.Vector SortTriple)
+    xssPrim = mkPrim <$> xssUnbox
 
 cppBenchName :: String
 cppBenchName = "C++"
diff --git a/bench/ForeignSorting.hs b/bench/ForeignSorting.hs
deleted file mode 100644
--- a/bench/ForeignSorting.hs
+++ /dev/null
@@ -1,18 +0,0 @@
--- |
--- Module:     ForeignSorting
--- Copyright:  (c) Sergey Vinokurov 2023
--- License:    Apache-2.0 (see LICENSE)
--- Maintainer: serg.foo@gmail.com
-
-module ForeignSorting
-  ( cppSortInt64
-  ) where
-
-import Data.Int
-import Foreign
-import Foreign.C.Types
-
-foreign import ccall unsafe "cplusplus_sort" cppSortInt64
-  :: Ptr Int64
-  -> CInt
-  -> IO ()
diff --git a/bench/ForeignSorting.hsc b/bench/ForeignSorting.hsc
new file mode 100644
--- /dev/null
+++ b/bench/ForeignSorting.hsc
@@ -0,0 +1,56 @@
+-- |
+-- Module:     ForeignSorting
+-- Copyright:  (c) Sergey Vinokurov 2023
+-- License:    Apache-2.0 (see LICENSE)
+-- Maintainer: serg.foo@gmail.com
+
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+module ForeignSorting
+  ( cppSortInt64
+  , cppSortCPoint
+  , cppSortCPointUnboxed
+  , CPoint(..)
+  ) where
+
+import Data.Int
+import Data.Word
+import Foreign
+import Foreign.C.Types
+
+#include <sort.h>
+
+foreign import ccall unsafe "cplusplus_sort_int64" cppSortInt64
+  :: Ptr Int64
+  -> CInt
+  -> IO ()
+
+data CPoint = CPoint !CDouble !CDouble !Word64
+  deriving (Eq, Show)
+
+
+
+instance Storable CPoint where
+   sizeOf    _ = (#size struct Point)
+   alignment _ = (#alignment struct Point)
+   peek p =
+     CPoint
+       <$> (#peek struct Point, x) p
+       <*> (#peek struct Point, y) p
+       <*> (#peek struct Point, id) p
+   poke p (CPoint x y n) = do
+     (#poke struct Point, x) p x
+     (#poke struct Point, y) p y
+     (#poke struct Point, id) p n
+
+foreign import ccall unsafe "cplusplus_sort_point" cppSortCPoint
+  :: Ptr CPoint
+  -> CInt
+  -> IO ()
+
+foreign import ccall unsafe "cplusplus_sort_point_unboxed" cppSortCPointUnboxed
+  :: Ptr CDouble
+  -> Ptr CDouble
+  -> Ptr Word64
+  -> CInt
+  -> IO ()
diff --git a/cbits/sort.cpp b/cbits/sort.cpp
--- a/cbits/sort.cpp
+++ b/cbits/sort.cpp
@@ -1,8 +1,178 @@
 #include <algorithm>
 #include <cstdint>
+#include <iterator>
 
+#include <sort.h>
+
+inline bool operator<(Point x, Point y) {
+    return x.id < y.id;
+}
+
+namespace std {
+
+namespace sort_helper {
+
+template <typename _Data1, typename _Data2, typename _Order>
+struct value_reference_t;
+
+template <typename _Data1, typename _Data2, typename _Order>
+struct value_t {
+    _Data1 data1;
+    _Data2 data2;
+    _Order val;
+    inline value_t(_Data1 _data1, _Data2 _data2, _Order _val) : data1(_data1), data2(_data2), val(_val) {}
+    inline value_t(const value_reference_t<_Data1, _Data2, _Order>& rhs);
+
+    inline bool operator < (const value_t<_Data1, _Data2, _Order>& rhs) const { return val < rhs.val; }
+};
+
+template <typename _Data1, typename _Data2, typename _Order>
+struct value_reference_t {
+    _Data1* pdata1;
+    _Data2* pdata2;
+    _Order* pval;
+    value_reference_t(_Data1* _itData1, _Data2* _itData2, _Order* _itVal)
+        : pdata1(_itData1)
+        , pdata2(_itData2)
+        , pval(_itVal)
+        {}
+    inline value_reference_t& operator = (const value_reference_t& rhs) {
+        *pdata1 = *rhs.pdata1;
+        *pdata2 = *rhs.pdata2;
+        *pval   = *rhs.pval;
+        return *this;
+    }
+    inline value_reference_t& operator = (const value_t<_Data1, _Data2, _Order>& rhs) {
+        *pdata1 = rhs.data1;
+        *pdata2 = rhs.data2;
+        *pval   = rhs.val;
+        return *this;
+    }
+    inline bool operator < (const value_reference_t& rhs) { return *pval < *rhs.pval; }
+};
+
+template <typename _Data1, typename _Data2, typename _Order>
+struct value_iterator_t :
+    std::iterator<
+    random_access_iterator_tag,
+    value_t<_Data1, _Data2, _Order>,
+    ptrdiff_t,
+    value_t<_Data1, _Data2, _Order>*,
+    value_reference_t<_Data1, _Data2, _Order>
+    >
+{
+    _Data1* itData1;
+    _Data2* itData2;
+    _Order* itVal;
+
+    value_iterator_t(_Data1* _itData1, _Data2* _itData2, _Order* _itVal)
+        : itData1(_itData1)
+        , itData2(_itData2)
+        , itVal(_itVal)
+        {}
+
+    inline ptrdiff_t operator - (const value_iterator_t& rhs) const { return itVal - rhs.itVal; }
+    inline value_iterator_t operator + (ptrdiff_t off) const {
+        return value_iterator_t(itData1 + off, itData2 + off, itVal + off);
+    }
+    inline value_iterator_t operator - (ptrdiff_t off) const {
+        return value_iterator_t(itData1 - off, itData2 - off, itVal - off);
+    }
+
+    inline value_iterator_t& operator += (ptrdiff_t off) {
+        itData1 += off;
+        itData2 += off;
+        itVal   += off;
+        return *this;
+    }
+    inline value_iterator_t& operator -= (ptrdiff_t off) {
+        itData1 -= off;
+        itData2 -= off;
+        itVal   -= off;
+        return *this;
+    }
+
+    inline value_iterator_t& operator ++ () { ++itData1; ++itData2; ++itVal; return *this; }
+    inline value_iterator_t& operator -- () { --itData1; --itData2; --itVal; return *this; }
+    inline value_iterator_t operator ++ (int) { return value_iterator_t(itData1++, itData2++, itVal++); }
+    inline value_iterator_t operator -- (int) { return value_iterator_t(itData1--, itData2--, itVal--); }
+    inline value_t<_Data1, _Data2, _Order> operator * () const {
+        return value_t<_Data1, _Data2, _Order>(*itData1, *itData2, *itVal);
+    }
+    inline value_reference_t<_Data1, _Data2, _Order> operator * () {
+        return value_reference_t<_Data1, _Data2, _Order>(itData1, itData2, itVal);
+    }
+
+    inline bool operator <  (const value_iterator_t& rhs) const { return itVal <  rhs.itVal; }
+    inline bool operator <= (const value_iterator_t& rhs) const { return itVal <= rhs.itVal; }
+    inline bool operator >  (const value_iterator_t& rhs) const { return itVal >  rhs.itVal; }
+    inline bool operator >= (const value_iterator_t& rhs) const { return itVal >= rhs.itVal; }
+    inline bool operator == (const value_iterator_t& rhs) const { return itVal == rhs.itVal; }
+    inline bool operator != (const value_iterator_t& rhs) const { return itVal != rhs.itVal; }
+};
+
+template <typename _Data1, typename _Data2, typename _Order>
+inline value_t<_Data1, _Data2, _Order>::value_t(const value_reference_t<_Data1, _Data2, _Order>& rhs)
+    : data1(*rhs.pdata1), data2(*rhs.pdata2), val(*rhs.pval) {}
+
+template <typename _Data1, typename _Data2, typename _Order>
+bool operator < (
+    const value_t<_Data1, _Data2, _Order>& lhs,
+    const value_reference_t<_Data1, _Data2, _Order>& rhs
+    )
+{
+    return lhs.val < *rhs.pval;
+}
+
+template <typename _Data1, typename _Data2, typename _Order>
+bool operator < (
+    const value_reference_t<_Data1, _Data2, _Order>& lhs,
+    const value_t<_Data1, _Data2, _Order>& rhs
+    )
+{
+    return *lhs.pval < rhs.val;
+}
+
+template <typename _Data1, typename _Data2, typename _Order>
+bool operator < (
+    const value_reference_t<_Data1, _Data2, _Order>& lhs,
+    const value_reference_t<_Data1, _Data2, _Order>& rhs
+    )
+{
+    return *lhs.pval < *rhs.pval;
+}
+
+template <typename _Data1, typename _Data2, typename _Order>
+void swap(
+    value_reference_t<_Data1, _Data2, _Order> lhs,
+    value_reference_t<_Data1, _Data2, _Order> rhs
+    )
+{
+    std::swap(*lhs.pdata1, *rhs.pdata1);
+    std::swap(*lhs.pdata2, *rhs.pdata2);
+    std::swap(*lhs.pval, *rhs.pval);
+}
+
+
+} // namespace sort_helper
+
+} // namespace std
+
 extern "C" {
-    void cplusplus_sort(int64_t* xs, int len) {
+
+    void cplusplus_sort_int64(int64_t* xs, int len) {
         std::sort(xs, xs + len);
     }
+
+    void cplusplus_sort_point(Point* xs, int len) {
+        std::sort(xs, xs + len);
+    }
+
+    void cplusplus_sort_point_unboxed(double* xs, double* ys, uint64_t* ids, int len) {
+        typedef std::sort_helper::value_iterator_t<double, double, uint64_t> PointIterator;
+
+        PointIterator i = PointIterator(xs, ys, ids);
+        std::sort(i, i + len);
+    }
+
 }
diff --git a/cbits/sort.h b/cbits/sort.h
new file mode 100644
--- /dev/null
+++ b/cbits/sort.h
@@ -0,0 +1,14 @@
+#ifndef SORT_H_
+#define SORT_H_ 1
+
+/* Created: 14 June 2023 */
+
+#include <stdint.h>
+
+struct Point {
+    double x;
+    double y;
+    uint64_t id;
+};
+
+#endif /* #ifndef SORT_H_ */
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/AveragingMedian.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/AveragingMedian.hs
deleted file mode 100644
--- a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/AveragingMedian.hs
+++ /dev/null
@@ -1,68 +0,0 @@
--- |
--- Module:     Data.Vector.Algorithms.Quicksort.Predefined.AveragingMedian
--- Copyright:  (c) Sergey Vinokurov 2023
--- License:    Apache-2.0 (see LICENSE)
--- Maintainer: serg.foo@gmail.com
-
-{-# LANGUAGE TypeFamilies         #-}
-{-# LANGUAGE UndecidableInstances #-}
-
-module Data.Vector.Algorithms.Quicksort.Predefined.AveragingMedian
-  ( AveragingMedian(..)
-  ) where
-
-import Control.Monad.Primitive
-import Data.Bits
-import Data.Int
-import Data.Kind
-import Data.Vector.Generic.Mutable qualified as GM
-
-import Data.Vector.Algorithms.Quicksort.Median
-import Data.Vector.Algorithms.Quicksort.Predefined.Pair
-
-data AveragingMedian a = AveragingMedian
-
-instance (PrimMonad m, s ~ PrimState m) => Median (AveragingMedian Int64) Int64 m s where
-  {-# INLINE selectMedian #-}
-  selectMedian
-    :: forall (v :: Type -> Type -> Type).
-       GM.MVector v Int64
-    => AveragingMedian Int64
-    -> v s Int64
-    -> m (MedianResult Int64)
-  selectMedian _ !v = do
-    let len :: Int
-        !len = GM.length v
-        pi0, pi1, pi2 :: Int
-        !pi0  = 0
-        !pi1  = halve len
-        !pi2  = len - 1
-    !pv0 <- GM.unsafeRead v pi0
-    !pv1 <- GM.unsafeRead v pi1
-    !pv2 <- GM.unsafeRead v pi2
-    pure $! Guess $ pv0 + pv1 + pv2 `quot` 3
-
-instance (PrimMonad m, s ~ PrimState m) => Median (AveragingMedian (TestPair Int32 b)) (TestPair Int32 b) m s where
-  {-# INLINE selectMedian #-}
-  selectMedian
-    :: forall (v :: Type -> Type -> Type).
-       GM.MVector v (TestPair Int32 b)
-    => AveragingMedian (TestPair Int32 b)
-    -> v s (TestPair Int32 b)
-    -> m (MedianResult (TestPair Int32 b))
-  selectMedian _ !v = do
-    let len :: Int
-        !len = GM.length v
-        pi0, pi1, pi2 :: Int
-        !pi0  = 0
-        !pi1  = halve len
-        !pi2  = len - 1
-    !(TestPair !pv0 pv0') <-                   GM.unsafeRead v pi0
-    !pv1                  <- fst . toTuple <$> GM.unsafeRead v pi1
-    !pv2                  <- fst . toTuple <$> GM.unsafeRead v pi2
-    pure $! Guess $ TestPair (pv0 + pv1 + pv2 `quot` 3) pv0'
-
-{-# INLINE halve #-}
-halve :: Int -> Int
-halve x = x `unsafeShiftR` 1
-
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PIntSequentialAveragingMedianST.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PIntSequentialAveragingMedianST.hs
deleted file mode 100644
--- a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PIntSequentialAveragingMedianST.hs
+++ /dev/null
@@ -1,24 +0,0 @@
--- |
--- Module:     Data.Vector.Algorithms.Quicksort.Predefined.VIntSequentialMedian3ST
--- Copyright:  (c) Sergey Vinokurov 2023
--- License:    Apache-2.0 (see LICENSE)
--- Maintainer: serg.foo@gmail.com
-
-module Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialAveragingMedianST
-  ( sortPIntSequentialAveragingMedianST
-  ) where
-
-import Control.Monad.ST
-import Data.Int
-import Data.Vector.Primitive qualified as P
-
-import Data.Vector.Algorithms.Quicksort.Parameterised
-
-import Data.Vector.Algorithms.Quicksort.Predefined.AveragingMedian
-import Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialMedian3ST ()
-
-{-# NOINLINE sortPIntSequentialAveragingMedianST #-}
-sortPIntSequentialAveragingMedianST :: P.MVector s Int64 -> ST s ()
-sortPIntSequentialAveragingMedianST = sortInplaceFM Sequential (AveragingMedian @Int64)
-
-
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PTripleSequentialMedian3ST.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PTripleSequentialMedian3ST.hs
new file mode 100644
--- /dev/null
+++ b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PTripleSequentialMedian3ST.hs
@@ -0,0 +1,30 @@
+-- |
+-- Module:     Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3ST
+-- Copyright:  (c) Sergey Vinokurov 2023
+-- License:    Apache-2.0 (see LICENSE)
+-- Maintainer: serg.foo@gmail.com
+
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3ST
+  ( sortPTripleSequentialMedian3ST
+  ) where
+
+import Control.Monad.ST
+import Data.Vector.Primitive qualified as P
+
+import Data.Vector.Algorithms.Quicksort.Parameterised
+
+import Data.Vector.Algorithms.FixedSort
+import Data.Vector.Algorithms.Heapsort
+
+import Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+
+{-# SPECIALIZE heapSort    :: P.MVector s SortTriple -> ST s ()        #-}
+{-# SPECIALIZE bitonicSort :: Int -> P.MVector s SortTriple -> ST s () #-}
+
+{-# NOINLINE sortPTripleSequentialMedian3ST #-}
+sortPTripleSequentialMedian3ST :: P.MVector s SortTriple -> ST s ()
+sortPTripleSequentialMedian3ST = sortInplaceFM Sequential (Median3 @SortTriple)
+
+
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PTripleSequentialMedian3or5ST.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PTripleSequentialMedian3or5ST.hs
new file mode 100644
--- /dev/null
+++ b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/PTripleSequentialMedian3or5ST.hs
@@ -0,0 +1,24 @@
+-- |
+-- Module:     Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3or5ST
+-- Copyright:  (c) Sergey Vinokurov 2023
+-- License:    Apache-2.0 (see LICENSE)
+-- Maintainer: serg.foo@gmail.com
+
+module Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3or5ST
+  ( sortPTripleSequentialMedian3or5ST
+  ) where
+
+import Control.Monad.ST
+import Data.Vector.Primitive qualified as P
+
+import Data.Vector.Algorithms.Quicksort.Parameterised
+
+import Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+
+import Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3ST ()
+
+{-# NOINLINE sortPTripleSequentialMedian3or5ST #-}
+sortPTripleSequentialMedian3or5ST :: P.MVector s SortTriple -> ST s ()
+sortPTripleSequentialMedian3or5ST = sortInplaceFM Sequential (Median3or5 @SortTriple)
+
+
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/SortTriple.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/SortTriple.hs
new file mode 100644
--- /dev/null
+++ b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/SortTriple.hs
@@ -0,0 +1,143 @@
+-- |
+-- Module:     Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+-- Copyright:  (c) Sergey Vinokurov 2023
+-- License:    Apache-2.0 (see LICENSE)
+-- Maintainer: serg.foo@gmail.com
+
+{-# LANGUAGE DerivingVia   #-}
+{-# LANGUAGE InstanceSigs  #-}
+{-# LANGUAGE MagicHash     #-}
+{-# LANGUAGE TypeFamilies  #-}
+{-# LANGUAGE UnboxedTuples #-}
+
+module Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+  ( SortTriple(..)
+  , U.MVector(..)
+  , U.Vector(..)
+  ) where
+
+import Data.Word
+import Data.Vector.Generic qualified as G
+import Data.Vector.Generic.Mutable qualified as GM
+import Data.Vector.Unboxed qualified as U
+
+import Data.Primitive.Types
+import GHC.Exts
+import GHC.Word
+
+data SortTriple = SortTriple !Double !Double !Word64
+
+instance Eq SortTriple where
+  SortTriple _ _ c == SortTriple _ _ c' = c == c'
+
+instance Ord SortTriple where
+  SortTriple _ _ c `compare` SortTriple _ _ c' = compare c c'
+
+instance U.IsoUnbox SortTriple (Double, Double, Word64) where
+  toURepr (SortTriple a b c) = (a, b, c)
+  fromURepr (a, b, c) = SortTriple a b c
+  {-# INLINE toURepr   #-}
+  {-# INLINE fromURepr #-}
+
+newtype instance U.MVector s SortTriple = MV_TestPair (U.MVector s (Double, Double, Word64))
+newtype instance U.Vector    SortTriple = V_TestPair  (U.Vector    (Double, Double, Word64))
+deriving via (SortTriple `U.As` (Double, Double, Word64)) instance GM.MVector U.MVector SortTriple
+deriving via (SortTriple `U.As` (Double, Double, Word64)) instance G.Vector   U.Vector  SortTriple
+instance U.Unbox SortTriple
+
+idxs :: Int# -> (# Int#, Int#, Int# #)
+idxs idx =
+  (# start
+  , start +# sizeOf# (undefined :: Double)
+  , start +# sizeOf# (undefined :: Double) +# sizeOf# (undefined :: Double)
+  #)
+  where
+    start = idx *# sizeOf# (undefined :: SortTriple)
+
+instance Prim SortTriple where
+  sizeOf# _
+    =  sizeOf# (undefined :: Double)
+    +# sizeOf# (undefined :: Double)
+    +# sizeOf# (undefined :: Word64)
+
+  alignment# _
+    = alignment# (undefined :: Word64)
+
+  indexByteArray# :: ByteArray# -> Int# -> SortTriple
+  indexByteArray# arr idx =
+    SortTriple
+      (D#   (indexWord8ArrayAsDouble# arr ia))
+      (D#   (indexWord8ArrayAsDouble# arr ib))
+      (W64# (indexWord8ArrayAsWord64# arr ic))
+    where
+      !(# ia, ib, ic #) = idxs idx
+
+  readByteArray# :: MutableByteArray# s -> Int# -> State# s -> (# State# s, SortTriple #)
+  readByteArray# arr idx s1 =
+    case readWord8ArrayAsDouble# arr ia s1 of
+      (# s2, a #) ->
+        case readWord8ArrayAsDouble# arr ib s2 of
+          (# s3, b #) ->
+            case readWord8ArrayAsWord64# arr ic s3 of
+              (# s4, c #) -> (# s4, SortTriple (D# a) (D# b) (W64# c) #)
+    where
+      !(# ia, ib, ic #) = idxs idx
+
+  writeByteArray# :: MutableByteArray# s -> Int# -> SortTriple -> State# s -> State# s
+  writeByteArray# arr idx (SortTriple (D# a) (D# b) (W64# c)) s1 =
+    case writeWord8ArrayAsDouble# arr ia a s1 of
+      s2 ->
+        case writeWord8ArrayAsDouble# arr ib b s2 of
+          s3 ->
+            writeWord8ArrayAsWord64# arr ic c s3
+    where
+      !(# ia, ib, ic #) = idxs idx
+
+  setByteArray#
+    :: MutableByteArray# s
+    -> Int# -- ^ offset
+    -> Int# -- ^ length
+    -> SortTriple
+    -> State# s
+    -> State# s
+  setByteArray# = defaultSetByteArray#
+
+  indexOffAddr# :: Addr# -> Int# -> SortTriple
+  indexOffAddr# addr idx =
+    SortTriple
+      (D#   (indexDoubleOffAddr# addr ia))
+      (D#   (indexDoubleOffAddr# addr ib))
+      (W64# (indexWord64OffAddr# addr ic))
+    where
+      !(# ia, ib, ic #) = idxs idx
+
+  readOffAddr# :: Addr# -> Int# -> State# s -> (# State# s, SortTriple #)
+  readOffAddr# addr idx s1 =
+    case readDoubleOffAddr# addr ia s1 of
+      (# s2, a #) ->
+        case readDoubleOffAddr# addr ib s2 of
+          (# s3, b #) ->
+            case readWord64OffAddr# addr ic s3 of
+              (# s4, c #) -> (# s4, SortTriple (D# a) (D# b) (W64# c) #)
+    where
+      !(# ia, ib, ic #) = idxs idx
+
+  writeOffAddr# :: Addr# -> Int# -> SortTriple -> State# s -> State# s
+  writeOffAddr# addr idx (SortTriple (D# a) (D# b) (W64# c)) s1 =
+    case writeDoubleOffAddr# addr ia a s1 of
+      s2 ->
+        case writeDoubleOffAddr# addr ib b s2 of
+          s3 ->
+            writeWord64OffAddr# addr ic c s3
+    where
+      !(# ia, ib, ic #) = idxs idx
+
+  setOffAddr#
+    :: Addr#
+    -> Int# -- ^ offset
+    -> Int# -- ^ length
+    -> SortTriple
+    -> State# s
+    -> State# s
+  setOffAddr# = defaultSetOffAddr#
+
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UPairSequentialAveragingMedianST.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UPairSequentialAveragingMedianST.hs
deleted file mode 100644
--- a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UPairSequentialAveragingMedianST.hs
+++ /dev/null
@@ -1,23 +0,0 @@
--- |
--- Module:     Data.Vector.Algorithms.Quicksort.Predefined.UPairSequentialAveragingMedianST
--- Copyright:  (c) Sergey Vinokurov 2023
--- License:    Apache-2.0 (see LICENSE)
--- Maintainer: serg.foo@gmail.com
-
-module Data.Vector.Algorithms.Quicksort.Predefined.UPairSequentialAveragingMedianST
-  ( sortUPairSequentialAveragingMedianST
-  ) where
-
-import Control.Monad.ST
-import Data.Int
-import Data.Vector.Unboxed qualified as U
-
-import Data.Vector.Algorithms.Quicksort.Parameterised
-
-import Data.Vector.Algorithms.Quicksort.Predefined.AveragingMedian
-import Data.Vector.Algorithms.Quicksort.Predefined.Pair
-import Data.Vector.Algorithms.Quicksort.Predefined.UPairSequentialMedian3ST ()
-
-{-# NOINLINE sortUPairSequentialAveragingMedianST #-}
-sortUPairSequentialAveragingMedianST :: U.MVector s (TestPair Int32 Int32) -> ST s ()
-sortUPairSequentialAveragingMedianST = sortInplaceFM Sequential (AveragingMedian @(TestPair Int32 Int32))
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UTripleSequentialMedian3ST.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UTripleSequentialMedian3ST.hs
new file mode 100644
--- /dev/null
+++ b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UTripleSequentialMedian3ST.hs
@@ -0,0 +1,30 @@
+-- |
+-- Module:     Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3ST
+-- Copyright:  (c) Sergey Vinokurov 2023
+-- License:    Apache-2.0 (see LICENSE)
+-- Maintainer: serg.foo@gmail.com
+
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+module Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3ST
+  ( sortUTripleSequentialMedian3ST
+  ) where
+
+import Control.Monad.ST
+import Data.Vector.Unboxed qualified as U
+
+import Data.Vector.Algorithms.Quicksort.Parameterised
+
+import Data.Vector.Algorithms.FixedSort
+import Data.Vector.Algorithms.Heapsort
+
+import Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+
+{-# SPECIALIZE heapSort    :: U.MVector s SortTriple -> ST s ()        #-}
+{-# SPECIALIZE bitonicSort :: Int -> U.MVector s SortTriple -> ST s () #-}
+
+{-# NOINLINE sortUTripleSequentialMedian3ST #-}
+sortUTripleSequentialMedian3ST :: U.MVector s SortTriple -> ST s ()
+sortUTripleSequentialMedian3ST = sortInplaceFM Sequential (Median3 @SortTriple)
+
+
diff --git a/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UTripleSequentialMedian3or5ST.hs b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UTripleSequentialMedian3or5ST.hs
new file mode 100644
--- /dev/null
+++ b/src-predefined/Data/Vector/Algorithms/Quicksort/Predefined/UTripleSequentialMedian3or5ST.hs
@@ -0,0 +1,24 @@
+-- |
+-- Module:     Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3or5ST
+-- Copyright:  (c) Sergey Vinokurov 2023
+-- License:    Apache-2.0 (see LICENSE)
+-- Maintainer: serg.foo@gmail.com
+
+module Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3or5ST
+  ( sortUTripleSequentialMedian3or5ST
+  ) where
+
+import Control.Monad.ST
+import Data.Vector.Unboxed qualified as U
+
+import Data.Vector.Algorithms.Quicksort.Parameterised
+
+import Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
+
+import Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3ST ()
+
+{-# NOINLINE sortUTripleSequentialMedian3or5ST #-}
+sortUTripleSequentialMedian3or5ST :: U.MVector s SortTriple -> ST s ()
+sortUTripleSequentialMedian3or5ST = sortInplaceFM Sequential (Median3or5 @SortTriple)
+
+
diff --git a/src/Data/Vector/Algorithms/Quicksort.hs b/src/Data/Vector/Algorithms/Quicksort.hs
--- a/src/Data/Vector/Algorithms/Quicksort.hs
+++ b/src/Data/Vector/Algorithms/Quicksort.hs
@@ -38,10 +38,10 @@
 -- to make sure that this function doesn't take any class
 -- dictionaries. If it does then performance will be very bad since
 -- either comparisons will go via indirection, vector reads/writes,
--- monadic bind, or any combinatior of those will go through
+-- monadic bind, or any combination of those will go through
 -- dictionary indirection. This can be avoided either by compiling
--- with @-fspecialise-aggressively@ flag or by using via @SPECIALIZE@
--- pragmas, like so:
+-- with @-fspecialise-aggressively@ flag or by issuing @SPECIALIZE@
+-- pragmas manually, like so:
 --
 -- > -- Either use the flag to specialize everything, ...
 -- > {-# OPTIONS_GHC -fspecialise-aggressively #-}
@@ -63,6 +63,10 @@
 -- > {-# SPECIALIZE heapSort    :: U.MVector RealWorld Int -> IO ()        #-}
 -- > {-# SPECIALIZE bitonicSort :: Int -> U.MVector RealWorld Int -> IO () #-}
 -- > {-# SPECIALIZE sort        :: U.MVector RealWorld Int -> IO ()        #-}
+--
+-- Please note that since GHC 9.6 SPECIALIZE may not do anything for
+-- the @ST@ monad unless -fpolymorphic-specialisation flag is also
+-- enabled.
 
 {-# OPTIONS_GHC -Wno-orphans #-}
 
@@ -80,10 +84,7 @@
 
 import Data.Vector.Algorithms.Quicksort.Parameterised
 
-{-# SPECIALIZE sortInplaceFM
-  :: (PrimMonad m, Ord a, GM.MVector v a) => Sequential -> Median3or5 a -> v (PrimState m) a -> m () #-}
-
-{-# INLINABLE sort #-}
+{-# INLINE sort #-}
 -- | Good default sort. Returns sorted copy.
 --
 -- This function takes generic vectors so will work with any vectors
@@ -98,7 +99,7 @@
   sortInplaceFM Sequential (Median3or5 @a) ys
   G.unsafeFreeze ys
 
-{-# INLINABLE sortInplace #-}
+{-# INLINE sortInplace #-}
 -- | Good default sort for mutable vectors.
 --
 -- This function takes generic mutable vectors so will work with any
diff --git a/src/Data/Vector/Algorithms/Quicksort/Median.hs b/src/Data/Vector/Algorithms/Quicksort/Median.hs
--- a/src/Data/Vector/Algorithms/Quicksort/Median.hs
+++ b/src/Data/Vector/Algorithms/Quicksort/Median.hs
@@ -8,6 +8,7 @@
 {-# LANGUAGE MagicHash              #-}
 {-# LANGUAGE TypeFamilies           #-}
 {-# LANGUAGE UndecidableInstances   #-}
+{-# LANGUAGE UnboxedTuples          #-}
 
 module Data.Vector.Algorithms.Quicksort.Median
   ( Median(..)
@@ -20,25 +21,17 @@
 
 import Control.Monad.Primitive
 import Data.Bits
-import Data.Function
 import Data.Kind (Type)
 import Data.Vector.Generic.Mutable qualified as GM
+import GHC.Exts (Int(..), Int#)
 
 -- | Median selection result.
 data MedianResult a
   -- | Value that was located at specific index in the original array.
   = ExistingValue !a {-# UNPACK #-} !Int
-  -- | Value that is a good guess for a real median but may not be
-  -- present in the array (or we don't know where it's exactly).
-  --
-  -- Good example is to pick first, last, and middle element and
-  -- average them, which restricts us to dealing with numeric values
-  -- but may yield good results depending on distribution of values in
-  -- the array to be sorted.
-  | Guess !a
 
-existingValue :: CmpFst a Int -> MedianResult a
-existingValue (CmpFst (x, n)) = ExistingValue x n
+existingValue :: (# a, Int# #) -> MedianResult a
+existingValue (# !x, n #) = ExistingValue x (I# n)
 
 -- | Median selection algorithm that, given a vector, should come up
 -- with an elements that has good chances to be median (i.e to be
@@ -76,8 +69,8 @@
 
 {-# INLINE pick3 #-}
 -- Pick median among 3 values.
-pick3 :: Ord a => a -> a -> a -> a
-pick3 a b c =
+pick3 :: Ord a => a -> Int# -> a -> Int# -> a -> Int# -> (# a, Int# #)
+pick3 !a ai !b bi !c ci =
   if b < a
   then
     -- ... b < a ...
@@ -86,13 +79,13 @@
       if c < b
       then
         -- c < b < a
-        b
+        (# b, bi #)
       else
         -- b <= c < a
-        c
+        (# c, ci #)
     else
       --  b < a <= c
-      a
+      (# a, ai #)
   else
     -- ... a <= b ...
     if c < b
@@ -100,18 +93,18 @@
       if c < a
       then
         -- c < a <= b
-        a
+        (# a, ai #)
       else
         -- a <= c <= b
-        c
+        (# c, ci #)
     else
       -- a <= b <= c
-      b
+      (# b, bi #)
 
 {-# INLINE sort3 #-}
 -- Establish sortered order among 3 values.
-sort3 :: Ord a => a -> a -> a -> (a, a, a)
-sort3 a b c =
+sort3 :: Ord a => a -> Int# -> a -> Int# -> a -> Int# -> (# a, Int#, a, Int#, a, Int# #)
+sort3 !a ai !b bi !c ci =
   if b < a
   then
     -- ... b < a ...
@@ -120,13 +113,13 @@
       if c < b
       then
         -- c < b < a
-        (c, b, a)
+        (# c, ci, b, bi, a, ai #)
       else
         -- b <= c < a
-        (b, c, a)
+        (# b, bi, c, ci, a, ai #)
     else
       --  b < a <= c
-      (b, a, c)
+      (# b, bi, a, ai, c, ci #)
   else
     -- ... a <= b ...
     if c < b
@@ -134,25 +127,13 @@
       if c < a
       then
         -- c < a <= b
-        (c, a, b)
+        (# c, ci, a, ai, b, bi #)
       else
         -- a <= c <= b
-        (a, c, b)
+        (# a, ai, c, ci, b, bi #)
     else
       -- a <= b <= c
-      (a, b, c)
-
-newtype CmpFst a b = CmpFst { unCmpFst :: (a, b) }
-
-instance Eq a => Eq (CmpFst a b) where
-  (==) = (==) `on` fst . unCmpFst
-
-instance Ord a => Ord (CmpFst a b) where
-  compare = compare `on` fst . unCmpFst
-
-{-# INLINE readAt #-}
-readAt :: (PrimMonad m, GM.MVector v a) => v (PrimState m) a -> Int -> m (CmpFst a Int)
-readAt xs n = (\x -> CmpFst (x, n)) <$> GM.unsafeRead xs n
+      (# a, ai, b, bi, c, ci #)
 
 instance (PrimMonad m, s ~ PrimState m) => Median (Median3 a) a m s where
   {-# INLINE selectMedian #-}
@@ -165,14 +146,14 @@
   selectMedian _ !v = do
     let len :: Int
         !len = GM.length v
-        pi0, pi1, pi2 :: Int
-        !pi0  = 0
-        !pi1  = halve len
-        !pi2  = len - 1
-    !pv0 <- readAt v pi0
-    !pv1 <- readAt v pi1
-    !pv2 <- readAt v pi2
-    pure $! existingValue $ pick3 pv0 pv1 pv2
+        pi0, pi1, pi2 :: Int#
+        !(I# pi0)  = 0
+        !(I# pi1)  = halve len
+        !(I# pi2)  = len - 1
+    !pv0 <- GM.unsafeRead v (I# pi0)
+    !pv1 <- GM.unsafeRead v (I# pi1)
+    !pv2 <- GM.unsafeRead v (I# pi2)
+    pure $! existingValue (pick3 pv0 pi0 pv1 pi1 pv2 pi2)
 
 instance (PrimMonad m, s ~ PrimState m) => Median (Median3or5 a) a m s where
   {-# INLINE selectMedian #-}
@@ -185,34 +166,34 @@
   selectMedian _ !v = do
     let len :: Int
         !len = GM.length v
-        pi0, pi1, pi2 :: Int
-        !pi0  = 0
-        !pi1  = halve len
-        !pi2  = len - 1
-    !pv0 <- readAt v pi0
-    !pv1 <- readAt v pi1
-    !pv2 <- readAt v pi2
+        pi0, pi1, pi2 :: Int#
+        !(I# pi0)  = 0
+        !(I# pi1)  = halve len
+        !(I# pi2)  = len - 1
+    !pv0 <- GM.unsafeRead v (I# pi0)
+    !pv1 <- GM.unsafeRead v (I# pi1)
+    !pv2 <- GM.unsafeRead v (I# pi2)
 
     -- If median of 3 has chances to be good enough
     if pv0 /= pv1 && pv1 /= pv2 && pv2 /= pv0
-    then pure $! existingValue $ pick3 pv0 pv1 pv2
+    then pure $! existingValue (pick3 pv0 pi0 pv1 pi1 pv2 pi2)
     else do
-      let pi01, pi12 :: Int
-          !pi01 = halve pi1
-          !pi12 = pi1 + pi01
+      let pi01, pi12 :: Int#
+          !(I# pi01) = halve (I# pi1)
+          !(I# pi12) = I# pi1 + I# pi01
 
-      !pv01 <- readAt v pi01
-      !pv12 <- readAt v pi12
+      !pv01 <- GM.unsafeRead v (I# pi01)
+      !pv12 <- GM.unsafeRead v (I# pi12)
 
-      let (!mn, !med, !mx) = sort3 pv0 pv1 pv2
-          (!mn', !mx')
-            | pv01 < pv12 = (pv01, pv12)
-            | otherwise   = (pv12, pv01)
+      let !(# !mn, !mni, !med, !medi, !mx, !mxi #) = sort3 pv0 pi0 pv1 pi1 pv2 pi2
+          !(# !mn', !mni', !mx', !mxi' #)
+            | pv01 < pv12 = (# pv01, pi01, pv12, pi12 #)
+            | otherwise   = (# pv12, pi12, pv01, pi01 #)
 
-          !med'
-            | mn' > mx  = mx
-            | mx' < mn  = mn
-            | otherwise = pick3 mn' med mx'
+          !med'@(# !_, !_ #)
+            | mn' > mx  = (# mx, mxi #)
+            | mx' < mn  = (# mn, mni #)
+            | otherwise = pick3 mn' mni' med medi mx' mxi'
 
       pure $! existingValue med'
 
diff --git a/src/Data/Vector/Algorithms/Quicksort/Parameterised.hs b/src/Data/Vector/Algorithms/Quicksort/Parameterised.hs
--- a/src/Data/Vector/Algorithms/Quicksort/Parameterised.hs
+++ b/src/Data/Vector/Algorithms/Quicksort/Parameterised.hs
@@ -140,10 +140,6 @@
         res <- selectMedian med v'
 
         (!pi', !pv) <- case res of
-          Guess pv -> do
-            (_, !pi') <- partitionTwoWaysGuessedPivot pv last v
-            pure (pi', pv)
-
           ExistingValue pv pi -> do
             when (pi /= last) $ do
               GM.unsafeWrite v pi =<< GM.unsafeRead v last
@@ -169,38 +165,6 @@
       where
         !len       = GM.length v
         !depthDiff = depth - logLen
-
-{-# INLINE partitionTwoWaysGuessedPivot #-}
-partitionTwoWaysGuessedPivot
-  :: (PrimMonad m, Ord a, GM.MVector v a)
-  => a -> Int -> v (PrimState m) a -> m (a, Int)
-partitionTwoWaysGuessedPivot !pv !lastIdx !v =
-  go 0 lastIdx
-  where
-    go !i !j = do
-      !(i', xi) <- goLT i
-      !(j', xj) <- goGT j
-      if i' < j'
-      then do
-        GM.unsafeWrite v j' xi
-        GM.unsafeWrite v i' xj
-        go (i' + 1) (j' - 1)
-      else pure (xi, i')
-      where
-        goLT !k = do
-          if k <= j
-          then do
-            !x <- GM.unsafeRead v k
-            if x < pv
-            then goLT (k + 1)
-            else pure (k, x)
-          -- Be careful not to write this pv into array - pv may not exsit there.
-          else pure (k, pv)
-        goGT !k = do
-          !x <- GM.unsafeRead v k
-          if x >= pv && i < k
-          then goGT (k - 1)
-          else pure (k, x)
 
 {-# INLINE partitionTwoWaysPivotAtEnd #-}
 partitionTwoWaysPivotAtEnd
diff --git a/test/Data/Vector/Algorithms/Quicksort/Tests.hs b/test/Data/Vector/Algorithms/Quicksort/Tests.hs
--- a/test/Data/Vector/Algorithms/Quicksort/Tests.hs
+++ b/test/Data/Vector/Algorithms/Quicksort/Tests.hs
@@ -31,9 +31,6 @@
 
 import Data.Vector.Algorithms.Quicksort.Predefined.Pair
 
-import Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialAveragingMedianST
-import Data.Vector.Algorithms.Quicksort.Predefined.UPairSequentialAveragingMedianST
-
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelMedian3IO
 import Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelMedian3or5IO
 
@@ -178,9 +175,6 @@
       sortsAndDoesNotLoseItemsST @U.Vector sortUPairParallelStrategiesMedian3ST
     , QC.testProperty "Data.Vector.Unboxed (TestPair Int32 Int32) sorting ParallelStrategies Median3or5" $
       sortsAndDoesNotLoseItemsST @U.Vector sortUPairParallelStrategiesMedian3or5ST
-
-    , QC.testProperty "Data.Vector.Unboxed (TestPair Int32 Int32) sorting Sequential AveragingMedian" $
-      sortsAndDoesNotLoseItemsST @U.Vector sortUPairSequentialAveragingMedianST
     ]
   , testGroup "IO"
     [ QC.testProperty "Data.Vector (TestPair Int32 Int32) sorting Sequential Median3" $
@@ -219,9 +213,6 @@
       sortsAndDoesNotLoseItemsSTtoIO @U.Vector sortUPairParallelStrategiesMedian3ST
     , QC.testProperty "Data.Vector.Unboxed (TestPair Int32 Int32) sorting ParallelStrategies Median3or5" $
       sortsAndDoesNotLoseItemsSTtoIO @U.Vector sortUPairParallelStrategiesMedian3or5ST
-
-    , QC.testProperty "Data.Vector.Unboxed (TestPair Int32 Int32) sorting Sequential AveragingMedian" $
-      sortsAndDoesNotLoseItemsSTtoIO @U.Vector sortUPairSequentialAveragingMedianST
     ]
   ]
   where
@@ -307,10 +298,6 @@
   , QC.testProperty "Data.Vector.Unboxed (Int32, Int32) sorting ParStrategies Median3or5" $
       \(xs :: [(Int32, Int32)]) ->
         runST (runSort @U.Vector sortUTupleParallelStrategiesMedian3or5ST xs) === L.sort xs
-
-  , QC.testProperty "Data.Vector.Primitive Int64 sorting Sequential AveragingMedian" $
-      \(xs :: [Int64]) ->
-        runST (runSort @P.Vector sortPIntSequentialAveragingMedianST xs) === L.sort xs
   ]
 
 sortTestsIO :: TestTree
@@ -427,11 +414,6 @@
   , QC.testProperty "Data.Vector.Unboxed (Int32, Int32) sorting ParStrategies Median3or5" $
       \(xs :: [(Int32, Int32)]) -> ioProperty $ do
         ys <- runSortSTtoIO @U.Vector sortUTupleParallelStrategiesMedian3or5ST xs
-        pure $ ys === L.sort xs
-
-  , QC.testProperty "Data.Vector.Primitive Int64 sorting Sequential AveragingMedian" $
-      \(xs :: [Int64]) -> ioProperty $ do
-        ys <- runSortSTtoIO @P.Vector sortPIntSequentialAveragingMedianST xs
         pure $ ys === L.sort xs
   ]
 
diff --git a/vector-quicksort.cabal b/vector-quicksort.cabal
--- a/vector-quicksort.cabal
+++ b/vector-quicksort.cabal
@@ -3,7 +3,7 @@
 name:
   vector-quicksort
 version:
-  0.1
+  0.2
 
 synopsis:
   Fast and flexible quicksort implementation for mutable vectors
@@ -41,6 +41,9 @@
 
 extra-source-files:
   Readme.md
+  Changelog.md
+  cbits/sort.cpp
+  cbits/sort.h
 
 homepage:
   https://github.com/sergv/vector-quicksort
@@ -54,7 +57,6 @@
     GHC2021
 
   default-extensions:
-    ImportQualifiedPost
     LambdaCase
 
   ghc-options:
@@ -67,7 +69,6 @@
     -Wno-missing-local-signatures
     -Wno-missing-safe-haskell-mode
     -Wno-safe
-    -Wno-type-defaults
     -Wno-unsafe
     -Wno-unused-packages
 
@@ -79,6 +80,11 @@
     ghc-options:
       -Wno-missing-kind-signatures
 
+  if impl(ghc >= 9.8)
+    ghc-options:
+      -Wno-missing-role-annotations
+      -Wno-missing-poly-kind-signatures
+
 library
   import: ghc-options
   exposed-modules:
@@ -91,17 +97,24 @@
   hs-source-dirs:
     src
   build-depends:
-    , base >= 4.14 && < 5
+    , base >= 4.15 && < 5
     , primitive
     , stm
     , vector
 
-library vector-quicksort-predefined
+library test-predefined-sorts
   import: ghc-options
 
+  visibility:
+    private
+
+  if impl(ghc >= 9.6)
+    ghc-options:
+      -fpolymorphic-specialisation
+
   exposed-modules:
-    Data.Vector.Algorithms.Quicksort.Predefined.AveragingMedian
     Data.Vector.Algorithms.Quicksort.Predefined.Pair
+    Data.Vector.Algorithms.Quicksort.Predefined.SortTriple
 
     Data.Vector.Algorithms.Quicksort.Predefined.BitonicIntST
 
@@ -117,9 +130,6 @@
     Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelStrategiesMedian3or5ST
     Data.Vector.Algorithms.Quicksort.Predefined.PIntParallelStrategiesMedian3or5IO
 
-    Data.Vector.Algorithms.Quicksort.Predefined.PIntSequentialAveragingMedianST
-    Data.Vector.Algorithms.Quicksort.Predefined.UPairSequentialAveragingMedianST
-
     Data.Vector.Algorithms.Quicksort.Predefined.VIntSequentialMedian3ST
     Data.Vector.Algorithms.Quicksort.Predefined.VIntSequentialMedian3IO
     Data.Vector.Algorithms.Quicksort.Predefined.VPairSequentialMedian3ST
@@ -173,15 +183,18 @@
     Data.Vector.Algorithms.Quicksort.Predefined.UTupleParallelStrategiesMedian3or5ST
     Data.Vector.Algorithms.Quicksort.Predefined.UTupleParallelStrategiesMedian3or5IO
 
+    Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3ST
+    Data.Vector.Algorithms.Quicksort.Predefined.PTripleSequentialMedian3or5ST
+    Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3ST
+    Data.Vector.Algorithms.Quicksort.Predefined.UTripleSequentialMedian3or5ST
+
   hs-source-dirs:
     src-predefined
 
   build-depends:
-    , base >= 4.14
-    , parallel
+    , base >= 4.15
     , primitive
-    , stm
-    , vector
+    , vector >= 0.13
     , vector-quicksort
 
 test-suite test
@@ -197,21 +210,23 @@
     test
   build-depends:
     , QuickCheck
-    , base >= 4.14
+    , base >= 4.15
     , containers
     , tasty
     , tasty-quickcheck
     , vector
-    , vector-quicksort-predefined
+    , test-predefined-sorts
   ghc-options:
     -rtsopts
     -threaded
     "-with-rtsopts=-N -A16m -n1m -qg -qb"
     -main-is TestMain
 
-library vector-quicksort-cpp
+library test-foreign-sort
   default-language:
     GHC2021
+  visibility:
+    private
   include-dirs:
     cbits
   cxx-sources:
@@ -235,12 +250,6 @@
   elif os(linux)
     extra-libraries: stdc++
   else
-    -- This is supposed to be under arch(wasm32), but we can't do that yet
-    -- since cabal check would fail with unrecognized arch, see
-    -- https://github.com/haskell/cabal/pull/8096 for the fix.
-    -- TODO: when a new cabal release is out with that fix, bump cabal in CI
-    -- and add proper arch(wasm32) declaration here.
-    cpp-options: -DSIMDUTF_NO_THREADS
     cxx-options: -fno-exceptions
     extra-libraries: c++ c++abi
 
@@ -254,26 +263,27 @@
     ForeignSorting
   hs-source-dirs:
     bench
+  build-tool-depends:
+    , hsc2hs:hsc2hs
   build-depends:
-    , atomic-counter
-    , base >= 4.14
+    , base >= 4.15
     , bytestring
     , deepseq
-    , primitive
     , random
     , stm
-    , tasty
+    , tasty >= 1.5
     , tasty-bench >= 0.3.2
+    , tasty-hunit
     , text
     , text-builder-linear
     , vector
     , vector-algorithms
     , vector-quicksort
-    , vector-quicksort-cpp
-    , vector-quicksort-predefined
+    , vector-quicksort:test-foreign-sort
+    , vector-quicksort:test-predefined-sorts
   ghc-options:
     -fno-spec-constr
     -rtsopts
     -threaded
     "-with-rtsopts=-N -A16m -n1m -qg -qb"
-    -main-is BenchmarkMain
+    -main-is BenchMain
