我想使用 memcpy
测量内存带宽。我修改了这个答案的代码:why vectorizing the loop does not have performance improvement它使用 memset
来测量带宽。问题是 memcpy
只比 memset
慢一点,我预计它会慢两倍左右,因为它在两倍的内存上运行。
更具体地说,我通过以下操作运行了超过 1 GB 的数组 a
和 b
(分配将 calloc
)100 次。
operation time(s)
-----------------------------
memset(a,0xff,LEN) 3.7
memcpy(a,b,LEN) 3.9
a[j] += b[j] 9.4
memcpy(a,b,LEN) 3.8
请注意 memcpy
仅比 memset
稍慢。操作 a[j] += b[j]
(其中 j
超过 [0,LEN)
)应该花费三倍于memcpy
因为它处理三倍多的数据。然而,它只比 memset
慢 2.5 倍。
然后我用 memset(b,0,LEN)
将 b
初始化为零并再次测试:
operation time(s)
-----------------------------
memcpy(a,b,LEN) 8.2
a[j] += b[j] 11.5
现在我们看到 memcpy
大约是 memset
的两倍,而 a[j] += b[j]
大约是三倍和我预期的一样慢 memset
。
至少在 memset(b,0,LEN)
之前,我预计 memcpy
会是 slower because the of lazy allocation (first touch)在 100 次迭代中的第一次迭代中。
为什么我只在 memset(b,0,LEN)
之后得到预期的时间?
测试.c
#include <time.h>
#include <string.h>
#include <stdio.h>
void tests(char *a, char *b, const int LEN){
clock_t time0, time1;
time0 = clock();
for (int i = 0; i < 100; i++) memset(a,0xff,LEN);
time1 = clock();
printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
time0 = clock();
for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
time1 = clock();
printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
time0 = clock();
for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
time1 = clock();
printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
time0 = clock();
for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
time1 = clock();
printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
memset(b,0,LEN);
time0 = clock();
for (int i = 0; i < 100; i++) memcpy(a,b,LEN);
time1 = clock();
printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
time0 = clock();
for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
time1 = clock();
printf("%f\n", (double)(time1 - time0) / CLOCKS_PER_SEC);
}
主.c
#include <stdlib.h>
int tests(char *a, char *b, const int LEN);
int main(void) {
const int LEN = 1 << 30; // 1GB
char *a = (char*)calloc(LEN,1);
char *b = (char*)calloc(LEN,1);
tests(a, b, LEN);
}
使用 (gcc 6.2) gcc -O3 test.c main.c
编译。 Clang 3.8 给出了基本相同的结果。
测试系统:i7-6700HQ@2.60GHz (Skylake),32GB DDR4,Ubuntu 16.10。在我的 Haswell 系统上,带宽在 memset(b,0,LEN)
之前有意义,即我只在我的 Skylake 系统上看到问题。
我首先从 a[j] += b[k]
操作中发现了这个问题 in this answer这是高估了带宽。
我想到了一个更简单的测试
#include <time.h>
#include <string.h>
#include <stdio.h>
void __attribute__ ((noinline)) foo(char *a, char *b, const int LEN) {
for (int i = 0; i < 100; i++) for(int j=0; j<LEN; j++) a[j] += b[j];
}
void tests(char *a, char *b, const int LEN) {
foo(a, b, LEN);
memset(b,0,LEN);
foo(a, b, LEN);
}
这输出。
9.472976
12.728426
但是,如果我在 calloc
之后在 main 中执行 memset(b,1,LEN)
(见下文),那么它会输出
12.5
12.5
这让我认为这是操作系统分配问题,而不是编译器问题。
#include <stdlib.h>
int tests(char *a, char *b, const int LEN);
int main(void) {
const int LEN = 1 << 30; // 1GB
char *a = (char*)calloc(LEN,1);
char *b = (char*)calloc(LEN,1);
//GCC optimizes memset(b,0,LEN) away after calloc but Clang does not.
memset(b,1,LEN);
tests(a, b, LEN);
}
最佳答案
重点是 malloc
和 calloc
在大多数平台上 不分配内存;他们分配地址空间。
malloc
等工作方式:
- 如果空闲列表可以满足请求,则从中切出一 block
- 在
calloc
的情况下:发出等同于memset(ptr, 0, size)
- 在
- 如果不是:要求操作系统扩展地址空间。
对于具有请求分页 (COW) 的系统(MMU 可以提供帮助),第二个选项归结为:
- 为请求创建足够的页表条目,并用对
/dev/zero
的 (COW) 引用填充它们> - 添加这些 PTEs到进程的地址空间
除了页表之外,这不会消耗任何物理内存。
- 一旦引用新内存进行读取,读取将来自
/dev/zero
。/dev/zero
设备是一个非常特殊的设备,在本例中映射到新内存的每一页。 - 但是,如果写入了新页面,COW 逻辑就会启动(通过页面错误):
- 分配物理内存
- /dev/zero 页面被复制到新页面
- 新页面与母页面分离
- 调用进程最终可以进行更新,这一切都开始了
关于c - Memcpy 与 memset 耗时相同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43168595/