Я написал две программы, чтобы проверить, удаляет ли clflush мои данные из кэша или нет. Из двух программ, которые я написал, только одна дает правильный результат (согласно моим ожиданиям, после clflush время доступа должно быть выше, чем до сброса).
Вот моя программа1, где я получаю ожидаемый результат.
#include <stdio.h>
#include <stdint.h>
inline void clflush(volatile void *p)
{
asm volatile ("clflush (%0)" :: "r"(p));
}
inline uint64_t rdtsc()
{
unsigned long a, d;
asm volatile ("cpuid; rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx");
return a | ((uint64_t)d << 32);
}static int i=10; // static variable
inline void test()
{
uint64_t start, end;
int j;
start = rdtsc();
j = i;
end = rdtsc();
printf("took %lu ticks\n", end - start);
}
int main(int ac, char **av)
{
test();
test();
printf("flush: ");
clflush((void *)&i);
test();
test();
return 0;
}
Вот мой вывод (как и ожидалось)
took 314 ticks
took 282 ticks
flush: took 442 ticks
took 272 ticks
Вот еще одна программа, где Я не получаю ожидаемого результата.
#include <stdio.h>
#include <stdint.h>
inline void clflush(volatile void *p)
{
asm volatile ("clflush (%0)" :: "r"(p));
}
inline uint64_t rdtsc()
{
unsigned long a, d;
asm volatile ("cpuid; rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx");
return a | ((uint64_t)d << 32);
}static const int i=10; // I make this as constant
inline void test()
{
uint64_t start, end;
int j;
start = rdtsc();
j = i;
end = rdtsc();
printf("took %lu ticks\n", end - start);
}
int main(int ac, char **av)
{
test();
test();
printf("flush: ");
clflush((void *)&i);
test();
test();
return 0;
}
Вот мой вывод (как и ожидалось)
took 314 ticks
took 282 ticks
flush: took 282 ticks // same as previous
took 272 ticks--------
took 314 ticks
took 282 ticks
flush: took 272 ticks // lower than previous
took 272 ticks
Если я сделаю статический int я = 10; в статическое const int i = 10; тогда результат не соответствует моим ожиданиям. Я получаю меньшее значение / равное время доступа после clflush.
Кто-нибудь может объяснить, почему это происходит? Как я могу сделать это (в C или C ++) в соответствии с моими ожиданиями (более высокое время доступа после clflush как program1)?
Я использую GCC под Fedora19 Linux. Любая помощь будет высоко оценена.
Я уверен, что проблема в том, что CPUID + RDTSC слишком длинный, по сравнению с «инструкциями между».
Я получаю очень разные результаты, предположительно в зависимости от «удачи» того, на каком фактическом процессоре выполняется код, что делает другой процессор и т. Д. И т. Д.
Вот три прогона подряд второй программы:
took 92 ticks
took 75 ticks
flush: took 75 ticks
took 474 ticks
took 221 ticks
took 243 ticks
flush: took 221 ticks
took 242 ticks
took 221 ticks
took 221 ticks
flush: took 221 ticks
took 230 ticks
Однако я не думаю, что из этого можно сделать вывод, что «clflush не работает». Только то, что в процессоре достаточно тактов и неиспользуемого исполнения, чтобы преодолеть очистку кеша и перезагрузить данные.
Вы, вероятно, получили бы гораздо более заметный эффект, если бы у вас был большой кусок данных, скажем, несколько килобайт. Я немного поэкспериментирую, но сейчас мне нужно немного еды …
#include <stdio.h>
#include <stdint.h>
inline void clflush(volatile void *p)
{
__asm volatile ("clflush (%0)" :: "r"(p));
}
inline uint64_t rdtsc()
{
unsigned long a, d;
__asm volatile ("rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx");
return a | ((uint64_t)d << 32);
}static int v[1024];
uint64_t t[5];
int r[5];
int ti = 0;
static inline void test()
{
uint64_t start, end;
int j;
start = rdtsc();
for(int i = 0; i < 1024; i++)
{
j += v[i];
}
end = rdtsc();
r[ti] = j;
t[ti++] = end - start;
}
int main(int ac, char **av)
{
for(int i = 0; i < 1024; i++)
{
v[i] = i;
}
test();
test();
t[ti++] = 0;
for(int i = 0; i < 1024; i+=4)
{
clflush((void *)&v[i]);
}
test();
test();
for(int i = 0; i < ti; i++)
{
if (t[i] == 0)
{
printf("flush\n");
}
else
{
printf("Test %lu [res=%d]\n", t[i], r[i]);
}
}
printf("\n");
return 0;
}
Я переместил printf из тестовой дорожки, чтобы уменьшить количество времени, проведенного там, и сделал намного большую область очистки. Это дает гораздо более длительное время выполнения, что, безусловно, помогает измерениям.
Test 2538 [res=523776]
Test 2593 [res=523776]
flush
Test 4845 [res=523776]
Test 2592 [res=523776]
Test 2550 [res=523776]
Test 2771 [res=523776]
flush
Test 4782 [res=523776]
Test 2513 [res=523776]
Test 2550 [res=523776]
Test 2708 [res=523776]
flush
Test 4356 [res=523776]
Test 2593 [res=523776]
Как вы можете видеть, после сброса данные примерно в два раза быстрее извлекаются по сравнению с первым доступом.
Редактировать:
Используя const, вот так
static const int v[1024] =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
/* snip 62 lines equal to this */
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
};
дает такой результат:
Test 14139 [res=8704]
Test 2639 [res=8704]
flush
Test 5287 [res=8704]
Test 2597 [res=8704]
Test 12983 [res=8704]
Test 2652 [res=8704]
flush
Test 4859 [res=8704]
Test 2550 [res=8704]
Test 12911 [res=8704]
Test 2581 [res=8704]
flush
Test 4705 [res=8704]
Test 2649 [res=8704]
Как видите, третий доступ явно медленнее, чем второй и четвертый. Первый доступ медленнее, потому что при первом доступе в кеше вообще ничего нет (включая таблицы страниц и т. Д.).
Других решений пока нет …