Skip to content

Fix argsort's sorted-input check for a runtime descending flag - #246

Open
Dhruvdesai793 wants to merge 1 commit into
numpy:mainfrom
Dhruvdesai793:issue-240-patch
Open

Dhruvdesai793 wants to merge 1 commit into
numpy:mainfrom
Dhruvdesai793:issue-240-patch

Conversation

@Dhruvdesai793

@Dhruvdesai793 Dhruvdesai793 commented Nov 13, 2026 •

Copy link
Copy Markdown

Fix argsort's sorted-input check for a runtime descending flag

The current code passes a runtime-selected comparator pointer to std::is_sorted, which can mean an indirect call for each comparison. The patch selects between two complete scans. Each scan has a fixed comparator that the compiler can inline.

bool already_sorted = descending
        ? std::is_sorted(arr, arr + arrsize, [](const T &a, const T &b) {
              return Comparator<vectype, true>::STDSortComparator(a, b);
          })
        : std::is_sorted(arr, arr + arrsize, [](const T &a, const T &b) {
              return Comparator<vectype, false>::STDSortComparator(a, b);
          });
if (already_sorted) { return; }

Benchmarks

I built the original and patched headers on my Ryzen AI 7 350 with GCC 16.2.1, -std=c++17 -O3 -march=haswell. An initial single run timed 100 calls on one million already-sorted values with runtime descending:

Version int32 ascending double ascending int32 descending double descending
Original 3.01 ms 2.87 ms 3.04 ms 2.96 ms
This patch 0.52 ms 0.59 ms 0.52 ms 0.53 ms

I also compared literal false with runtime false, using reversed int32_t as a control. This benchmark checks the result, averages 20 calls per run, and alternates build order over three runs. The ranges are:

Input Original, constant Original, runtime Patch, constant Patch, runtime
sorted int32 2.738–3.100 ms 2.815–3.334 ms 0.761–0.783 ms 0.757–0.779 ms
sorted float64 2.770–2.908 ms 2.916–2.981 ms 0.765–0.812 ms 0.764–0.792 ms
reversed int32 35.553–36.276 ms 35.691–36.381 ms 36.045–36.798 ms 36.076–37.832 ms

The sorted-input gain was consistent. I couldn't reproduce the constant-versus-runtime gap from #240 here. These are static API timings, not NumPy timings; the reversed control was close, but slightly slower with the patch.

Benchmark source:

#include "x86simdsort-static-incl.h"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <numeric>
#include <vector>

template<class T, bool Constant>
void run(size_t n, bool reverse_input, bool runtime_descending) {
    std::vector<T> values(n);
    std::iota(values.begin(), values.end(), T{0});
    if (reverse_input) std::reverse(values.begin(), values.end());
    std::vector<size_t> indices(n);
    std::iota(indices.begin(), indices.end(), size_t{0});
    x86simdsortStatic::argsort(values.data(), indices.data(), n, false,
                               runtime_descending, true);
    for (size_t i = 0; i < n; ++i) {
        const size_t expected = reverse_input ? n - 1 - i : i;
        if (indices[i] != expected) {
            std::fprintf(stderr, "wrong argsort result at %zu\n", i);
            std::exit(2);
        }
    }
    constexpr int reps = 20;
    volatile size_t sink = 0;
    auto start = std::chrono::steady_clock::now();
    for (int r = 0; r < reps; ++r) {
        std::iota(indices.begin(), indices.end(), size_t{0});
        if constexpr (Constant)
            x86simdsortStatic::argsort(values.data(), indices.data(), n, false, false, true);
        else
            x86simdsortStatic::argsort(values.data(), indices.data(), n, false, runtime_descending, true);
        sink += indices[r % n];
    }
    auto stop = std::chrono::steady_clock::now();
    auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(stop-start).count();
    std::printf("%s %s %s %.3f ms/call sink=%zu\n", sizeof(T)==4?"int32":"float64", reverse_input?"reversed":"sorted", Constant?"constant":"runtime", double(ns)/reps/1e6, size_t(sink));
}
int main(int argc, char** argv) {
    if (argc != 2) return 2;
    bool direction = std::atoi(argv[1]) != 0;
    if (direction) return 2;
    run<int32_t,true>(1000000,false,direction);
    run<int32_t,false>(1000000,false,direction);
    run<double,true>(1000000,false,direction);
    run<double,false>(1000000,false,direction);
    run<int32_t,true>(1000000,true,direction);
    run<int32_t,false>(1000000,true,direction);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant