Repository: numpy/numpy
File: numpy/_core/src/npysort/binsearch.cpp (C++)

Code before:

    To batch multiple queries, we do one pivot pass for each different length
    for all keys, storing intermediate values of base_j for every key_j. To
    avoid consuming extra memory, we use the ret array to store intermediate
    values of each base until they become the final result in the last step.

    There are two benefits of this approach:

    1. Cache locallity of pivots. In early iterations each key is compared
    against the same set of pivots. For example, in the first iteration all
    keys are compared against the median. In the second iteration, all keys
    end up being compared against 1st and 3rd quartiles.

    2. Independent calculations for out-of-order execution. In the single-key
    version, step i+1 depends on computation of step i. Meaning that step i+1
    must wait for step i to complete before proceeding. When batching multiple
    keys, we compute each step for all keys before continuing on the next
    step. All the computations at a given step are independent across
    different keys. Meaning that the CPU can execute multiple keys
    out-of-order in parallel.

    Invariant:
    - cmp(arr[i], key_val) == true  for all i < base
    - cmp(arr[i], key_val) == false for all i >= base + length

    The insertion index candidates are i in range [base, base+length] and
    on each iteration we shrink the range into either
        [base, ceil(length / 2)]
    or
        [base + floor(length / 2), ceil(length / 2)]

    Optimization: we unroll the first iteration for the following reasons:
        1. ret is not initialized with the bases, so we save |keys| writes
        by not having to intialize it with 0s.
        2. By assuming the initial base for every key is 0, we also save
        |keys| reads.
        3. In the first iteration, all elements are compared against the
        median. So we can store it in a variable and use it for all keys.

    This initial block replaces the initialization loop that is used for the
    arr_len==0 case. Note that when arr_len = 1, then half is 0 so the
    following block initializes the array as with 0s.
    */
    npy_intp interval_length = arr_len;
    npy_intp half = interval_length >> 1;
    interval_length -= half; // length -> ceil(length / 2)

    npy_intp base = 0;
    const T mid_val = *(const T *)(arr + (base + half) * arr_str);

    for (npy_intp i = 0; i < key_len; ++i) {

Review comment (suggestion):
```suggestion
    The insertion index candidates are in range [base, base+length] and
```

Code after:

    To batch multiple queries, we do one pivot pass for each different length
    for all keys, storing intermediate values of base_j for every key_j. To
    avoid consuming extra memory, we use the ret array to store intermediate
    values of each base until they become the final result in the last step.

    There are two benefits of this approach:

    1. Cache locallity of pivots. In early iterations each key is compared
    against the same set of pivots. For example, in the first iteration all
    keys are compared against the median. In the second iteration, all keys
    end up being compared against 1st and 3rd quartiles.

    2. Independent calculations for out-of-order execution. In the single-key
    version, step i+1 depends on computation of step i. Meaning that step i+1
    must wait for step i to complete before proceeding. When batching multiple
    keys, we compute each step for all keys before continuing on the next
    step. All the computations at a given step are independent across
    different keys. Meaning that the CPU can execute multiple keys
    out-of-order in parallel.

    Invariant:
    - cmp(arr[i], key_val) == true  for all i < base
    - cmp(arr[i], key_val) == false for all i >= base + length

    The insertion index candidates are in range [base, base+length] and
    on each iteration we shrink the range into either
        [base, ceil(length / 2)]
    or
        [base + floor(length / 2), ceil(length / 2)]

    Optimization: we unroll the first iteration for the following reasons:
        1. ret is not initialized with the bases, so we save |keys| writes
        by not having to intialize it with 0s.
        2. By assuming the initial base for every key is 0, we also save
        |keys| reads.
        3. In the first iteration, all elements are compared against the
        median. So we can store it in a variable and use it for all keys.

    This initial block replaces the initialization loop that is used for the
    arr_len==0 case. Note that when arr_len = 1, then half is 0 so the
    following block initializes the array as with 0s.
    */
    npy_intp interval_length = arr_len;
    npy_intp half = interval_length >> 1;
    interval_length -= half; // length -> ceil(length / 2)

    npy_intp base = 0;
    const T mid_val = *(const T *)(arr + (base + half) * arr_str);

    for (npy_intp i = 0; i < key_len; ++i) {