在下面的程序中,我有两个缓冲区,一个是64字节对齐的缓冲区,另一个是我假设在运行2.6.x内核的64位Linux主机上对齐16字节。
缓存行长度为64byte。所以,在这个程序中,我一次只访问一个缓存行。我希望看到posix_memaligned
相等,如果不比非对齐缓冲区快。
以下是一些指标
./readMemory 10000000
time taken by posix_memaligned buffer: 293020299
time taken by standard buffer: 119724294
./readMemory 100000000
time taken by posix_memaligned buffer: 548849137
time taken by standard buffer: 211197082
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <linux/time.h>
void now(struct timespec * t);
int main(int argc, char **argv)
{
char *buf;
struct timespec st_time, end_time;
int runs;
if (argc !=2)
{
printf("Usage: ./readMemory <number of runs>\n");
exit(1);
}
errno = 0;
runs = strtol(argv[1], NULL, 10);
if (errno !=0) {
printf("Invalid number of runs: %s \n", argv[1]);
exit(1);
}
int returnVal = -1;
returnVal = posix_memalign((void **)&buf, 64, 1024);
if (returnVal != 0)
{
printf("error in posix_memaligh\n");
}
char tempBuf[64];
char * temp = buf;
size_t cpyBytes = 64;
now(&st_time);
for(int x=0; x<runs; x++) {
temp = buf;
for(int i=0; i < ((1024/64) -1); i+=64)
{
memcpy(tempBuf, temp, cpyBytes);
temp += 64;
}
}
now(&end_time);
printf("time taken by posix_memaligned buffer: %ld \n", (end_time.tv_nsec - st_time.tv_nsec));
char buf1[1024];
temp = buf1;
now(&st_time);
for(int x=0; x<runs; x++)
{
temp = buf1;
for(int i=0; i < ((1024/64) -1); i+=64)
{
memcpy(tempBuf, temp, cpyBytes);
temp += 64;
}
}
now(&end_time);
printf("time taken by standard buffer: %ld \n", (end_time.tv_nsec - st_time.tv_nsec));
return 0;
}
void now(struct timespec *tnow)
{
if(clock_gettime(CLOCK_MONOTONIC_RAW, tnow) <0 )
{
printf("error getting time");
exit(1);
}
}
第一个循环的反汇编是
movq -40(%rbp), %rdx
movq -48(%rbp), %rcx
leaq -176(%rbp), %rax
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
addq $64, -48(%rbp)
addl $64, -20(%rbp)
第二个循环的反汇编是
movq -40(%rbp), %rdx
movq -48(%rbp), %rcx
leaq -176(%rbp), %rax
movq %rcx, %rsi
movq %rax, %rdi
call memcpy
addq $64, -48(%rbp)
addl $64, -4(%rbp)
答案 0 :(得分:1)
原因可能是缓冲区的相对对齐。
复制字对齐数据(32/64位)时, memcpy
的效果最快
如果两个缓冲区完全一致,则一切正常
如果两个缓冲区以相同的方式错位,memcpy
通过逐字节复制一个小前缀,然后在余数上逐字运行来处理它。
但是如果一个缓冲区是字对齐而另一个缓冲区不是,那么就没有办法让读写字对齐。所以memcpy
仍然一字一句地工作,但是一半的内存访问严重对齐。
如果两个堆栈缓冲区都以相同的方式未对齐(例如,两个地址都是8 * x + 2),但posix_memalign
的缓冲区是对齐的,它可以解释您所看到的内容。
答案 1 :(得分:1)
您的基准测试存在一些问题:
答案 2 :(得分:0)
当我交换你的测量块时 - 也就是说,首先运行标准缓冲区测量并且posix_memalign
秒,我得到完全相反的结果。
换句话说,我的CPU(英特尔酷睿2)的第一个复制循环几乎总是慢于第二个,无论它们如何对齐。
我尝试过malloc()
标准缓冲区,而不是将它放在堆栈上 - 它几乎没有对速度产生任何影响,第一个循环仍然总是较慢。
我还尝试posix_memalign()
你的小64字节缓冲区 - 它没有任何区别。
编辑:我编辑了你的代码进行3次测量:posix aligned,malloc'ed和堆栈缓冲区(见下面的代码)。
事实证明,只有第一个循环很慢。任何后续循环几乎完全相同(有一些小噪音)。
我相信我们正在观察Linux调度程序,只要它看到100%的CPU负载,就会提高CPU时钟速度。
我跑步的结果:
$ ./readmemory 2000000 5
time taken by posix aligned: 19599140
time taken by std malloc : 14711350
time taken by std on stack : 14680668
time taken by posix aligned: 14729273
time taken by std malloc : 14685338
time taken by std on stack : 14839183
time taken by posix aligned: 14709836
time taken by std malloc : 15551900
time taken by std on stack : 14659350
time taken by posix aligned: 14721298
time taken by std malloc : 14691732
time taken by std on stack : 14691246
time taken by posix aligned: 14722127
time taken by std malloc : 15538286
time taken by std on stack : 14723657
更新的代码:
// compile with: g++ readmemory.c -o readmemory -lrt
#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 1024
#define COPY_BYTES 64
void now(struct timespec *tnow) {
if (clock_gettime(CLOCK_MONOTONIC, tnow) < 0) {
printf("error getting time");
exit(1);
}
}
void measure(char * buf, int runs, const char * msg) {
char tempBuf[64];
struct timespec st_time, end_time;
char * temp;
now(&st_time);
for (int x=0; x<runs; x++) {
temp = buf;
for (int i=0; i < ((BUF_SIZE/COPY_BYTES) - 1); i+=COPY_BYTES) {
memcpy(tempBuf, temp, COPY_BYTES);
temp += COPY_BYTES;
}
}
now(&end_time);
printf("time taken by %s: %ld\n", msg, end_time.tv_nsec - st_time.tv_nsec);
}
int main(int argc, char **argv) {
char * buf1; // posix_memalign'ed
char * buf2; // malloc'ed
char buf3[BUF_SIZE]; // alloc on stack
int rc = -1;
int runs;
int loops;
if (argc != 3) {
printf("Usage: ./readMemory <runs> <loops>\n");
exit(1);
}
errno = 0;
runs = strtol(argv[1], NULL, 0);
if (errno != 0) {
printf("Invalid number of runs: %s \n", argv[1]);
exit(1);
}
loops = strtol(argv[2], NULL, 0);
rc = posix_memalign((void **)&buf1, COPY_BYTES, BUF_SIZE);
if (rc != 0) {
printf("error in posix_memalign\n");
exit(1);
}
buf2 = (char *) malloc(BUF_SIZE);
if (buf2 == NULL) {
printf("error in malloc\n");
exit(1);
}
for (int i=0; i<loops; i++) {
measure(buf1, runs, "posix aligned");
measure(buf2, runs, "std malloc ");
measure(buf3, runs, "std on stack ");
}
return 0;
}
我认为我们正在观察现代CPU实现缓存的相当复杂的方式。