Есть ли способ проверить, был ли кэш процессора недавно очищен?

#linux #cpu #flush #hardware-interface #cpu-cache

#linux #процессор #очистка #аппаратный интерфейс #cpu-cache

Вопрос:

В i386 Linux. Предпочтительно на c / (c / posix std libs) / proc, если это возможно. Если нет, есть ли какая-либо часть сборки или сторонняя библиотека, которая может это сделать?

Редактировать: Я пытаюсь разработать тест, очищает ли модуль ядра строку кэша или весь процесс (с помощью wbinvd()). Программа запускается от имени root, но я бы предпочел оставаться в пользовательском пространстве, если это возможно.

Комментарии:

1. Вы имеете в виду кэш процессора? И сколько точно времени прошло в последнее время?

2. Намного меньше секунды (я знаю, что это не очень точно). В принципе, я хочу проверить, очищала ли кэш довольно быстрая функция.

3. Вопрос не имеет смысла: (а) обычно существует несколько кэшей, (б) строки кэша постоянно удаляются, (в) «очистка» кэша (т. Е. Удаление всех строк кэша) обычно никогда не происходит, (г) в целом центральный процессор не имеет представления о том, что происходит ни в одном из кэшей

4. Это может быть необычно, но очистка — это то, что делается в некоторых местах, нет?

5. @Roman: да, WBINVD очищает все кэши данных (неясно, очищал ли он также кэш команд L1, и его реализация зависит от процессора), но использовать эту инструкцию довольно необычно (единственный пример, который приходит на ум, — самоизменяющийся код), и даже в этом случае кэши немедленно начнут заполняться снова, и процессор не имеет прямого представления о текущем состоянии любого из кэшей. Вы должны объяснить, чего вы действительно пытаетесь достичь, т. Е. мотивацию вашего вопроса.

Ответ №1:

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

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

Вот результат:

потребовался 81 тик 
потребовался 81 тик 
очистка: заняла 387 тактов 
потребовалось 72 такта

Вы видите 3 попытки: первая гарантирует, что i он находится в кэше (что так и есть, потому что он был просто обнулен как часть BSS), вторая — это чтение i , которое должно быть в кэше. Затем кэш clflush выгружается i (вместе со своими соседями) и показывает, что его повторное чтение занимает значительно больше времени. Окончательное чтение подтверждает, что он вернулся в кэш. Результаты очень воспроизводимы, и разница достаточно существенна, чтобы легко увидеть промахи в кэше. Если бы вы позаботились о калибровке накладных расходов rdtsc() , вы могли бы сделать разницу еще более заметной.

Если вы не можете прочитать адрес памяти, который хотите протестировать (хотя для этих целей должен работать даже mmap of /dev/mem ), вы можете сделать вывод, что вам нужно, если вы знаете размер строки кэша и ассоциативность кэша. Затем вы можете использовать доступные ячейки памяти для проверки активности в интересующем вас наборе.

Исходный код:

 #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));
    return a | ((uint64_t)d << 32);
}

volatile int i;

inline void
test()
{
    uint64_t start, end;
    volatile int j;

    start = rdtsc();
    j = i;
    end = rdtsc();
    printf("took %lu ticksn", end - start);
}

int
main(int ac, char **av)
{
    test();
    test();
    printf("flush: ");
    clflush(amp;i);
    test();
    test();
    return 0;
}
  

Комментарии:

1. Спасибо, сначала это выглядело хорошо, но в итоге оказалось слишком неточным. возможно, из-за проблем в моем коде.

2. @Ben Есть ли какие-либо особые требования (компиляция, системные настройки и т.д.), Которые необходимо выполнить, чтобы заставить этот код работать? Я получаю в строке «flush:» отметки в том же порядке величины, что и обращения к кешированию. Я использую macOS 10.15.5 с gcc-9 и clang 11 (оба протестированы).

Ответ №2:

Я не знаю ни одной общей команды для получения состояния кэша, но есть способы:

  1. Я думаю, это самый простой: если вы получили свой модуль ядра, просто разберите его и найдите команды аннулирования / очистки кэша (atm. мне на ум пришли только 3: WBINDVD, CLFLUSH, INVD).
  2. Вы только что сказали, что это для i386, но я предполагаю, что вы не имеете в виду 80386. Проблема в том, что существует множество различных устройств с разным расширением и функциями. Например, в новейшей серии Intel включены некоторые регистры производительности / профилирования для системы кэширования, которые вы можете использовать для оценки промахов кэша / попаданий / количества передач и тому подобного.
  3. Аналогично 2, очень зависит от используемой вами системы. Но если у вас многопроцессорная конфигурация, вы могли бы просмотреть первый протокол согласованности кэша (MESI) со вторым.

Вы упомянули WBINVD — afaik, который всегда будет очищать полные, то есть все, строки кэша

Комментарии:

1. У меня есть исходный код, я пытаюсь написать тест, чтобы подтвердить, что происходит при определенных условиях. Atom как двухъядерный, так и одноядерный.

2. Если у вас есть исходный код, не могли бы вы просто просмотреть его, чтобы проверить, выдавалась ли когда-либо инструкция WBINVD?

3. 1 за идеи для ПК — Счетчики производительности могли бы предоставить информацию, которую запрашивает вопрос.

4. jalf: 1) Я пытаюсь выполнить полуавтоматическое тестирование, а не проверку кода (и код может измениться, надеюсь, тест все еще будет работать). 2) WBINVD вызывается в некоторых местах на основе политики. Я пытаюсь протестировать различные условия, чтобы увидеть, вызывается ли он.

Ответ №3:

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

На самом деле это может быть более эффективным, чем пытаться запросить у процессора информацию, которая может существовать, а может и не существовать, и на которую, вероятно, повлияет ваш простой вопрос об этом — да, Гейзенберг был намного раньше своего времени 🙂