c++中的快速内存访问
Fast memory access in C++?
在开发一款基于c++的快速内存访问游戏时我应该考虑什么?
我加载的内存是静态的,所以我应该把它放在一个连续的内存块中,对吗?
另外,我应该如何组织结构内部的变量来提高性能?
内存性能非常模糊。
我认为你正在寻找的是关于处理CPU缓存,因为在缓存中访问和在主存中访问之间有大约10个因子。
关于缓存背后机制的完整参考,您可能希望阅读lwn.net上Ulrich Drepper的这一系列优秀文章。
简而言之:瞄准位置
你不应该在内存中跳来跳去,所以(如果可能的话)尽量把要一起使用的项目组合在一起。
以可预测性为目标
如果您的内存访问是可预测的,那么CPU可能会为下一个工作块预取内存,以便在完成当前块后立即或很快可用。
典型的例子是数组上的for
循环:
for (int i = 0; i != MAX; ++i)
for (int j = 0; j != MAX; ++j)
array[i][j] += 1;
用array[j][i] += 1;
改变array[i][j] += 1;
,性能变化…在低优化水平;)
编译器应该捕捉到那些明显的情况,但有些情况更隐蔽。例如,使用基于节点的容器(链表,二叉搜索树)而不是基于数组的容器(向量,一些哈希表)可能会降低应用程序的速度。
不要浪费空间…谨防虚假分享
尝试打包你的结构。这与对齐有关,您可能会由于结构中的对齐问题而浪费空间,这会人为地增加结构大小并浪费缓存空间。
典型的经验法则是通过减小尺寸来排列结构中的项(使用sizeof
)。这是愚蠢的,但效果很好。如果你对大小和对齐有更多的了解,只要避免洞:)注意:只对有很多实例的结构有用…
但是,要小心虚假共享。在多线程程序中,并发访问两个足够接近的变量来共享同一条缓存线是昂贵的,因为它涉及大量的缓存无效和CPU争夺缓存线的所有权。
概要文件
不幸的是,这是很难弄清楚。
如果你碰巧在Unix上编程,Callgrind
(Valgrind套件的一部分)可以与缓存模拟一起运行,并识别触发缓存失败的代码部分。
我想还有其他的工具,我只是从来没用过。
你不在乎。这样的事情很可能是最小性质的微优化。首先让它工作,如果它太慢了,然后找出哪些部分是慢的,并优化那些(提示:它可能是你调用库等的方式,而不是内存访问)。
我同意前面的说法。你应该先写好你的游戏,然后找出时间应该花在哪里,并尝试着去完善它。
然而,本着提供一些可能有用的[可能分散对实际问题的注意力:-)]建议的精神,您可能会发现一些常见的陷阱:
-
函数指针和虚拟方法提供了很大的设计灵活性,但是如果它们被非常非常频繁地使用,你会发现它们比内联的东西要慢。这主要是因为CPU很难通过函数指针对调用执行分支预测。在c++中,一个很好的缓解方法是使用模板,它可以在编译时为您提供类似的设计灵活性。
这种方法的一个潜在的缺点是内联会增加代码的大小。好消息是,您的编译器可以决定是否内联,并且它可能比您做出更好的决定。在许多情况下,你的优化器知道你特定的CPU架构,并可以做出一些适当的猜测。 -
在频繁访问的数据结构中避免间接访问。
例如:
struct Foo
{
// [snip] other members here...
Bar *barObject; // pointer to another allocation owned by Foo structure
};
有时创建的内存布局效率不如下面的:
struct Foo
{
// [snip] other members here...
Bar barObject; // object is a part of Foo, with no indirection
};
这可能听起来很傻,在大多数情况下你不会注意到任何区别。但总的想法是,避免"不必要的间接"是件好事。不要特意去做这件事,但这是要记住的。
这种方法的一个潜在缺点是它可能使您的Foo
对象不再整齐地适合缓存…
沿着前面两个项目的线…在c++中,STL容器和算法可以生成一些非常高效的目标代码。在
<algorithm>
的情况下,传递给各种算法的函子可以很容易地内联,帮助您避免不必要的指针调用,同时仍然允许通用例程。在容器的情况下,STL可以适当地在列表节点等内部声明类型参数T
的对象,这有助于避免数据结构中不必要的间接。是的,内存访问可以产生影响…一个例子是在大图像中的像素之间循环。如果一次处理一列图像,可能比一次处理一行图像更糟糕。在最常见的图像格式中,(x, y)处的像素通常与(x +1, y)处的像素相邻,而(x, y)处的像素通常距离(x, y+1)(宽度)像素。
与第二项相同,有一次在一个图像处理项目中工作(尽管是在今天的标准下的旧硬件上),我发现即使是确定像素位置所涉及的算法也会导致速度变慢。例如,如果您正在处理坐标(x, y),那么直观的做法是在
buf[y * bytes_per_line + x]
处引用像素。如果您的CPU在进行乘法运算时速度很慢,而您的映像又很大,那么这可能会增加。在这种情况下,最好一次循环一行,而不是继续计算(x, y)在各种坐标下的位置。
在出现问题之前找到解决方案是没有效率的。
你最好专注于你的设计,把这些细节留到以后,谁知道呢,也许你最终会因为一个好的整体设计而永远不会有任何性能问题。
从缓存中读取地址要比从主存中读取快得多。因此,尽量让你正在阅读的所有地址保持在尽可能近的位置。
例如,在构建链表时,您最好为所有节点(可以或多或少按顺序放置)使用一个大块,而不是每个节点使用一个malloc(这可能会使您的数据结构碎片化)
内存使用不必是连续的。如果你能将所使用的内存大小减半,那可能会有所帮助。
在结构体组织方面,应该把字节放在一起,然后把short放在一起,等等。否则,编译器将浪费内存,将较小的字节和short对齐到双字位置。
是另一个技巧。如果你正在使用一个类,你可以把它放在堆栈上,而不是用new来分配它。
我的意思
CmyClass x;
instead of
Cmyclass px = new CmyClass;
...
delete px;
* *编辑当您在c++堆中调用new()或malloc时,堆有时会在几个周期内返回一个新的内存块,有时则不会。当你在堆栈上声明一个类时,你仍然消耗了相同数量的内存(可能比这更复杂),但类只是被"推"到堆栈上,不需要函数调用。永远。当函数退出时,堆栈被清理,堆栈收缩。
- C++尝试深度复制唯一指针时出现内存访问冲突
- 如何使用 MPI 的远程内存访问 (RMA) 功能并行化数据聚合?
- CRTP - 危险的内存访问?
- C++ Python 的扩展 - 安全内存访问和内存布局
- 在Visual Studio中查找非法内存访问
- C++内存访问违反内存大块
- 数组中未映射的内存访问从python传递到c++
- 使用加速进程间创建消息队列 - 内存访问冲突
- C 指针转换会导致内存访问冲突
- 为什么代码会抛出非法内存访问错误
- 多线程环境中C++内存访问
- CUDA 中的递归返回非法内存访问
- 为什么创建进程 API 调用会导致内存访问冲突?
- 在 C++ 中遍历链表比在具有类似内存访问的 Go 中慢
- 确定打开进程的内存访问位置
- 存在内存访问异常,但我不确定我的代码中出了什么问题
- 指向结构的指针的 2D 数组.内存访问问题
- GPU 内存访问和使用 (CUDA)
- 在实时程序中是动态内存访问有害的
- 随机 mmaped 内存访问比堆数据访问慢 16%