Программа C для определения уровней и размера кэша

Полная перезапись / обновление для ясности (и ваше здравомыслие, его abit слишком долго)... ( Old Post)

Для назначения мне нужно найти уровни (L1,L2,...) и размер каждого кэша. Учитывая подсказки и то, что я нашел до сих пор: я думаю, что идея состоит в том, чтобы создавать массивы разных размеров и читать их. Сроки этих операций:

sizes = [1k, 4k, 256K, ...]
foreach size in sizes 
    create array of `size`

    start timer
    for i = 0 to n // just keep accessing array
        arr[(i * 16) % arr.length]++ // i * 16 supposed to modify every cache line ... see link
    record/print time

ОБНОВЛЕНО (28 сентября 18:57 UTC+8)

Смотрите также полный источник

Хорошо, теперь, следуя совету @ mah, возможно, я исправил проблему отношения SNR... а также нашел способ синхронизации моего кода (wall_clock_time из лабораторного примера кода)

Тем не менее, я, кажется, получаю неправильные результаты: я на Intel Core i3 2100: [ SPECS]

  • L1: 2 x 32K
  • L2: 2 x 256K
  • L3: 3 МБ

Результаты, которые я получил, на графике:

длинаМод: от 1КБ до 512К

Основание 1-го пика - 32K ... разумно... 2-е - 384K ... почему? Я ожидаю 256?

lengthMod: от 512 КБ до 4 МБ

Тогда почему этот диапазон может быть в беспорядке?


Я также читал о предварительной загрузке или вмешательстве из других приложений, поэтому я закрыл как можно больше вещей во время работы скрипта, и последовательно (через многократные прогоны) кажется, что данные размером 1 МБ и выше всегда такие грязные?

6 ответов

Решение

Время, необходимое для измерения вашего времени (то есть время только для вызова функции clock()) во много раз (много много много....) раз больше времени, которое требуется для выполнения arr[(i*16)&lengthMod]++, Это чрезвычайно низкое отношение сигнал / шум (среди других вероятных ошибок) делает ваш план неосуществимым. Большая часть проблемы заключается в том, что вы пытаетесь измерить одну итерацию цикла; пример кода, который вы связали, пытается измерить полный набор итераций (считайте часы перед началом цикла; прочитайте его снова после выхода из цикла; не используйте printf() внутри цикла).

Если ваша петля достаточно велика, вы можете решить проблему отношения сигнал / шум.

Что касается того, "какой элемент увеличивается"; arr адрес буфера в 1 МБ; arr[(i * 16) & lengthMod]++; причины (i * 16) * lengthMod генерировать смещение от этого адреса; это смещение является адресом int, который увеличивается. Вы выполняете сдвиг (i * 16 превратится в i << 4), логическое и, дополнительно, затем либо чтение / добавление / запись, либо единичное увеличение, в зависимости от вашего процессора).

Редактирование: Как описано, ваш код страдает от плохого SNR (отношение сигнал / шум) из-за относительной скорости доступа к памяти (кеш или нет кеша) и вызова функций просто для измерения времени. Чтобы получить время, которое вы получаете в настоящее время, я предполагаю, что вы изменили код, чтобы он выглядел примерно так:

int main() {
    int steps = 64 * 1024 * 1024;
    int arr[1024 * 1024];
    int lengthMod = (1024 * 1024) - 1;
    int i;
    double timeTaken;
    clock_t start;

    start = clock();
    for (i = 0; i < steps; i++) {
        arr[(i * 16) & lengthMod]++;
    }
    timeTaken = (double)(clock() - start)/CLOCKS_PER_SEC;
    printf("Time for %d: %.12f \n", i, timeTaken);
}

Это перемещает измерение за пределы цикла, поэтому вы измеряете не один доступ (что на самом деле было бы невозможно), а измеряете steps доступ.

Вы можете увеличить steps по мере необходимости, и это будет иметь прямое влияние на ваши сроки. Поскольку время, которое вы получаете, слишком близко друг к другу, а в некоторых случаях даже инвертировано (ваше время колеблется между размерами, что, скорее всего, не связано с кэшем), вы можете попробовать изменить значение steps в 256 * 1024 * 1024 или даже больше.

ПРИМЕЧАНИЕ: вы можете сделать steps настолько большой, насколько вы можете вписать в int со знаком (который должен быть достаточно большим), так как логично и гарантирует, что вы обернетесь в своем буфере.

После 10 минут поиска инструкции по эксплуатации Intel и еще 10 минут кодирования я придумал это (для процессоров Intel):

