2015-05-12 7 views
1

Моя цель - измерить эффект (кеширование) различных типов с помощью простого кода. Я следую этой статье, особенно страница 20 и 21: https://people.freebsd.org/~lstewart/articles/cpumemory.pdfИзмерение циклов процессора кода C++

Я работаю над 64-разрядным Linux. Кэш L1d имеет значение 32K, L2 - 256K, а L3 - 25M.

Это мой код (я скомпилировать этот код с г ++, без флагов):

#include <iostream> 

// *********************************** 
// This is for measuring CPU clocks 
#if defined(__i386__) 
static __inline__ unsigned long long rdtsc(void) 
{ 
    unsigned long long int x; 
    __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); 
    return x; 
} 
#elif defined(__x86_64__) 
static __inline__ unsigned long long rdtsc(void) 
{ 
    unsigned hi, lo; 
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi)); 
    return ((unsigned long long)lo)|(((unsigned long long)hi)<<32); 
} 
#endif 
// *********************************** 


static const int ARRAY_SIZE = 100; 

struct MyStruct { 
    struct MyStruct *n; 
}; 

int main() { 
    MyStruct myS[ARRAY_SIZE]; 
    unsigned long long cpu_checkpoint_start, cpu_checkpoint_finish; 

    // Initializing the array of structs, each element pointing to the next 
    for (int i=0; i < ARRAY_SIZE - 1; i++){ 
     myS[i].n = &myS[i + 1]; 
     for (int j = 0; j < NPAD; j++) 
      myS[i].pad[j] = (long int) i; 
    } 
    myS[ARRAY_SIZE - 1].n = NULL; // the last one 
    for (int j = 0; j < NPAD; j++) 
     myS[ARRAY_SIZE - 1].pad[j] = (long int) (ARRAY_SIZE - 1); 

    // Filling the cache 
    MyStruct *current = &myS[0]; 
    while ((current = current->n) != NULL) 
     ; 

    // Sequential access 
    current = &myS[0]; 

    // For CPU usage in terms of clocks (ticks) 
    cpu_start = rdtsc(); 

    while ((current = current->n) != NULL) 
     ; 

    cpu_finish = rdtsc(); 

    unsigned long long avg_cpu_clocks = (cpu_finish - cpu_start)/ARRAY_SIZE; 

    std::cout << "Avg CPU Clocks: " << avg_cpu_clocks << std::endl; 
    return 0; 
} 

У меня есть две проблемы:

1- я изменял ARRAY_SIZE от 1 до 1000000 (поэтому размер мой массив находится в диапазоне от 2 до 2 Мбайт), но средняя частота процессора всегда равна 10.

Согласно этому PDF (рис. 3-10 на стр. 21), я ожидал получить 3-5 часов, когда массив может полностью вписывается в L1 и получает более высокие цифры (9 циклов), когда он превышает размер L1.

2- Если я увеличу ARRAY_SIZE за 1 000 000, я получу ошибку сегментации (сброс ядра), из-за переполнения стека. Мой вопрос заключается в том, не использует ли динамическое распределение (MyStruct *myS = new MyStruct[ARRAY_SIZE]) любое ограничение производительности.

+3

Вам необходимо попросить ваш компилятор оптимизировать для целей бенчмаркинга. Поэтому скомпилируйте с помощью 'g ++ -O2 -Wall -mtune = native'; не используйте 'rdtsc', но читайте [time (7)] (http://man7.org/linux/man-pages/man7/time.7.html) –

+0

@BasileStarynkevitch Я использовал эти флаги, а теперь средний cpu clocks равно 4, все равно независимо от длины массива. Это предполагает, что либо все может поместиться в L1d (что не так), либо предварительная выборка делает отличную работу (что я сомневаюсь). – narengi

+0

@BasileStarynkevitch Кроме того, не могли бы вы рассказать мне, почему rdtsc не подходит для этой цели? – narengi

ответ

3

Хотя метод, который вы использовали для измерения исполнения, не является надежным, результат, который вы получили (4 цикла), имеет смысл, и вот почему. Измеряемый цикл содержит три команды (mov, test, branch). Из-за предсказания ветвей мы можем притвориться, что у нас есть только последовательность команд mov/test. Из-за суперскалярного выполнения выполнение команды mov может быть перекрыто выполнением тестовой команды некоторой предыдущей итерации. Следовательно, mov определяет время выполнения. Задержка получения данных из кэша L1 в большинстве современных процессоров Intel составляет 4 цикла. Поэтому для каждого элемента должно занимать около 4 циклов, предполагая, что он существует в кеше L1. Однако, просто угадывая, но это зависит от микроархитектуры, причина, по которой количество элементов в массиве, по-видимому, не влияет на время, заключается в том, что аппаратные предварительные считыватели L1 и L2 скрывают большую часть затрат на получение данных с более низких уровней памяти система эффективно делает размер кеша L1 бесконечным.

Если вы хотите оценить преимущества кэшей, а не аппаратную предварительную выборку, вам необходимо отключить предварительное тестирование. Способ сделать это зависит от процессора и может оказаться невозможным.

Intel recommends using the rdtscp instruction to measure execution time. При правильном использовании он может давать измерения более точными, чем clock_gettime или любые другие функции.

Относительно вашего второго вопроса. Вы должны выделить массив из кучи. Для этой конкретной программы она не будет нести дополнительную штрафную санкцию.

+0

Благодарим за отзыв. Вы упомянули, что мой метод измерения не является надежным. Я изменил rdtsc на rdtscp в своем коде и все равно получаю те же результаты. Каков правильный метод бенчмаркинга? Я просто наткнулся на инструмент PCM от Intel, но некоторые проблемы заставили его работать. Я бы, безусловно, был бы заинтересован в инструменте, который дает мне кеш-хит/пропустить cnts, часы на инструкцию, .... Вы используете какой-либо инструмент/библиотеку? – narengi

+0

Это гораздо сложнее, чем просто изменить rdtsc на rdtscp, есть несколько вещей, которые вы должны позаботиться, чтобы обеспечить надежное измерение, как описано в документе Intel. –

+0

Есть много таких инструментов, как perf и Intel VTune Amplifier. –