ReLU 阈值处理中 numpy.ceil 和 numpy.clip 的缓慢:瓶颈在哪里?

Slowness of numpy.ceil and numpy.clip in ReLU thresholding: where is the bottleneck?

这是一个基于(或跟进)另一个问题的问题:

本着想出一种最快计算导数的方法的精神,我写了一些解决方案,其中一个是:

In [35]: np.random.seed(0)       
In [36]: X = np.random.randn(3072,10000) 

# computing ReLU derivative
In [42]: np.ceil(np.clip(X, 0, 1))

在将其与 进行基准测试时,我发现上述方法非常慢(在 30x 以北)。以下是时间安排(从最快到最慢)

In [43]: %timeit -n100 ne.evaluate('X>=0').view('i1')  
10.6 ms ± 203 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [44]: %timeit -n100 (X>=0).view('i1')
13.6 ms ± 77.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [45]: %timeit -n100 ne.evaluate('(X>=0)+0') 
22.1 ms ± 16.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# the super slowest one
In [46]: %timeit -n100 np.ceil(np.clip(X, 0, 1)) 
317 ms ± 2.14 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

导致这种缓慢的 is/are 因素是什么?瓶颈在哪里?

首先,您只是在执行一系列更复杂的操作。对于每个输入,您的 ceil/clip 执行以下操作:

  • 输入值是否小于0?如果是,则将中间值设置为 0。
  • 否则,是否大于1?如果是,则将中间值设置为 1。
  • 否则,将中间值设置为输入值。
  • 计算中间值的上限并将输出设置为该值。

(这发生在两个阶段,一个阶段完成所有剪裁,一个阶段完成所有天花板。)

您正在根据对每个输入执行以下操作的选项来计时:

  • 在输入和 0 之间执行 >= 比较并将输出设置为该值。

>= 更快也就不足为奇了。


其次,您的 ceil/clip 写入的字节数是 >= 的 16 倍。 >= 为每个输入元素生成一个输出字节(view 是一个视图,因此那里没有数据复制),而你的 ceil/clip 生成一个中间数组和一个输出数组,两者都是 dtype float64.


第三,分支预测器在随机数组上的 clip 表现不佳。它不知道每次会选择哪个分支。一个更可预测的数组通过 clip 的速度要快得多:

In [21]: %timeit X.clip(0, 1)
1 loop, best of 5: 211 ms per loop

In [22]: A = np.full_like(X, 0.5)

In [23]: %timeit A.clip(0, 1)
10 loops, best of 5: 86.6 ms per loop

最后,至少在我测试的机器和 NumPy 构建上,numpy.ceil 出奇地慢:

In [24]: %timeit np.ceil(X)
10 loops, best of 5: 166 ms per loop

我不确定它是在攻击软件 ceil 实现还是什么。这在不同的版本上可能会有所不同。