void i386_cpuid_caches () {
    int i;
    for (i = 0; i < 32; i++) {

        // Variables to hold the contents of the 4 i386 legacy registers
        uint32_t eax, ebx, ecx, edx; 

        eax = 4; // get cache info
        ecx = i; // cache id

        __asm__ (
            "cpuid" // call i386 cpuid instruction
            : "+a" (eax) // contains the cpuid command code, 4 for cache query
            , "=b" (ebx)
            , "+c" (ecx) // contains the cache id
            , "=d" (edx)
        ); // generates output in 4 registers eax, ebx, ecx and edx 

        // taken from http://download.intel.com/products/processor/manual/325462.pdf Vol. 2A 3-149
        int cache_type = eax & 0x1F; 

        if (cache_type == 0) // end of valid cache identifiers
            break;

        char * cache_type_string;
        switch (cache_type) {
            case 1: cache_type_string = "Data Cache"; break;
            case 2: cache_type_string = "Instruction Cache"; break;
            case 3: cache_type_string = "Unified Cache"; break;
            default: cache_type_string = "Unknown Type Cache"; break;
        }

        int cache_level = (eax >>= 5) & 0x7;

        int cache_is_self_initializing = (eax >>= 3) & 0x1; // does not need SW initialization
        int cache_is_fully_associative = (eax >>= 1) & 0x1;


        // taken from http://download.intel.com/products/processor/manual/325462.pdf 3-166 Vol. 2A
        // ebx contains 3 integers of 10, 10 and 12 bits respectively
        unsigned int cache_sets = ecx + 1;
        unsigned int cache_coherency_line_size = (ebx & 0xFFF) + 1;
        unsigned int cache_physical_line_partitions = ((ebx >>= 12) & 0x3FF) + 1;
        unsigned int cache_ways_of_associativity = ((ebx >>= 10) & 0x3FF) + 1;

        // Total cache size is the product
        size_t cache_total_size = cache_ways_of_associativity * cache_physical_line_partitions * cache_coherency_line_size * cache_sets;

        printf(
            "Cache ID %d:\n"
            "- Level: %d\n"
            "- Type: %s\n"
            "- Sets: %d\n"
            "- System Coherency Line Size: %d bytes\n"
            "- Physical Line partitions: %d\n"
            "- Ways of associativity: %d\n"
            "- Total Size: %zu bytes (%zu kb)\n"
            "- Is fully associative: %s\n"
            "- Is Self Initializing: %s\n"
            "\n"
            , i
            , cache_level
            , cache_type_string
            , cache_sets
            , cache_coherency_line_size
            , cache_physical_line_partitions
            , cache_ways_of_associativity
            , cache_total_size, cache_total_size >> 10
            , cache_is_fully_associative ? "true" : "false"
            , cache_is_self_initializing ? "true" : "false"
        );
    }
}

Ссылка: http://download.intel.com/products/processor/manual/325462.pdf 3-166 Том. 2A

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

Редактировать: Добавлен дескриптор типа кэша. Edit2: добавлен индикатор уровня кэша. Edit3: добавлено больше документации.

Я знаю это! (На самом деле это очень сложно из-за предварительной выборки)

 for (times = 0; times < Max; time++) /* many times*/
     for (i=0; i < ArraySize; i = i + Stride)
           dummy = A[i]; /* touch an item in the array */

Изменение шага позволяет проверить свойства кешей. Глядя на график, вы получите ответы.

Посмотрите на слайды 35-42 http://www.it.uu.se/edu/course/homepage/avdark/ht11/slides/11_Memory_and_optimization-1.pdf

Эрик Хагерстен - действительно хороший учитель (и также очень компетентный, когда-то он был ведущим архитектором на солнце), так что посмотрите на остальные его слайды, чтобы получить более интересные объяснения!

Чтобы ответить на ваш вопрос о странных цифрах выше 1 МБ, это довольно просто; политики вытеснения кеша, связанные с предсказанием ветвлений и тем фактом, что кеш L3 распределяется между ядрами.

Ядро i3 имеет очень интересную структуру кэша. Фактически любой современный процессор делает. Вы должны прочитать о них в Википедии; есть множество способов решить: "ну, я, вероятно, не буду нуждаться в этом...", тогда он может сказать: "Я положу это в кэш жертвы" или любое другое. Время кэширования L1-2-3 может быть очень сложным из-за большого количества факторов и индивидуальных решений, принятых при проектировании.

Кроме того, все эти решения и многое другое (см. Статьи Википедии на эту тему) должны быть синхронизированы между кешами двух ядер. Методы синхронизации общего кэша L3 с отдельными кэшами L1 и L2 могут быть уродливыми, они могут включать в себя обратное отслеживание и повторные вычисления или другие методы. Маловероятно, что у вас когда-либо будет полностью бесплатное второе ядро, и ничто не будет конкурировать за пространство кэш-памяти L3 и, таким образом, вызывать странности синхронизации.

В общем, если вы работаете с данными, скажем, свертываете ядро, вы хотите убедиться, что оно вписывается в кэш L1, и сформировать свой алгоритм вокруг этого. Кэш L3 на самом деле не предназначен для работы с набором данных так, как вы это делаете (хотя он лучше основной памяти!)

Клянусь, если бы мне пришлось реализовывать алгоритмы кэширования, я бы сошел с ума.

Для бенчмаркинга с различными шагами вы можете попробовать lat_mem_rd из пакета lmbench, он с открытым исходным кодом: http://www.bitmover.com/lmbench/lat_mem_rd.8.html

Я разместил свой порт для Windows на http://habrahabr.ru/post/111876/ - это довольно долго, чтобы быть скопированным здесь. Это два года назад, я не тестировал его с современными процессорами.

Для окон вы можете использовать метод GetLogicalProcessorInformation.

Для Linux вы можете использовать sysconf(), Вы можете найти действительные аргументы для sysconf в /usr/include/unistd.h или же /usr/include/bits/confname.h

Другие вопросы по тегам