Кэш процессора

Кэш процессора один из факторов объясняющих почему современные процессоры так быстро работают. Без использования кэша современным процессорам (например с тактовой частотой 1 гигагерц и частой работы памяти 100 мегагерц) пришлось бы простаивать ожидая когда они получат данные.В КЭШе должны находиться наиболее часто используемые данные и данные которые недавно обрабатывались. Попробуйте в БИОСе отключить использование КЭШа и вы увидите насколько упадет производительность вашей системы.

Когда мы используем SMP, ядро ОС должно разумно использовать кэш процессора. Любой поток в зависимости от готовности процессора может быть перемещен с одного процессора на другой. Однако такая схема имеет свои недостатки. Например, если поток хранит свои данные в КЭШе первого процессора, то после переброса потока на другой процессор, в КЭШе первого процессора они должны быть перенесены в общую память, откуда их загрузит в свой кэш второй процессор. При перемещении потока, желательно избегать эффектов перегрузки данных из одного КЭШа в другой. Для достижения этой цели в SMP ядре Neutrino используется эвристический подход для оценки эффективности использования переноса потоков.

К счастью, программистам не надо вникать во все тонкости планирования загрузки процессоров. Все сложности связанные с ним берет на себя ядро и компилятор. Было проведено много семинаров разработчиков QNX посвященных оптимизации работы на многопроцессорных компьютерах. На этих семинарах предлагалось много идей от разработчиков по написанию кода.

Вот наверное наиболее частый пример:

char array[256000];
memset( array, 0, sizeof( array ) );
for ( cnt = 0; cnt < sizeof( array ); cnt++ )
do_something( array[cnt] );
Попробуйте за 20 секунд увеличить эффективность работы этой программы.

А вот решение:

char array[256000];
memset( array, 0, sizeof( array ) );
for ( cnt = sizeof( array )-1; cnt >=0; cnt—)
do_something( array[cnt] );
Итак первый пример. Понятно что массив размером 256000 байт не поместиться в кэш Celeron. Когда функция memset заканчивает свою работу, начало массива уже находится вне кэша процессора. Что получаем мы: мы копируем снова начало массива в кэш, на чем теряем драгоценное время. Я привел пример с массивом размера 256 килобайт, что бы вам было проще понять эту идею. В операционной системе подобной Neutrino у вас работают помимо вашей программы другие программы (например программа обработчика прерываний). Даже если во время их работы ваши данные будут выгружаться из кэша, второй вариант решения более предпочтителен.

Другое решение этой задачи состоит в использование функции memset_r(), которая начинает записывать данные с конца (фактически это будет программа аналогичная нашему решению). Просто замените функцию memset() на memset_r().

Другое решение этой проблемы состоит в уменьшение обрабатываемых блоков. Обработка небольших блоков проходит быстрее чем одна большая. Давайте посмотрим это на следующем примере:

#include «stdafx.h»
#include
#include
#include
#include
#define BLOCK_SIZE 10000
void work1( char *data, size_t size ) {
size_t cnt;
for( cnt = 0 ; cnt < size; cnt++, data++ ) {
*data ^= 1;
}
}
void work2( char *data, size_t size ) {
size_t cnt;
for( cnt = 0 ; cnt < size; cnt++, data++ ) {
*data |= 2;
}
}
void work3( char *data, size_t size ) {
size_t cnt;
for( cnt = 0 ; cnt < size; cnt++, data++ ) {
*data += 3;
}
}
int main(int argc, char* argv[]) {
int t;
int i;
float elapse;
static char dummy[BLOCK_SIZE * 10000];
t = clock();
// — один большой блок
memset( dummy, 0, sizeof( dummy ) );
work1( dummy, sizeof( dummy ) );
work2 ( dummy,sizeof( dummy ) );
work3 ( dummy,sizeof ( dummy ) );
elapse = ( clock()-t ) / (float)CLOCKS_PER_SEC;
printf(«Duration %f sec(s)\n», elapse );
t = clock();
// — маленькие блоки
for( i=0; i<sizeof( dummy ) ; i+= BLOCK_SIZE ) {
memset( &dummy;[i], 0, BLOCK_SIZE );
work1 ( &dummy;[i], BLOCK_SIZE );
work2 ( &dummy;[i], BLOCK_SIZE);
work3 ( &dummy;[i], BLOCK_SIZE);
}
elapse = ( clock()-t ) / (float)CLOCKS_PER_SEC;
printf(Duration %f sec(s)\n , elapse );
return 0;
}
>
На Celeron мы получим следующий результат:

Duration 8.609000 sec(s)
Duration 6.766000 sec(s)
Даже с учетом накладных расходов на цикл, где мы вызываем 4 функции мы получили прирост более чем на 20%. Убедились? Я мог бы еще больше повысить производительность данной программы если бы в функциях work_1 и work_3 обрабатывал данные в цикле от большего к меньшему.

Также стоит отметить вниманием сравнение обычной системы относительно SMP системы, если вы сравниваете системный блок с двумя Celeron 500MHz c системным блоком с одним Celeron 1000 MGz. SMP система имеет в нашем примере вдвое больше кэша, что дает основание отдавать предпочтение SMP системе. Чем кэша больше тем лучше