- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
请不要说这是过早的微优化。鉴于我有限的知识,我想尽可能多地了解所描述的 SB 功能和程序集的工作原理,并确保我的代码使用此架构功能。感谢您的理解。
几天前我开始学习内在函数,所以答案对某些人来说似乎很明显,但我没有可靠的信息来源来弄清楚这个问题。
我需要为 Sandy Bridge CPU 优化一些代码(这是一项要求)。现在我知道它可以在每个周期执行一次 AVX 乘法和一次 AVX 加法,并阅读这篇论文:
http://research.colfaxinternational.com/file.axd?file=2012%2F7%2FColfax_CPI.pdf
它展示了如何在 C++ 中完成。所以,问题是我的代码不会使用 Intel 的编译器自动矢量化(这是任务的另一个要求),所以我决定使用像这样的内部函数手动实现它:
__sum1 = _mm256_setzero_pd();
__sum2 = _mm256_setzero_pd();
__sum3 = _mm256_setzero_pd();
sum = 0;
for(kk = k; kk < k + BS && kk < aW; kk+=12)
{
const double *a_addr = &A[i * aW + kk];
const double *b_addr = &newB[jj * aW + kk];
__aa1 = _mm256_load_pd((a_addr));
__bb1 = _mm256_load_pd((b_addr));
__sum1 = _mm256_add_pd(__sum1, _mm256_mul_pd(__aa1, __bb1));
__aa2 = _mm256_load_pd((a_addr + 4));
__bb2 = _mm256_load_pd((b_addr + 4));
__sum2 = _mm256_add_pd(__sum2, _mm256_mul_pd(__aa2, __bb2));
__aa3 = _mm256_load_pd((a_addr + 8));
__bb3 = _mm256_load_pd((b_addr + 8));
__sum3 = _mm256_add_pd(__sum3, _mm256_mul_pd(__aa3, __bb3));
}
__sum1 = _mm256_add_pd(__sum1, _mm256_add_pd(__sum2, __sum3));
_mm256_store_pd(&vsum[0], __sum1);
这里解释了我手动展开循环的原因:
Loop unrolling to achieve maximum throughput with Ivy Bridge and Haswell
他们说您需要展开 3 倍才能在桑迪上获得最佳性能。我的天真测试证实这确实比没有展开或 4 倍展开运行得更好。
好的,问题来了。来自 Intel Parallel Studio 15 的 icl 编译器生成这个:
$LN149:
movsxd r14, r14d ;78.49
$LN150:
vmovupd ymm3, YMMWORD PTR [r11+r14*8] ;80.48
$LN151:
vmovupd ymm5, YMMWORD PTR [32+r11+r14*8] ;84.49
$LN152:
vmulpd ymm4, ymm3, YMMWORD PTR [r8+r14*8] ;82.56
$LN153:
vmovupd ymm3, YMMWORD PTR [64+r11+r14*8] ;88.49
$LN154:
vmulpd ymm15, ymm5, YMMWORD PTR [32+r8+r14*8] ;86.56
$LN155:
vaddpd ymm2, ymm2, ymm4 ;82.34
$LN156:
vmulpd ymm4, ymm3, YMMWORD PTR [64+r8+r14*8] ;90.56
$LN157:
vaddpd ymm0, ymm0, ymm15 ;86.34
$LN158:
vaddpd ymm1, ymm1, ymm4 ;90.34
$LN159:
add r14d, 12 ;76.57
$LN160:
cmp r14d, ebx ;76.42
$LN161:
jb .B1.19 ; Prob 82% ;76.42
对我来说,这看起来一团糟,正确的顺序(使用方便的 SB 功能所需的乘法加法)被打破了。
问题:
此汇编代码是否会利用我所指的 Sandy Bridge 功能?
如果不是,我需要做什么才能利用该功能并防止代码像这样“纠结”?
此外,当只有一个循环迭代时,顺序非常干净,即加载、乘法、加法,这是应该的。
最佳答案
对于 x86 CPU,许多人期望从点积中获得最大的 FLOPS
for(int i=0; i<n; i++) sum += a[i]*b[i];
但结果是 not to be the case .
能给出最大FLOPS的是这个
for(int i=0; i<n; i++) sum += k*a[i];
其中 k
是一个常量。为什么 CPU 没有针对点积进行优化?我可以推测。 CPU 优化的其中一项是 BLAS . BLAS 正在考虑许多其他例程的构建 block 。
随着 n
的增加,Level-1 和 Level-2 BLAS 例程成为内存带宽限制。只有 Level-3 例程(例如矩阵乘法)能够受计算限制。这是因为 Level-3 计算为 n^3
,而读取为 n^2
。因此,CPU 针对 Level-3 例程进行了优化。 Level-3 例程不需要针对单个点积进行优化。他们每次迭代只需要从一个矩阵中读取 (sum += k*a[i]
)。
由此我们可以得出结论,为获得 Level-3 例程的最大 FLOPS,每个周期需要读取的位数是
read_size = SIMD_WIDTH * num_MAC
其中 num_MAC 是每个周期可以完成的乘法累加运算的数量。
SIMD_WIDTH (bits) num_MAC read_size (bits) ports used
Nehalem 128 1 128 128-bits on port 2
Sandy Bridge 256 1 256 128-bits port 2 and 3
Haswell 256 2 512 256-bits port 2 and 3
Skylake 512 2 1024 ?
对于 Nehalem-Haswell,这符合硬件的能力。我实际上并不知道 Skylake 将能够在每个时钟周期读取 1024 位,但如果它不能,AVX512 就不会很有趣,所以我对我的猜测充满信心。可以在 http://www.anandtech.com/show/6355/intels-haswell-architecture/8 找到每个港口的 Nahalem、Sandy Bridge 和 Haswell 的漂亮地 block 。
到目前为止,我已经忽略了延迟和依赖链。要真正获得最大 FLOPS,您需要在 Sandy Bridge 上至少展开循环 3 次(我使用 4 次是因为我发现使用 3 的倍数不方便)
回答有关性能问题的最佳方法是找到您期望的理论最佳操作性能,然后比较您的代码与该性能的接近程度。我称之为效率。这样做你会发现,尽管你在程序集中看到的指令重新排序,但性能仍然很好。但是您可能需要考虑许多其他微妙的问题。以下是我遇到的三个问题:
l1-memory-bandwidth-50-drop-in-efficiency-using-addresses-which-differ-by-4096 .
obtaining-peak-bandwidth-on-haswell-in-the-l1-cache-only-getting-62%
difference-in-performance-between-msvc-and-gcc-for-highly-optimized-matrix-multp .
我还建议您考虑使用 IACA研究性能。
关于c - 当编译器对 Sandy 上的 AVX 指令重新排序时,它会影响性能吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27769834/
SQLite、Content provider 和 Shared Preference 之间的所有已知区别。 但我想知道什么时候需要根据情况使用 SQLite 或 Content Provider 或
警告:我正在使用一个我无法完全控制的后端,所以我正在努力解决 Backbone 中的一些注意事项,这些注意事项可能在其他地方更好地解决......不幸的是,我别无选择,只能在这里处理它们! 所以,我的
我一整天都在挣扎。我的预输入搜索表达式与远程 json 数据完美配合。但是当我尝试使用相同的 json 数据作为预取数据时,建议为空。点击第一个标志后,我收到预定义消息“无法找到任何内容...”,结果
我正在制作一个模拟 NHL 选秀彩票的程序,其中屏幕右侧应该有一个 JTextField,并且在左侧绘制弹跳的选秀球。我创建了一个名为 Ball 的类,它实现了 Runnable,并在我的主 Draf
这个问题已经有答案了: How can I calculate a time span in Java and format the output? (18 个回答) 已关闭 9 年前。 这是我的代码
我有一个 ASP.NET Web API 应用程序在我的本地 IIS 实例上运行。 Web 应用程序配置有 CORS。我调用的 Web API 方法类似于: [POST("/API/{foo}/{ba
我将用户输入的时间和日期作为: DatePicker dp = (DatePicker) findViewById(R.id.datePicker); TimePicker tp = (TimePic
放宽“邻居”的标准是否足够,或者是否有其他标准行动可以采取? 最佳答案 如果所有相邻解决方案都是 Tabu,则听起来您的 Tabu 列表的大小太长或您的释放策略太严格。一个好的 Tabu 列表长度是
我正在阅读来自 cppreference 的代码示例: #include #include #include #include template void print_queue(T& q)
我快疯了,我试图理解工具提示的行为,但没有成功。 1. 第一个问题是当我尝试通过插件(按钮 1)在点击事件中使用它时 -> 如果您转到 Fiddle,您会在“内容”内看到该函数' 每次点击都会调用该属
我在功能组件中有以下代码: const [ folder, setFolder ] = useState([]); const folderData = useContext(FolderContex
我在使用预签名网址和 AFNetworking 3.0 从 S3 获取图像时遇到问题。我可以使用 NSMutableURLRequest 和 NSURLSession 获取图像,但是当我使用 AFHT
我正在使用 Oracle ojdbc 12 和 Java 8 处理 Oracle UCP 管理器的问题。当 UCP 池启动失败时,我希望关闭它创建的连接。 当池初始化期间遇到 ORA-02391:超过
关闭。此题需要details or clarity 。目前不接受答案。 想要改进这个问题吗?通过 editing this post 添加详细信息并澄清问题. 已关闭 9 年前。 Improve
引用这个plunker: https://plnkr.co/edit/GWsbdDWVvBYNMqyxzlLY?p=preview 我在 styles.css 文件和 src/app.ts 文件中指定
为什么我的条形这么细?我尝试将宽度设置为 1,它们变得非常厚。我不知道还能尝试什么。默认厚度为 0.8,这是应该的样子吗? import matplotlib.pyplot as plt import
当我编写时,查询按预期执行: SELECT id, day2.count - day1.count AS diff FROM day1 NATURAL JOIN day2; 但我真正想要的是右连接。当
我有以下时间数据: 0 08/01/16 13:07:46,335437 1 18/02/16 08:40:40,565575 2 14/01/16 22:2
一些背景知识 -我的 NodeJS 服务器在端口 3001 上运行,我的 React 应用程序在端口 3000 上运行。我在 React 应用程序 package.json 中设置了一个代理来代理对端
我面临着一个愚蠢的问题。我试图在我的 Angular 应用程序中延迟加载我的图像,我已经尝试过这个2: 但是他们都设置了 src attr 而不是 data-src,我在这里遗漏了什么吗?保留 d
我是一名优秀的程序员,十分优秀!