【问题标题】:Fused fast conversion from int16 to [-1.0, 1.0] float32 range in NumPyNumPy 中从 int16 到 [-1.0, 1.0] float32 范围的融合快速转换
【发布时间】:2020-08-13 13:44:42
【问题描述】:

我正在寻找 NumPy 中从 int16 到 float32 的最快和最节省内存的转换例程。我的用例是音频样本的转换,因此真实世界的数组很容易在 100K-1M 元素范围内。

我想出了两种方法。 第一种:将int16转换为float32,然后就地除法。这将需要至少两次遍历内存。

第二种:直接使用divide,指定一个在float32中的out-array。从理论上讲,这应该只通过一次内存,因此会更快一些。

我的问题:

  1. 第二种方式是直接用float32除法吗? (我希望它不使用 float64 作为中间 dtype)
  2. 一般来说,有没有办法在指定的 dtype 中进行除法?
  3. 我需要指定一些casting 参数吗?
  4. 关于从 [-1.0, 1.0] float32 转换回 int16 的相同问题

谢谢!

import numpy

a = numpy.array([1,2,3], dtype = 'int16')

# first
b = a.astype(numpy.float32)
c = numpy.divide(b, numpy.float32(32767.0), out = b)

# second
d = numpy.divide(a, numpy.float32(32767.0), dtype = 'float32')

print(c, d)

【问题讨论】:

  • 您不必将整个数组相除,也可以将其乘以 (1/32767),这样会快一点。我还用 numba 进行了尝试(单线程与 numpy 大致相同,多线程版本对于不太小的数组 (1_000_000) 元素和 np.copy(a) 一样快,速度快 60%

标签: performance numpy casting


【解决方案1】:

第二种方式是直接用float32除法吗? (我希望它不要使用 float64 作为中间 dtype)

是的。您可以通过查看代码或更直接地通过扫描硬件事件来检查,这些事件清楚地表明执行了单浮点算术指令(至少在 Numpy 1.18 中)。

一般来说,有没有办法在指定的dtype中进行除法?

AFAIK,不直接使用 Numpy。类型提升规则始终适用。但是,使用 Numba 可以逐个单元格地执行转换,这比使用中间数组更有效(分配和读/写成本很高)。

我需要指定一些强制转换参数吗?

这里不需要,因为在这种情况下不会损失精度。实际上,在第一个版本中,输入操作数的类型为float32 以及结果。对于第二个版本,会自动应用类型提升规则,并且在除法之前将a 隐式转换为float32(可能比第一种方法更有效,因为无法创建中间数组)。 casting 参数可帮助您控制此处的安全级别(默认为 safe):例如,您可以将其转换为 no 以确保不发生强制转换(对于两个操作数和结果,如果需要强制转换,则会引发错误)。更多信息可以查看can_cast的文档。

关于从 [-1.0, 1.0] float32 转换回 int16 的相同问题

类似的答案也适用。但是,您应该关注float32 * int16 -> float32 的类型提升规则。因此,multiply 的结果必须转换为int16,并且出现准确性损失。因此,您可以使用 casting 参数来启用 unsafe 强制转换(现已弃用)并可能获得更好的性能。


注意事项和建议:

我建议你使用 Numba 的 @njit 来高效地执行操作。

请注意,如果使用 SIMD 指令,现代处理器能够非常快速地执行此类操作。因此,内存带宽缓存分配策略应该是两个主要的限制因素。快速转换可以通过预分配缓冲区、避免创建新的临时数组以及避免复制不必要的(大)数组。

【讨论】:

  • 感谢您的详细回复!弄清楚如何查看已执行的程序集也将非常有趣。关于 Numba:NumPy 本身不这样做是 SIMD 吗? (除法+演员)我在神经网络训练的数据加载步骤中这样做,所以缓存缓冲区不是很容易,因为在 PyTorch 中控制数据加载器线程本地缓冲区目前不是很容易,但我同意这是值得的尝试进一步优化
  • 现在我得到了f2s_numpy = lambda signal: np.multiply(signal, np.float32(32767), dtype = 'int16')s2f_numpy = lambda signal: np.divide(signal, np.float32(32767), dtype = 'float32')。任何cmets
  • Numpy do not use SIMD instructions for all operations,但 it does for simple ones 之类的除法(我在我的 PC 上检查过,虽然我的处理器支持 AVX,但它使用了 SSE)。您可以在 Linux 上使用perf 分析 Numpy。 s2f_numpy 看起来不错,但不是 f2s_numpy:我认为您需要使用 np.multiply(signal, np.float32(32767)).astype('int16')(遗憾的是不是很快),因为 dtype='int16' 不被接受并且 casting='unsafe' 给出错误的结果。
  • 感谢您的指正!我为此创建了一个问题:github.com/numpy/numpy/issues/17196
猜你喜欢
  • 2017-07-21
  • 2020-11-02
  • 1970-01-01
  • 2016-03-09
  • 2017-07-28
  • 2016-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多