Skip to content

np.clip(a, mn, mx) on Windows runs slower than np.clip(np.clip(a, None, mx), mn, None)) #18673

Closed

Description

@pijyoi

On Windows platform, np.clip() on floating point inputs with both bounds provided runs significantly slower than calling np.clip() twice, once with the lower bound, and again with the upper bound.

Reproducing code example:

from time import perf_counter
import numpy as np

def myclip(data, vmin, vmax, out=None):
    out = np.clip(data, None, vmax, out=out)
    return np.clip(out, vmin, None, out=out)

data = np.random.random((1000,1000))
out = np.ones_like(data)
vmin, vmax = 0.1, 0.9

assert np.all(np.clip(data, vmin, vmax) == myclip(data, vmin, vmax))

t0 = perf_counter()
for _ in range(10):
    np.clip(data, vmin, vmax, out=out)
t1 = perf_counter()
print(t1 - t0)

t0 = perf_counter()
for _ in range(10):
    myclip(data, vmin, vmax, out=out)
t1 = perf_counter()
print(t1 - t0)

Error message:

The timing outputs where np.clip is significantly slower than myclip:
0.17425939999999995
0.06601459999999992

NumPy/Python version information:

1.20.1 3.8.8 (tags/v3.8.8:024d805, Feb 19 2021, 13:18:16) [MSC v.1928 64 bit (AMD64)]

Activity

  1. rossbar commented on Mar 24, 2021

    @rossbar
    Contributor

    I can't replicate this behavior, albeit on linux.

    In [9]: %timeit np.clip(data, vmin, vmax, out=out)
    1.33 ms ± 4.83 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    In [10]: %timeit myclip(data, vmin, vmax, out=out)
    3.06 ms ± 30.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
  2. pijyoi commented on Mar 24, 2021

    @pijyoi
    ContributorAuthor

    Maybe I should have specified more clearly that the issue is only for Windows. Yes, on Linux, np.clip is faster, as would be expected.

  3. rossbar commented on Mar 24, 2021

    @rossbar
    Contributor

    No you were plenty clear 👍 , I was just surprised by the platform dependence.

  4. rajpratyush commented on Apr 14, 2021

    @rajpratyush

    how about if we use joblib here ?

  5. pijyoi commented on Oct 19, 2021

    @pijyoi
    ContributorAuthor

    I noticed that the use of _NPY_CLIP macro would result in multiple re-evaluations of the input arguments.
    By avoiding the use of the _NPY_CLIP macro and using a temporary variable, I was able to get np.clip to run faster than myclip in the reproducing example script above. (Windows 10 x64, MSVC 19.28)

    file numpy\core\src\umath\clip.c.src

    NPY_NO_EXPORT void
    @name@_clip(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
    {
        if (steps[1] == 0 && steps[2] == 0) {
            /* min and max are constant throughout the loop, the most common case */
            /* NOTE: it may be possible to optimize these checks for nan */
            @type@ min_val = *(@type@ *)args[1];
            @type@ max_val = *(@type@ *)args[2];
    
            char *ip1 = args[0], *op1 = args[3];
            npy_intp is1 = steps[0], os1 = steps[3];
            npy_intp n = dimensions[0];
    
            /* contiguous, branch to let the compiler optimize */
            if (is1 == sizeof(@type@) && os1 == sizeof(@type@)) {
                for(npy_intp i = 0; i < n; i++, ip1 += is1, op1 += os1) {
                    // *(@type@ *)op1 = _NPY_CLIP(*(@type@ *)ip1, min_val, max_val);
                    @type@ t = *(@type@ *)ip1;
                    t = _NPY_@name@_MAX(t, min_val);
                    t = _NPY_@name@_MIN(t, max_val);
                    *(@type@ *)op1 = t;
                }
            }
            else {
                for(npy_intp i = 0; i < n; i++, ip1 += is1, op1 += os1) {
                    // *(@type@ *)op1 = _NPY_CLIP(*(@type@ *)ip1, min_val, max_val);
                    @type@ t = *(@type@ *)ip1;
                    t = _NPY_@name@_MAX(t, min_val);
                    t = _NPY_@name@_MIN(t, max_val);
                    *(@type@ *)op1 = t;
                }
            }
        }
        else {
            TERNARY_LOOP {
                *(@type@ *)op1 = _NPY_CLIP(*(@type@ *)ip1, *(@type@ *)ip2, *(@type@ *)ip3);
            }
        }
        npy_clear_floatstatus_barrier((char*)dimensions);
    }
  6. added a commit that references this issue on Oct 19, 2021
  7. pijyoi commented on Oct 22, 2021

    @pijyoi
    ContributorAuthor

    fixed by #20134

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions