Kwker

Performance

Kwker is fastest when each call does only the work you need, on data it can use as it is. Five habits cover most programs. Then measure on your own machine: the commands at the end take a few seconds.

Habit Instead of Use
Ask only for what you need sorting everything, then taking a few top_k, select, partial_sort
Sort in place a sorted copy you then assign back sort(a)
One call per matrix a Python loop over rows sort(m, axis=1)
Threads for big arrays one core on millions of values threads=
Reuse buffers in a loop new working memory on every call Plan

Ask only for what you need

The largest few values, a median or the first page of a ranking need much less work than a full sort. top_k returns the k largest or smallest values and their positions; select puts one value of a given rank in place, as numpy.partition does.

PythonRuns on your machine.
import numpy as np
import kwker

scores = np.array([0.2, 0.9, 0.4, 0.7, 0.1, 0.8])
values, positions = kwker.top_k(scores, 3, descending=True)
print(values, positions)

a = np.array([7, 1, 9, 4, 3])
kwker.select(a, 2)        # the median of 5 values: rank 2
print(a[2])
Output
[0.9 0.8 0.7] [1 5 3]
4

Sort in place

sort(a) sorts the array itself, views and strided arrays included, without a copy. sorted(a) makes a new array: use it only when you still need the original order.

PythonRuns on your machine.
import numpy as np
import kwker

a = np.array([3.5, 1.0, 2.25])
kwker.sort(a)             # no new array
print(a)
Output
[1.   2.25 3.5 ]

One call per matrix

To sort every row, pass the whole matrix with axis=1. One call sorts all rows; a Python loop pays the call overhead once per row, which dominates for short rows.

PythonRuns on your machine.
import numpy as np
import kwker

m = np.array([[3, 1, 2],
              [9, 7, 8]])
kwker.sort(m, axis=1)
print(m)
Output
[[1 2 3]
 [7 8 9]]

Threads for big arrays

Calls run on one thread unless you pass threads=. From about a million values, more cores help; below that, one thread is usually fastest. threads=0 uses every CPU the process may use.

PythonRuns on your machine.
import numpy as np
import kwker

big = np.random.default_rng(0).random(2_000_000)
kwker.sort(big, threads=4)
print(bool(np.all(big[:-1] <= big[1:])))
Output
True

Reuse buffers in a loop

Some calls need working memory. Kwker keeps its own buffers for reuse, so most programs need nothing here. In a hot loop of argsort or top_k calls, a Plan holds one set of buffers and your settings (order, threads) for the whole loop: once it has grown, its calls allocate nothing.

PythonRuns on your machine.
import numpy as np
import kwker

rng = np.random.default_rng(1)
with kwker.Plan() as plan:
    for _ in range(3):
        batch = rng.random(10_000)
        order = plan.argsort(batch)
print(bool(np.all(np.diff(batch[order]) >= 0)))
Output
True

How the speed comes about

Runtime controls lists the switches for each of these: the engine cap, the algorithm classes, threads and scratch memory.

Measure on your machine

Command What it tells you
python -m kwker doctor the engine in use and anything that slows Kwker down
python -m kwker.bench --quick Kwker against NumPy, PyTorch, pyarrow and Polars on this CPU
python -m kwker audit -- your_program.py your own program with and without Kwker, and whether the results match

Evaluate on your machine walks through the three. The benchmarks page has the published results against x86-simd-sort, VQSort and the model runtimes, with the raw data and the method.

Notes