- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
给定一个包含三个(或四个)浮点数的向量。对它们求和的最快方法是什么?
SSE(movaps、shuffle、add、movd)总是比x87快吗? SSE3 中的水平添加指令值得吗?
转移到 FPU 的成本是多少,然后是 faddp、faddp?最快的特定指令序列是什么?
“尝试安排事物以便您一次可以总结四个向量”将不被接受作为答案。 :-) 例如为了对数组求和,您可以使用多个向量累加器进行垂直求和(以隐藏 addps 延迟),并在循环后减少到一个,但是您需要对最后一个向量进行水平求和。
最佳答案
一般来说,对于任何类型的向量水平减少,提取/混洗高半到低,然后垂直添加(或最小/最大/或/和/异或/乘/任何);重复直到剩下一个元素。 如果您从大于 128 位的向量开始,将其缩小一半,直到达到 128(然后您可以在该向量上使用此答案中的函数之一)。除非最后需要将结果广播到所有元素,否则可以考虑一直做全宽shuffle。
宽向量、整数和 FP 的相关问答
__m128
和 __m128d
这个答案(见下文)__m256d
与 Ryzen 1 与 Intel 的性能分析(说明了为什么 vextractf128
比 0x251812231343125141 1325134312513241318 好得多)vperm2f128
Get sum of values stored in __m256d with SSE/AVX__m256
的整数示例,同样在整个数组中匹配,仅在最后进行求和。 (特别值得一提的是,先进行一些 8 位累加,然后扩大 8 -> 64 位以避免溢出,而无需在此时执行完整的 hsum。)_mm256_cmpeq_epi8
32 位元素:这个答案(见下文)。 64 位元素应该是显而易见的:只有一个 pshufd/paddq 步骤。__m128i
8 位无符号元素: Complete AVX+FMA array dot-product example 对 __m128i
,然后对两个 qword 一半(或更宽的向量 4 或 4)求和。 How to count character occurrences using SIMD 显示带有 SSE2 的 128 位。psadbw
有一个 AVX512 示例。 Fastest way to horizontally sum SSE unsigned byte vector 有一个 AVX2 _mm_setzero_si128()
示例。__m256i
使用 set1(1) 作为窄整数的单 uop 加宽水平添加构建块:Summing 8-bit integers in __m512i with AVX intrinsics_mm_madd_epi16
和 __m256i
具有 32 位元素。__m512i
(以及 pd、epi32 和 epi64)。还有reduce_min/max/mul/和/或。手动执行会导致基本相同的 asm。_mm512_reduce_add_ps
以下是一些基于
Fastest method to calculate sum of all packed 32-bit integers using AVX512 or AVX2 的微架构指南和指令表调整的版本。另请参阅
Getting max value in a __m128i vector with SSE? 标签维基。它们在任何 CPU 上都应该是高效的,没有大的瓶颈。 (例如,我避免了会帮助一个 uarch 但对另一个 uarch 缓慢的事情)。代码大小也被最小化。
__m128
习惯用法仅适用于代码大小,不适用于任何现有 CPU 的速度。它有一些用例(如转置和添加,见下文),但单个向量不是其中之一。
hadd
和“垂直”操作开始,以缩减为一个 XMM (
vextractf128
) 向量。一般来说,对于宽向量,最好的办法是反复缩小一半,直到缩小到 128 位向量,无论元素类型如何。 (除了 8 位整数,如果你想在不溢出到更宽元素的情况下进行 hsum,那么第一步是
__m128
。)
vpsadbw
函数的改进。 (
on the Godbolt Compiler Explorer ,以及
Agner Fog's C++ Vector Class Library 上的代码)。我使用 CPP 宏为 SSE2、SSE4 和 AVX 的代码大小选择最佳洗牌,并在 AVX 不可用时避免
horizontal_add
。
movdqa
,因此这在这里非常相关。 haddps
如果它很少使用:它运行时很慢,但这并不经常。只有 2 条指令最大限度地减少了对周围代码(I$ 大小)的影响。haddps
这样在 64 位块中移动数据(在 64 位一半内没有混洗)的混洗也很快。movhlps
(Merom: 1uop) 明显快于 movhlps
(Merom: 3uops)。在 Pentium-M 上,比 shufps
便宜。此外,它在 Core2 上的 FP 域中运行,避免了其他 shuffle 的旁路延迟。 movaps
比 unpcklpd
快。 unpcklps
很慢,pshufd
/pshuflw
很快(因为它们只洗 64 位 91341 的一半) 1122pshufhw
(MMX) 快,pshufb mm0
慢。 pshufb xmm0
非常慢(Merom 和 Pentium M 上为 6uops) haddps
(Merom: 1uop) 很有趣 :这是唯一的 1uop insn 元素。 movshdup
将数据带入整数域,导致绕过延迟将其返回到 shufps
的 FP 执行单元,但 addps
完全在 FP 域中。 movhlps
也在 float 域中运行。shufpd
在整数域中运行,但只有一个 uop。movshdup
的旁路延迟,避免了 shufps
)movhlps
/movaps
指令需要仔细选择shuffle 。只有少数洗牌作为复制和洗牌工作,而不是修改目的地。组合来自两个输入的数据(如 movdqa
或 unpck*
)的混洗可以与不再需要的 tmp 变量一起使用,而不是 movhlps
。// Use dummy = a recently-dead variable that vec depends on,
// so it doesn't introduce a false dependency,
// and the compiler probably still has it in a register
__m128d highhalf_pd(__m128d dummy, __m128d vec) {
#ifdef __AVX__
// With 3-operand AVX instructions, don't create an extra dependency on something we don't need anymore.
(void)dummy;
return _mm_unpackhi_pd(vec, vec);
#else
// Without AVX, we can save a MOVAPS with MOVHLPS into a dead register
__m128 tmp = _mm_castpd_ps(dummy);
__m128d high = _mm_castps_pd(_mm_movehl_ps(tmp, _mm_castpd_ps(vec)));
return high;
#endif
}
float hsum_ps_sse1(__m128 v) { // v = [ D C | B A ]
__m128 shuf = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 3, 0, 1)); // [ C D | A B ]
__m128 sums = _mm_add_ps(v, shuf); // sums = [ D+C C+D | B+A A+B ]
shuf = _mm_movehl_ps(shuf, sums); // [ C D | D+C C+D ] // let the compiler avoid a mov by reusing shuf
sums = _mm_add_ss(sums, shuf);
return _mm_cvtss_f32(sums);
}
# gcc 5.3 -O3: looks optimal
movaps xmm1, xmm0 # I think one movaps is unavoidable, unless we have a 2nd register with known-safe floats in the upper 2 elements
shufps xmm1, xmm0, 177
addps xmm0, xmm1
movhlps xmm1, xmm0 # note the reuse of shuf, avoiding a movaps
addss xmm0, xmm1
# clang 3.7.1 -O3:
movaps xmm1, xmm0
shufps xmm1, xmm1, 177
addps xmm1, xmm0
movaps xmm0, xmm1
shufpd xmm0, xmm0, 1
addss xmm0, xmm1
我报告了一个 github 。它有自己的洗牌内部表示,并将其转回洗牌。 gcc 更经常使用与您使用的内在函数直接匹配的指令。float hsum_ps_sse3(__m128 v) {
__m128 shuf = _mm_movehdup_ps(v); // broadcast elements 3,1 to 2,0
__m128 sums = _mm_add_ps(v, shuf);
shuf = _mm_movehl_ps(shuf, sums); // high half -> low half
sums = _mm_add_ss(sums, shuf);
return _mm_cvtss_f32(sums);
}
# gcc 5.3 -O3: perfectly optimal code
movshdup xmm1, xmm0
addps xmm0, xmm1
movhlps xmm1, xmm0
addss xmm0, xmm1
这有几个优点:_mm_movehl_ps(same,same)
副本来解决破坏性洗牌(没有 AVX): movaps
的目的地是只写的,因此它为 a43412 创建了 0x3118312 的死寄存器这也是我使用 movshdup xmm1, xmm2
而不是 tmp
的原因。movehl_ps(tmp, sums)
是 3 个字节, movehl_ps(sums, sums)
是 4 个字节(与 movhlps
相同)。不需要立即字节,因此对于 AVX,movshdup
是 5 个字节,但 shufps
和 vshufps
都是 4 个字节。vmovhlps
而不是 vmovshdup
保存另一个字节。由于这不会在内部循环内使用,因此切换额外晶体管的额外能量可能可以忽略不计。来自上面 3 个元素的 FP 异常没有风险,因为所有元素都保存有效的 FP 数据。然而,clang/LLVM 实际上“理解”向量混洗,并且如果它知道只有低元素重要的话,它会发出更好的代码。addps
( addss
) 指令就可以解决问题(Paul R 的回答)。这也是最容易输入和记住的。不过,它是 不快 。甚至英特尔 Skylake 仍然将每个 haddps
解码为 3 uop,具有 6 个周期的延迟。因此,即使它节省了机器代码字节(L1 I-cache),它也会在更有值(value)的 uop-cache 中占用更多空间。 _mm_hadd_ps
的实际用例: Do 128bit cross lane operations in AVX512 give better performance? ,或者在中间步骤 clang bug about pessimizing the shuffles 进行一些缩放。#ifdef __AVX__
float hsum256_ps_avx(__m256 v) {
__m128 vlow = _mm256_castps256_ps128(v);
__m128 vhigh = _mm256_extractf128_ps(v, 1); // high 128
vlow = _mm_add_ps(vlow, vhigh); // add the low 128
return hsum_ps_sse3(vlow); // and inline the sse3 version, which is optimal for AVX
// (no wasted instructions, and all of them are the 4B minimum)
}
#endif
vmovaps xmm1,xmm0 # huh, what the heck gcc? Just extract to xmm1
vextractf128 xmm0,ymm0,0x1
vaddps xmm0,xmm1,xmm0
vmovshdup xmm1,xmm0
vaddps xmm0,xmm1,xmm0
vmovhlps xmm1,xmm1,xmm0
vaddss xmm0,xmm0,xmm1
vzeroupper
ret
double hsum_pd_sse2(__m128d vd) { // v = [ B | A ]
__m128 undef = _mm_undefined_ps(); // don't worry, we only use addSD, never touching the garbage bits with an FP add
__m128 shuftmp= _mm_movehl_ps(undef, _mm_castpd_ps(vd)); // there is no movhlpd
__m128d shuf = _mm_castps_pd(shuftmp);
return _mm_cvtsd_f64(_mm_add_sd(vd, shuf));
}
# gcc 5.3.0 -O3
pxor xmm1, xmm1 # hopefully when inlined, gcc could pick a register it knew wouldn't cause a false dep problem, and avoid the zeroing
movhlps xmm1, xmm0
addsd xmm0, xmm1
# clang 3.7.1 -O3 again doesn't use movhlps:
xorpd xmm2, xmm2 # with #define _mm_undefined_ps _mm_setzero_ps
movapd xmm1, xmm0
unpckhpd xmm1, xmm2
addsd xmm1, xmm0
movapd xmm0, xmm1 # another clang bug: wrong choice of operand order
// This doesn't compile the way it's written
double hsum_pd_scalar_sse2(__m128d vd) {
double tmp;
_mm_storeh_pd(&tmp, vd); // store the high half
double lo = _mm_cvtsd_f64(vd); // cast the low half
return lo+tmp;
}
# gcc 5.3 -O3
haddpd xmm0, xmm0 # Lower latency but less throughput than storing to memory
# ICC13
movhpd QWORD PTR [-8+rsp], xmm0 # only needs the store port, not the shuffle unit
addsd xmm0, QWORD PTR [-8+rsp]
存储到内存并返回避免了 ALU uop。如果 shuffle 端口压力或一般的 ALU uops 是瓶颈,那很好。 (请注意,它不需要 haddps
或任何东西,因为 x86-64 SysV ABI 提供了信号处理程序不会踩到的红色区域。)haddps
是一个方便的复制和洗牌。不幸的是,位和字节移位是就地的,atoi()
将目标的高半部分放在结果的低半部分,与 sub rsp, 8
可以将高半部分提取到不同寄存器的方式相反。pshufd
可能会很好,但前提是我们有一个临时注册。 punpckhqdq
是一个安全的选择,Merom 之后的一切都很快。int hsum_epi32_sse2(__m128i x) {
#ifdef __AVX__
__m128i hi64 = _mm_unpackhi_epi64(x, x); // 3-operand non-destructive AVX lets us save a byte without needing a mov
#else
__m128i hi64 = _mm_shuffle_epi32(x, _MM_SHUFFLE(1, 0, 3, 2));
#endif
__m128i sum64 = _mm_add_epi32(hi64, x);
__m128i hi32 = _mm_shufflelo_epi16(sum64, _MM_SHUFFLE(1, 0, 3, 2)); // Swap the low two elements
__m128i sum32 = _mm_add_epi32(sum64, hi32);
return _mm_cvtsi128_si32(sum32); // SSE2 movd
//return _mm_extract_epi32(hl, 0); // SSE4, even though it compiles to movd instead of a literal pextrd r32,xmm,0
}
# gcc 5.3 -O3
pshufd xmm1,xmm0,0x4e
paddd xmm0,xmm1
pshuflw xmm1,xmm0,0x4e
paddd xmm0,xmm1
movd eax,xmm0
int hsum_epi32_ssse3_slow_smallcode(__m128i x){
x = _mm_hadd_epi32(x, x);
x = _mm_hadd_epi32(x, x);
return _mm_cvtsi128_si32(x);
}
在某些 CPU 上,对整数数据使用 FP shuffle 是安全的。我没有这样做,因为在现代 CPU 上最多可以节省 1 或 2 个代码字节,而没有速度提升(代码大小/对齐效果除外)。
关于assembly - 进行水平 SSE 向量求和(或其他缩减)的最快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6996764/
我想用一个向量执行以下操作。 a = np.array(np.arange(0, 4, 1)) 我想得到一个乘法,结果是一个矩阵 | 0 1 2 3 4 -| - - - - - - - 0
正如标题所述,我正在尝试使用 gsub,其中我使用向量作为“模式”和“替换”。目前,我的代码如下所示: names(x1) names(x1) [1] "2110023264A.Ms.Amp
所以当我需要做一些线性代数时,我更容易将向量视为列向量。因此,我更喜欢 (n,1) 这样的形状。 形状 (n,) 和 (n,1) 之间是否存在显着的内存使用差异? 什么是首选方式? 以及如何将 (n,
我不明白为什么 seq() 可以根据元素中是否存在小数点输出不同的类,而 c() 总是创建一个 num向量,无论是否存在小数。 例如: seqDec <- seq(1, 2, 0.5) # num v
机器学习与传统编程的一个重要区别在于机器学习比传统编程涉及了更多的数学知识。不过,随着机器学习的飞速发展,各种框架应运而生,在数据分析等应用中使用机器学习时,使用现成的库和框架成为常态,似乎越来越不需
寻找有关如何将 RegEnable 用作向量的示例/建议。此外,我想控制输入和使能信号成为 Vector 中寄存器索引的函数。 首先,我如何声明 RegEnable() 的 Vector,其次如何迭代
假设我有一个包含变量名称的向量 v1,我想为每个变量分配一个值(存储在单独的向量中)。我如何在没有迭代的情况下做到这一点? v1 <- c("a","b","c") v2 <- c(1,2,3) 我想
R 提供了三种类型来存储同质对象列表:向量、矩阵 和数组。 据我所知: 向量是一维数组的特殊情况 矩阵是二维数组的特例 数组还可以具有任意维度级别(包括 1 和 2)。 在向量上使用一维数组和在矩阵上
我正在绕着numpy/scipy中的所有选项转圈。点积、乘法、matmul、tensordot、einsum 等 我想将一维向量与二维矩阵(这将是稀疏csr)相乘并对结果求和,这样我就有了一个一维向量
我是一个 IDL 用户,正在慢慢切换到 numpy/scipy,并且有一个操作我在 IDL 中非常经常做,但无法用 numpy 重现: IDL> a = [2., 4] IDL> b = [3., 5
在python计算机图形工具包中,有一个vec3类型用于表示三分量向量,但是我如何进行以下乘法: 三分量向量乘以其转置结果得到 3*3 矩阵,如下例所示: a = vec3(1,1,1) matrix
我正在构建一款小型太空射击游戏。当涉及到空间物理学时,我曾经遇到过数学问题。 用文字描述如下:有一个最大速度。因此,如果您全速行驶,您的飞船将在屏幕上一遍又一遍地移动,就像在旧的小行星游戏中一样。如果
我正在尝试在 python 中实现 Vector3 类。如果我用 c++ 或 c# 编写 Vector3 类,我会将 X、Y 和 Z 成员存储为 float ,但在 python 中,我读到鸭式是要走
我是 Spark 和 Scala 的新手,我正在尝试阅读有关 MLlib 的文档。 http://spark.apache.org/docs/1.4.0/mllib-data-types.html上的
我有一个包含四个逻辑向量的数据框, v1 , v2 , v3 , v4 是对还是错。我需要根据 boolean 向量的组合对数据帧的每一行进行分类(例如, "None" , "v1 only" , "
我正在创建一个可视化来说明主成分分析的工作原理,方法是绘制一些实际数据的特征值(为了说明的目的,我将子集化为二维)。 我想要来自 this fantastic PCA tutorial 的这两个图的组
我有以下排序向量: > v [1] -1 0 1 2 4 5 2 3 4 5 7 8 5 6 7 8 10 11 如何在不遍历整个向量的情况下删除 -1、0 和 11
有什么方法可以让 R 对向量和其他序列数据结构使用基于零的索引,例如在 C 和 python 中。 我们有一些代码在 C 中进行一些数值处理,我们正在考虑将其移植到 R 中以利用其先进的统计功能,但是
我有一个函数可以查询我的数据库中最近的 X 个条目,它返回一个 map 向量,如下所示: [{:itemID "item1" :category "stuff" :price 5} {:itemI
我有 ([[AA ww me bl qw 100] [AA ee rr aa aa 100] [AA qq rr aa aa 90]] [[CC ww me bl qw 100] [CC ee rr
我是一名优秀的程序员,十分优秀!