У меня есть вопрос о ложном совместном использовании параллельного программирования.
Исходный код находится внизу.
Кратко изложите мой вопрос здесь:
if define USE_REGISTER,
perf stat -e L1-dcache-load -e L1-dcache-load-misses ./falseSharing
и
# определитьPADNUM 1
результат
12,0461,8669 L1-dcache-load
2861,8883 L1-dcache-load-misses # 2.38% of all L1-dcache hit
exe время
реальное 0m0,683s
затем я увеличиваю PADNUM на 1 на 1.
Время выполнения почти не меняется до тех пор, пока PADNUM не станет равным 7.
Ускорение ожидается X2, когдаPADNUM> = 7, как
real 0m0,305s
, и в это время пропуск кэша составляет
12,0115,2333 L1-dcache-load
6,9266 L1-dcache-load-misses # 0.01% of all L1-dcache hits
Если я применил USE_REGISTER, как и ожидалось, некоторые переменные будут сохранены в регистре, поэтому число L1-dcache-load упадет до ~ 7,0000,0000.
Мои вопросы:
Почему потеря кэша составляет ~ 2 ~ 3% ?Интуитивно Я думаю, что это будет ~ 50%, так как 2 потока выполняют чередование.
Почему pad равен 7 только ?так как DUMP_TID говорит, например:
addr (x) = 0x602200, addr (y) = 0x602220, sizeof padding = 28, начало пэда в 0x602204, sizeof (f) =36
это означает, что в моем компьютере заполнение составляет 28 байт, размер float составляет 4 байта, поэтому выравнивание кажется равным 32 байтам, вместо 64 байт.
Однако в cat cpuinfo говорится, что строка кэша выровнена с 64 байтами:
cache_alignment: 64
#include <stdio.h>
#include <stdlib.h>
#include <thread>
#include <iostream>
#include <sstream>
#include <mutex>
#ifdef USE_REGISTER
#define REGISTER register
#else
#define REGISTER
#endif
using namespace std;
#define PADNUM 1
#define REGISTER register
struct fooPad {
float x;
float pad[PADNUM];
float y;
};
fooPad f;
/* The two following functions are running concurrently: */
std::mutex g_display_mutex;
#define DUMP_TID() \
do{ \
g_display_mutex.lock(); \
std::thread::id this_id = std::this_thread::get_id(); \
std::cout << __FUNCTION__ << "() thread = " << this_id << endl; \
g_display_mutex.unlock(); \
} while(0)
float sum_a(void)
{
DUMP_TID();
REGISTER float s = 0;
REGISTER float i;
for (i = 0; i < 10000000; ++i)
s += f.x;
return s;
}
float inc_b(void)
{
DUMP_TID();
REGISTER float i;
for (i = 0; i < 10000000; ++i){
f.y += 1.0;
}
return f.y;
}
int main()
{
float a = 0;
float b = 0;
f.x = 1.0;
f.y = 1.5;
printf("addr(x) = %p, addr(y) = %p, sizeof padding = %ld, pad start at %p, sizeof(f) = %ld\n",
&(f.x),
&(f.y),
sizeof(f.pad),
&(f.pad[0]), sizeof(f) );
for (int i = 0; i < 10; i++) {
#pragma omp parallel num_threads(2)
#pragma omp single
{
#pragma omp task
//a = sum_a();
a = sum_a();
#pragma omp task
//b = inc_b();
b = inc_b();
#pragma omp taskwait
}
}
printf("a = %f, b = %f\n", a, b);
return 1;
}
/*
int main()
{
arr[0] = 1; arr[1] = 2;
float a = 0;
float b = 0;
f.x = 1.0;
f.y = 1.5;
printf("size %ld %ld %ld\n", sizeof(f.x),sizeof(f.y), sizeof(f) );
printf("size %ld %ld %ld\n", sizeof(f1.x),sizeof(f1.y), sizeof(f1) );
printf("size %ld %ld %ld\n", sizeof(f2.x),sizeof(f2.y), sizeof(f2) );
for (int i = 0; i < 10; i++) {
#pragma omp parallel num_threads(1)
#pragma omp single
{
#pragma omp task
a = sum_a();
#pragma omp task
b = inc_b();
#pragma omp taskwait
}
}
printf("a = %f, b = %f\n", a, b);
return 1;
}
*/