与astype(int)相比,numpy around / rint slow
所以,如果我有像x=np.random.rand(60000)*400-200
。 iPython的%timeit
说:
-
x.astype(int)
需要0.14ms -
np.rint(x)
和np.around(x)
需要1.01ms
请注意,在rint
和around
情况下,您仍然需要花费额外的0.14ms来做最终的astype(int)
(假设这是你最终想要的)。
问题:我认为大多数现代硬件都能够在同等时间内完成两项操作。 如果是这样的话,为什么numpy需要花费8倍的时间来进行舍入?
碰巧我对算术的准确性并不十分挑剔,但我看不出如何利用numpy(我正在做杂乱的生物学而不是粒子物理学)。
np.around(x).astype(int)
和x.astype(int)
不会产生相同的值。 前者舍入均匀(它与((x*x>=0+0.5) + (x*x<0-0.5)).astype(int)
)相同,而后者向零((x*x>=0+0.5) + (x*x<0-0.5)).astype(int)
。 然而,
y = np.trunc(x).astype(int) z = x.astype(int)
显示y==z
但计算y
要慢得多。 所以这是np.trunc
和np.around
函数很慢。
In [165]: x.dtype Out[165]: dtype('float64') In [168]: y.dtype Out[168]: dtype('int64')
所以np.trunc(x)
从double到double向零np.trunc(x)
入。 然后astype(int)
必须将double转换为int64。
在内部我不知道python或numpy正在做什么,但我知道我将如何在C中执行此操作。让我们讨论一些硬件。 使用SSE4.1,可以使用以下方法执行从double到double的round,floor,ceil和trunc:
_mm_round_pd(a, 0); //round: round even _mm_round_pd(a, 1); //floor: round towards minus infinity _mm_round_pd(a, 2); //ceil: round towards positive infinity _mm_round_pd(a, 3); //trunc: round towards zero
但numpy需要支持没有SSE4.1的系统,所以它必须在没有SSE4.1以及SSE4.1的情况下构建,然后使用调度程序。
但是直到使用SSE / AVX从双直接到int64这样做在AVX512之前效率不高。 但是,只使用SSE2可以有效地将double舍入到int32:
_mm_cvtpd_epi32(a); //round double to int32 then expand to int64 _mm_cvttpd_epi32(a); //trunc double to int32 then expand to int64
这些将两个双精度转换为两个int64。
在你的情况下,这将工作正常,因为范围肯定在int32内。 但除非python知道范围适合int32,否则它不能假设这样,所以它必须舍入或截断到int64,这是缓慢的。 此外,无论如何,numpy必须构建以支持SSE2来执行此操作。
但也许您可以使用单个浮点数组开始。 在那种情况下你可以做到:
_mm_cvtps_epi32(a); //round single to int32 _mm_cvttps_epi32(a) //trunc single to int32
这些将四个单一转换为四个int32。
所以为了回答你的问题,SSE2可以有效地从double到int32舍入或截断。 使用_mm512_cvtpd_epi64(a)
或_mm512_cvttpd_epi64(a)
AVX512也能够有效地从double到int64进行舍入或截断。 SSE4.1可以将float / trunc / floor / ceil从float变为float或者double或double到double。
正如@jme在注释中指出的那样, rint
和around
函数必须确定是否将分数向上或向下舍入到最接近的整数。 相反, astype
函数将始终向下舍入,因此它可以立即丢弃小数信息。 还有许多其他function可以做同样的事情。 此外,您可以通过使用较小的整数位来提高速度。 但是,您必须小心,您可以容纳所有输入数据。
%%timeit np.int8(x) 10000 loops, best of 3: 165 µs per loop
注意,这不会存储-128到127范围之外的值,因为它是8位。 示例中的某些值超出此范围。
在我试过的所有其他np.intc
, np.intc
似乎是最快的:
%%timeit np.int16(x) 10000 loops, best of 3: 186 µs per loop %%timeit np.intc(x) 10000 loops, best of 3: 169 µs per loop %%timeit np.int0(x) 10000 loops, best of 3: 170 µs per loop %%timeit np.int_(x) 10000 loops, best of 3: 188 µs per loop %%timeit np.int32(x) 10000 loops, best of 3: 187 µs per loop %%timeit np.trunc(x) 1000 loops, best of 3: 940 µs per loop
你的例子,在我的机器上:
%%timeit np.around(x) 1000 loops, best of 3: 1.48 ms per loop %%timeit np.rint(x) 1000 loops, best of 3: 1.49 ms per loop %%timeit x.astype(int) 10000 loops, best of 3: 188 µs per loop