Я хочу измерить пропускную способность памяти, используя memcpy
. Я изменил код из этого ответа: почему для векторизации цикла нет улучшения производительности, который использовал memset
для измерения полосы пропускания. Проблема в том, что memcpy
работает только медленнее, чем memset
, когда я ожидаю, что он будет примерно в два раза медленнее, так как он работает дважды в памяти.
В частности, я запускаю более 1 ГБ массивов 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
, поскольку он работает в три раза больше данных. Однако это примерно на 2,5 меньше, чем memset
.
Затем я инициализировал b
до нуля с помощью memset(b,0,LEN)
и снова проверил:
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
будет медленнее, поскольку ленивое выделение (первое касание) на первом 100 итераций.
Почему я получаю только время, ожидаемое после memset(b,0,LEN)
?
test.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);
}
main.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), 32 ГБ DDR4, Ubuntu 16.10. В моей системе Haswell полосы пропускания имеют смысл до memset(b,0,LEN)
т.е. я вижу только проблему в своей системе Skylake.
Я впервые обнаружил эту проблему из a[j] += b[k]
операций в этом ответе, которая переоценивала пропускную способность.
Я придумал более простой тест
#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
Однако, если я выполняю memset(b,1,LEN)
в main после calloc
(см. ниже), то он выдает
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);
}