这是一个简单的 memset
带宽基准:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main()
{
unsigned long n, r, i;
unsigned char *p;
clock_t c0, c1;
double elapsed;
n = 1000 * 1000 * 1000; /* GB */
r = 100; /* repeat */
p = calloc(n, 1);
c0 = clock();
for(i = 0; i < r; ++i) {
memset(p, (int)i, n);
printf("%4d/%4ld\r", p[0], r); /* "use" the result */
fflush(stdout);
}
c1 = clock();
elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);
free(p);
}
在我的系统(详情如下)中使用单个DDR3-1600内存模块,它输出:
带宽= 4.751 GB / s(千兆= 10 ^ 9)
这是理论RAM速度的37%: 1.6 GHz * 8 bytes = 12.8 GB/s
另一方面,这是一个类似的“阅读”测试:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
unsigned long do_xor(const unsigned long* p, unsigned long n)
{
unsigned long i, x = 0;
for(i = 0; i < n; ++i)
x ^= p[i];
return x;
}
int main()
{
unsigned long n, r, i;
unsigned long *p;
clock_t c0, c1;
double elapsed;
n = 1000 * 1000 * 1000; /* GB */
r = 100; /* repeat */
p = calloc(n/sizeof(unsigned long), sizeof(unsigned long));
c0 = clock();
for(i = 0; i < r; ++i) {
p[0] = do_xor(p, n / sizeof(unsigned long)); /* "use" the result */
printf("%4ld/%4ld\r", i, r);
fflush(stdout);
}
c1 = clock();
elapsed = (c1 - c0) / (double)CLOCKS_PER_SEC;
printf("Bandwidth = %6.3f GB/s (Giga = 10^9)\n", (double)n * r / elapsed / 1e9);
free(p);
}
它输出:
带宽= 11.516 GB / s(千兆= 10 ^ 9)
我可以接近读取性能的理论极限,例如对大型阵列进行异或,但写入速度要慢得多 . 为什么?
OS Ubuntu 14.04 AMD64(我用 gcc -O3
编译 . 使用 -O3 -march=native
使读取性能稍差,但不影响 memset
)
CPU 至强E5-2630 v2
RAM 单个"16GB PC3-12800 Parity REG CL11 240-Pin DIMM"(它在盒子上说的内容)我认为使用单个DIMM可以使性能更具可预测性 . 我假设使用4个DIMM, memset
将快4倍 .
Motherboard Supermicro X9DRG-QF(支持4通道内存)
Additional system :具有2x 4GB DDR3-1067 RAM的笔记本电脑:读取和写入都是大约5.5 GB / s,但请注意它使用2个DIMM .
P.S. 用此版本替换 memset
会产生完全相同的性能
void *my_memset(void *s, int c, size_t n)
{
unsigned long i = 0;
for(i = 0; i < n; ++i)
((char*)s)[i] = (char)c;
return s;
}
7 回答
缓存和位置几乎可以肯定地解释了您所看到的大部分效果 .
写入时没有任何缓存或位置,除非您需要非确定性系统 . 大多数写入时间都是以数据一直到达存储介质所需的时间来衡量的(无论是硬盘驱动器还是内存芯片),而读取可以来自任何数量的缓存层,这些缓存层比存储介质 .
有了你的程序,我明白了
在具有六个2GB DIMM的台式机(Core i7,x86-64,GCC 4.9,GNU libc 2.19)上 . (我手边没有更多细节,抱歉 . )
但是,该程序报告
12.209 GB/s
的写入带宽:魔法全部在
_mm_stream_si128
,也就是机器指令movntdq
,它将一个16字节的数量写入系统RAM,绕过缓存(官方术语是“non-temporal store”) . 我认为这非常有说服力地证明了性能差异与缓存行为有关 .注: glibc 2.19确实有一个精心手工优化的
memset
,它使用向量指令 . 但是,它不使用非临时存储 . 对于memset
来说,这可能是正确的事情;通常,您在使用它之前不久就会清除内存,因此您希望它在缓存中很热 . (我认为一个更聪明的memset
可能会切换到非临时存储以获得非常大的块清除,理论上你不可能在缓存中想要所有这些,因为缓存根本不是那么大 . )(这是
libc.so.6
,而不是程序本身 - 试图转储程序集的另一个人似乎只是找到了它的PLT条目 . 在Unixy系统上获取真正的memset
的汇编转储的最简单方法是. )
性能的主要区别来自PC /内存区域的缓存策略 . 当您从内存中读取并且数据不在缓存中时,必须首先通过内存总线将内存提取到缓存,然后才能对数据执行任何计算 . 但是,当您写入内存时,会有不同的写入策略 . 很可能你的系统正在使用回写缓存(或者更确切地说是“写分配”),这意味着当你写入不在缓存中的内存位置时,数据首先从内存中提取到缓存并最终写入当数据从高速缓存中逐出时,返回存储器,这意味着数据的往返和写入时的2x总线带宽使用 . 还有直写高速缓存策略(或“无写入分配”),这通常意味着在写入时高速缓存未命中时,数据不会被提取到高速缓存,并且应该使读取和接收的数据更接近相同的性能 . 写道 .
差异 - 至少在我的机器上,与AMD处理器 - 是读取程序使用矢量化操作 . 对编写程序进行反编译会得到以下结果:
但这对于阅读计划:
另请注意,您的"homegrown"
memset
实际上已经优化到对memset
的调用:我找不到关于
memset
是否使用向量化操作的任何参考,memset@plt
的反汇编在这里是无益的:This question表明,由于
memset
旨在处理每个案例,因此可能缺少一些优化 .This guy绝对相信您需要使用自己的汇编程序
memset
来利用SIMD指令 . This question does, too .我将在黑暗中拍摄,并猜测它没有使用SIMD操作,因为它无法判断它是否会在某个东西上运行一个矢量化操作的大小的倍数,或者存在一些与对齐相关的问题 .
但是,通过使用
cachegrind
进行检查,我们可以确认这不是缓存效率问题 . 写程序产生以下内容:并且读取程序产生:
虽然读取程序具有较低的LL未命中率,因为它执行更多的读取(每个
XOR
操作额外读取),但未命中的总数是相同的 . 所以无论问题是什么,都不存在 .它可能就是它如何(整个系统)执行 . 读取速度更快appears to be a common trend具有广泛的相对吞吐量性能 . 快速分析列出的DDR3英特尔和DDR2图表,作为几个选择案例(写/读)%;
一些性能最佳的DDR3芯片的写入速率约为读取吞吐量的60-70% . 但是,有一些内存模块(即Golden Empire CL11-13-13 D3-2666)下降到只有~30%写入 .
与读取相比,性能最佳的DDR2芯片似乎仅具有约50%的写入吞吐量 . 但也有一些特别糟糕的竞争者(即OCZ OCZ21066NEW_BT1G)降至约20% .
虽然这可能无法解释报告的~40%写入/读取的原因,但由于使用的基准代码和设置可能不同(notes are vague),这绝对是一个因素 . (我会运行一些现有的基准程序,看看这些数字是否与问题中发布的代码一致 . )
更新:
我从链接的站点下载了内存查找表并在Excel中进行了处理 . 虽然它仍然显示了大范围的值,但它比上面的原始回复要小得多,后者仅查看顶部读取的内存芯片和图表中的一些选定的"interesting"条目 . 我不确定为什么这些差异,特别是在上面列出的可怕竞争者中,不存在于次要名单中 .
然而,即使在新数字下,差异仍然广泛地在读取性能的50%-100%(中位数65,平均值65)之间 . 请注意,仅仅因为芯片在写入/读取比率方面“100%”有效并不意味着它总体上更好 . 只是它在两个操作之间更加均衡 .
这是我的工作假设 . 如果正确,它解释了为什么写入比读取慢两倍:
即使
memset
仅写入虚拟内存,忽略其先前的内容,在硬件级别,计算机也无法对DRAM进行纯写入:它将DRAM的内容读入缓存,在那里修改它们然后将它们写回DRAM . 因此,在硬件层面,memset
同时进行读写(即使前者似乎无用)!因此大约两倍的速度差异 .因为读取你只是脉冲地址线并读出感测线上的核心状态 . 回写周期在数据传递到CPU之后发生,因此不会减慢速度 . 另一方面,要写入,必须首先执行伪读取以重置核心,然后执行写入循环 .
(以防万一它不明显,这个答案是诙谐的 - 描述为什么写入比在旧的核心内存盒上读取要慢 . )