c++ - 为什么使用 Eigen 密集动态矩阵的 setZero 比静态矩阵更快?
问题描述
我编译了以下代码进行测试
#include <iostream>
#include <Eigen/Dense>
#include <chrono>
int main()
{
constexpr size_t t = 10000;
constexpr size_t size = 100;
auto t1 = std::chrono::steady_clock::now();
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> m1;
for (size_t i = 0; i < t; ++i)
{
m1.setZero(size, size);
}
auto t2 = std::chrono::steady_clock::now();
Eigen::Matrix<double, size, size> m2;
for (size_t i = 0; i < t; ++i)
{
m2.setZero();
}
auto t3 = std::chrono::steady_clock::now();
double t12 = static_cast<std::chrono::duration<double>>(t2 - t1).count();
double t23 = static_cast<std::chrono::duration<double>>(t3 - t2).count();
std::cout << "dynamic: " << t12 << " static: " << t23 << std::endl;
return 0;
}
我发现动态矩阵总是比静态矩阵快
编译-O0
dynamic: 2.34759 static: 4.29692
编译-O3
dynamic: 0.0170274 static: 0.0363988
直观地说,动态矩阵不会setZero
分配导致内存分配开销的新内存吗?
解决方案
TL;DR:性能结果强烈依赖于目标平台和编译器。
首先,这种说法并不总是正确的。实际上,动态版本在我的机器上更快,因为我得到了结果dynamic: 0.128093 static: 0.142624
(使用-std=c++20 -O3 -DNDEBUG
和 GCC 10.2.1)。原因是 GCC/Clangmemset
在动态版本中生成代码调用,而它们在静态情况下生成许多直接归零SIMD 指令(更具体地说是 128 位 SSE 指令)。在某些情况下,memset
实现可以比主流编译器(例如 GCC 和 Clang)生成的代码更快。
实际上,memset
如果 libc 针对目标机器进行了优化,而 GCC 可能不使用它,则可以使用更广泛的 SIMD 指令。这可能是 x86-64 平台的情况,其中 AVX-256 和 AVX-512 可用,但主流编译器不会自动启用,因为在所有 x86-64 平台上生成的二进制文件都需要向后兼容。当启用平台上可用的最先进的 SIMD 指令集时,静态版本的结果往往要好得多,但这并不总是如此。在我的机器上,我得到dynamic: 0.105638 static: 0.0929698
了-march=native
添加。
更深入的分析表明,我的 libc (GNU libc 2.31) 没有直接使用 SIMD 指令,而是使用rep stos
指令。现代处理器可以优化这条指令并执行比 1 字节宽得多的存储。但是,它也有很大的启动开销。有关此指令及其性能的更多信息,请参阅这篇非常详细的帖子。
其他参数对于比较这两种实现很重要:CPU 缓存的大小/种类、RAM 的速度以及矩阵大小。实际上,如果矩阵不适合 CPU 缓存,那么由于非临时存储(NTS) ,memset
实现通常会更快。这在使用写入分配缓存的处理器(如英特尔处理器)上尤其如此,因为这样的处理器将在没有 NTS 指令的情况下从 RAM 中读取矩阵,然后再将零写入其中(这个过程比使用 NTS 直接写入内存要慢 2 倍指示)。
问题是 GCC/Clang 假设静态矩阵足够小,因此它可能适合 CPU 缓存(并且可能在缓存中,因为矩阵实际上应该存储在堆栈中),因此在大量展开的循环可能比使用更快memset
,后者使用多个版本并根据数据大小和目标平台选择一个好的版本。希望这通常是正确的,因此您获得的性能结果。请注意,当大小是动态的,因为它在编译时是未知的,所以很难做出这个假设(没有配置文件引导的优化)。
请注意,其他参数可能很重要,例如执行顺序(因为频率缩放)和内存对齐(尽管它们在我的机器上似乎并不重要)。
推荐阅读
- mysql - Mysql [5.6] Innodb select query order by id asc limit 100 比 desc 慢很多
- php - Php - 从文件中读取和匹配字符串
- vb.net - 异步、等待 EventHandler 返回值
- python - 如何在 For 循环 Python 中通过字典中的键获取值
- java - Gradle:下载 gradle 需要“很长时间”,activity_main.xml 一直显示“正在加载”
- python - 有没有一种有效的方法可以将 RGB 图像中的主要颜色分箱?
- sql-server - 函数与存储过程作为外部名称属性
- javascript - 如何使用 socket.io 接收多个发出的参数
- java - 如何让程序显示吐司而不是崩溃?
- kubernetes - 如何在部署中运行命令?