- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
为什么我的 SIMD vector4 长度函数比原始 vector 长度方法慢 3 倍?
SIMD vector4 长度函数:
__extern_always_inline float vec4_len(const float *v) {
__m128 vec1 = _mm_load_ps(v);
__m128 xmm1 = _mm_mul_ps(vec1, vec1);
__m128 xmm2 = _mm_hadd_ps(xmm1, xmm1);
__m128 xmm3 = _mm_hadd_ps(xmm2, xmm2);
return sqrtf(_mm_cvtss_f32(xmm3));
}
简单的实现:
sqrtf(V[0] * V[0] + V[1] * V[1] + V[2] * V[2] + V[3] * V[3])
SIMD 版本用了 16110ms 来迭代 1000000000 次。原始版本快了约 3 倍,仅需 4746 毫秒。
#include <math.h>
#include <time.h>
#include <stdint.h>
#include <stdio.h>
#include <x86intrin.h>
static float vec4_len(const float *v) {
__m128 vec1 = _mm_load_ps(v);
__m128 xmm1 = _mm_mul_ps(vec1, vec1);
__m128 xmm2 = _mm_hadd_ps(xmm1, xmm1);
__m128 xmm3 = _mm_hadd_ps(xmm2, xmm2);
return sqrtf(_mm_cvtss_f32(xmm3));
}
int main() {
float A[4] __attribute__((aligned(16))) = {3, 4, 0, 0};
struct timespec t0 = {};
clock_gettime(CLOCK_MONOTONIC, &t0);
double sum_len = 0;
for (uint64_t k = 0; k < 1000000000; ++k) {
A[3] = k;
sum_len += vec4_len(A);
// sum_len += sqrtf(A[0] * A[0] + A[1] * A[1] + A[2] * A[2] + A[3] * A[3]);
}
struct timespec t1 = {};
clock_gettime(CLOCK_MONOTONIC, &t1);
fprintf(stdout, "%f\n", sum_len);
fprintf(stdout, "%ldms\n", (((t1.tv_sec - t0.tv_sec) * 1000000000) + (t1.tv_nsec - t0.tv_nsec)) / 1000000);
return 0;
}
我在 Intel(R) Core(TM) i7-8550U CPU 上运行以下命令。首先使用 vec4_len
版本,然后使用普通 C。
我用 GCC (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 编译:
gcc -Wall -Wextra -O3 -msse -msse3 sse.c -lm && ./a.out
SSE 版本输出:
499999999500000128.000000
13458ms
纯 C 版本输出:
499999999500000128.000000
4441ms
最佳答案
最明显的问题是使用低效的点积(使用 haddps
成本是 2x shuffle uops + 1x add uop)而不是 shuffle + add。参见 Fastest way to do horizontal float vector sum on x86在 _mm_mul_ps
之后该做什么并没有那么糟糕。但这仍然不是 x86 可以非常有效地完成的事情。
但无论如何,真正的问题是您的基准循环。
A[3] = k;
然后使用 _mm_load_ps(A)
创建一个存储转发停顿,如果它天真地编译的话到 vector 洗牌。如果加载仅从单个存储指令加载数据,而没有其他数据,则存储 + 重新加载可以以约 5 个延迟周期有效地转发。否则它必须对整个存储缓冲区进行较慢的扫描以组装字节。这为存储转发增加了大约 10 个周期的延迟。
我不确定这对吞吐量有多大影响,但可能足以阻止无序执行重叠足够多的循环迭代以隐藏延迟和仅在 sqrtss
shuffle 上出现瓶颈吞吐量。
(您的 Coffee Lake CPU 每 3 个周期有 1 个 sqrtss
吞吐量,因此令人惊讶的是 SQRT 吞吐量不是您的瓶颈。1 相反它会是洗牌吞吐量或其他东西。)
参见 Agner Fog's微架构指南和/或优化手册。
此外,通过让编译器提升V[0] * V[0] + V[1] * V[1] + V[2 的计算,你更加偏向于 SSE ] * V[2]
跳出循环。
表达式的那部分是循环不变的,因此编译器只需在每次循环迭代时执行(float)k
平方、加法和标量 sqrt。 (并将其转换为 double
以添加到您的累加器中)。
(@StaceyGirl 已删除的答案指出了这一点;查看其中的内部循环代码是编写此答案的良好开端。)
来自 Kamil's Godbolt link 的 GCC9.1 内部循环看起来很糟糕,并且似乎包含循环存储/重新加载以将新的 A[3]
合并到 8 字节的 A[2..3]
对中,进一步限制了 CPU 重叠多次迭代的能力。
我不确定为什么 gcc 认为这是个好主意。它可能有助于将 vector 加载分成 8 字节的一半的 CPU(如 Pentium M 或 Bobcat)以避免存储转发停顿。但这不是“通用”现代 x86-64 CPU 的明智调整。
.L18:
pxor xmm4, xmm4
mov rdx, QWORD PTR [rsp+8] ; reload A[2..3]
cvtsi2ss xmm4, rbx
mov edx, edx ; truncate RDX to 32-bit
movd eax, xmm4 ; float bit-pattern of (float)k
sal rax, 32
or rdx, rax ; merge the float bit-pattern into A[3]
mov QWORD PTR [rsp+8], rdx ; store A[2..3] again
movaps xmm0, XMMWORD PTR [rsp] ; vector load: store-forwarding stall
mulps xmm0, xmm0
haddps xmm0, xmm0
haddps xmm0, xmm0
ucomiss xmm3, xmm0
movaps xmm1, xmm0
sqrtss xmm1, xmm1
ja .L21 ; call sqrtf to set errno if needed; flags set by ucomiss.
.L17:
add rbx, 1
cvtss2sd xmm1, xmm1
addsd xmm2, xmm1 ; total += (double)sqrtf
cmp rbx, 1000000000
jne .L18 ; }while(k<1000000000);
这种疯狂在标量版本中不存在。
无论哪种方式,gcc 都设法避免了完整的 uint64_t
-> float
转换的低效率(x86 在 AVX512 之前的硬件中没有)。据推测,它能够证明使用有符号的 64 位 -> float 转换将始终有效,因为无法设置高位。
脚注 1:但是 sqrtps
具有与标量相同的每 3 个周期 1 个吞吐量,因此通过执行以下操作您只能获得 CPU sqrt 吞吐量能力的 1/4一次水平处理 1 个 vector ,而不是并行处理 4 个 vector 的 4 个长度。
关于c - 为什么 vector 长度 SIMD 代码比普通 C 慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56623230/
#include using namespace std; class C{ private: int value; public: C(){ value = 0;
这个问题已经有答案了: What is the difference between char a[] = ?string?; and char *p = ?string?;? (8 个回答) 已关闭
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 7 年前。 此帖子已于 8 个月
除了调试之外,是否有任何针对 c、c++ 或 c# 的测试工具,其工作原理类似于将独立函数复制粘贴到某个文本框,然后在其他文本框中输入参数? 最佳答案 也许您会考虑单元测试。我推荐你谷歌测试和谷歌模拟
我想在第二台显示器中移动一个窗口 (HWND)。问题是我尝试了很多方法,例如将分辨率加倍或输入负值,但它永远无法将窗口放在我的第二台显示器上。 关于如何在 C/C++/c# 中执行此操作的任何线索 最
我正在寻找 C/C++/C## 中不同类型 DES 的现有实现。我的运行平台是Windows XP/Vista/7。 我正在尝试编写一个 C# 程序,它将使用 DES 算法进行加密和解密。我需要一些实
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
有没有办法强制将另一个 窗口置于顶部? 不是应用程序的窗口,而是另一个已经在系统上运行的窗口。 (Windows, C/C++/C#) 最佳答案 SetWindowPos(that_window_ha
假设您可以在 C/C++ 或 Csharp 之间做出选择,并且您打算在 Windows 和 Linux 服务器上运行同一服务器的多个实例,那么构建套接字服务器应用程序的最明智选择是什么? 最佳答案 如
你们能告诉我它们之间的区别吗? 顺便问一下,有什么叫C++库或C库的吗? 最佳答案 C++ 标准库 和 C 标准库 是 C++ 和 C 标准定义的库,提供给 C++ 和 C 程序使用。那是那些词的共同
下面的测试代码,我将输出信息放在注释中。我使用的是 gcc 4.8.5 和 Centos 7.2。 #include #include class C { public:
很难说出这里问的是什么。这个问题是含糊的、模糊的、不完整的、过于宽泛的或修辞性的,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开它,visit the help center 。 已关
我的客户将使用名为 annoucement 的结构/类与客户通信。我想我会用 C++ 编写服务器。会有很多不同的类继承annoucement。我的问题是通过网络将这些类发送给客户端 我想也许我应该使用
我在 C# 中有以下函数: public Matrix ConcatDescriptors(IList> descriptors) { int cols = descriptors[0].Co
我有一个项目要编写一个函数来对某些数据执行某些操作。我可以用 C/C++ 编写代码,但我不想与雇主共享该函数的代码。相反,我只想让他有权在他自己的代码中调用该函数。是否可以?我想到了这两种方法 - 在
我使用的是编写糟糕的第 3 方 (C/C++) Api。我从托管代码(C++/CLI)中使用它。有时会出现“访问冲突错误”。这使整个应用程序崩溃。我知道我无法处理这些错误[如果指针访问非法内存位置等,
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 我们不允许提问寻求书籍、工具、软件库等的推荐。您可以编辑问题,以便用事实和引用来回答。 关闭 7 年前。
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于 Stack Overflow 来说是偏离主题的,因为
我有一些 C 代码,将使用 P/Invoke 从 C# 调用。我正在尝试为这个 C 函数定义一个 C# 等效项。 SomeData* DoSomething(); struct SomeData {
这个问题已经有答案了: Why are these constructs using pre and post-increment undefined behavior? (14 个回答) 已关闭 6
我是一名优秀的程序员,十分优秀!