比方说,我有一些16字节对齐的结构,只包装3xFloat32数组:
#[repr(C, align(16))]
pub struct Vector(pub [f32; 3]);
现在,我想将其划分为两个实例,例如:
use core::arch::x86_64;
let a = Vector([1f32, 2f32, 3f32]);
let b = Vector([4f32, 5f32, 6f32]);
let mut sum = Vector([0f32, 0f32, 0ff32]);
unsafe {
let a1 = x86_64::_mm_load_ps(a.0.as_ptr());
let b1 = x86_64::_mm_load_ps(b.0.as_ptr());
let sum1 = x86_64::_mm_div_ps(a1, b1);
x86_64::_mm_store_ps(sum.0.as_mut_ptr(), sum);
}
它可以进行除法,但是存在一个问题:第4个元素包含垃圾,除其他外,这可能表示NaN。并且,如果未屏蔽某些例外标志,则将触发SIGFPE。我想以某种方式避免这种情况,而不会完全沉默信号。即我或者只想在第4对元素上使其静音,或者在其中添加一些合理的值。最好,最快的方法是什么?还是总体上有更好的方法?
[通常,没有人会掩盖FP异常,否则您需要改组,例如复制元素之一,因此顶部元素与其他元素之一进行相同的划分。或还有其他一些已知的安全物品。
也许,如果您可以假设该元素中的分红不是NaN,那么也许只需要对除数进行改组就可以摆脱。
使用AVX512,您可以使用零掩码抑制元素的异常,但是直到那时为止,还没有这样的功能。此外,AVX512还允许您覆盖舍入模式+不遮盖所有异常(SAE),因此您可以使最接近偶数显式获得SAE。但这会抑制all元素的异常。
严重,请勿启用FP例外。如果异常的数量是明显的副作用,编译器几乎/不知道如何以安全的方式进行优化。例如GCC的-ftrapping-math
默认为打开状态,但已损坏。
我不会认为LLVM会更好;默认的严格FP可能仍会进行优化,使一个SIGFPE的源将提高2或4。也许甚至将其提高0的源也将提高1,或者反之亦然,例如GCC破损且几乎无用的默认设置。] >
但是,如果您希望永远都没有某种异常,启用FP异常对于调试可能会很有用。但是您可以通过忽略具有该源地址的SIMD指令来处理偶尔出现的误报。
如果在性能和异常正确性之间进行权衡,则大多数库用户宁愿使性能最大化。
即使清除然后再用fenv
填充物检查粘滞的FP掩蔽标志,也很少,并且需要在受控的情况下使用。我对库函数调用没有任何期望,尤其是没有使用任何SIMD的期望。
如果MXCSR没有设置FTZ和DAZ,则您可以从次常态(也称为非常态)中放慢速度。 (即正常情况,除非您使用-ffast-math
的Rust等效符号进行编译。)对于具有SSE / AVX指令的典型x86硬件,
产生NaN或+ -Inf不需要花费额外的时间
。 (有趣的事实:NaN也很慢,即使在现代硬件上也具有x87数学功能)。因此,例如,在进行数学运算之前,以_mm_or_ps
结果为cmpps
是安全的,可以在向量的某些元素中创建NAN。或使用_mm_and_ps
在除数之前在除数中创建一些零。但是要注意填充中的垃圾是什么,因为它可能导致虚假的次正常状态。
0.0
和NaN(全都是)通常都是安全的。通常仅使用SIMD向量的4个元素中的3个是个坏主意,因为这通常意味着您使用的是单个SIMD向量来保存单个几何向量,而不是3个向量为4的向量x
坐标,4个y
坐标和4个z
坐标。
随机播放/水平填充主要花费额外的指令(内存中已存储的标量的广播负载除外),但是如果您以这种方式使用SIMD,通常会需要大量随机播放。在某些情况下,您无法对一系列事物进行矢量化处理,但仍可以通过SIMD加快速度。
[像在C语言中一样,在Rust中,sizeof
始终是alignof
的倍数:这是必须的,因为sizeof
用作数组中的stride