Существует ли какое-либо обходное решение для "резервирования" доли кеша?
Предположим, что я должен написать вычислительную интенсивную функцию C или С++, которая имеет 2 массива в качестве входных данных и один массив в качестве вывода. Если вычисление использует 2 входных массива чаще, чем обновление выходного массива, я попаду в ситуацию, когда выходной массив редко кэшируется, потому что он выведен для извлечения 2 входных массивов.
Я хочу зарезервировать одну часть кэша для выходного массива и как-то принудительно обеспечить, чтобы эти строки не удалялись после их получения, , чтобы всегда записывать частичные результаты в кеше.
Update1(output[]) // Output gets cached
DoCompute1(input1[]); // Input 1 gets cached
DoCompute2(input2[]); // Input 2 gets cached
Update2(output[]); // Output is not in the cache anymore and has to get cached again
...
Я знаю, что существуют механизмы, помогающие выселению: clflush, clevict, _mm_clevict и т.д. Существуют ли какие-либо механизмы для противоположного?
Я думаю о трех возможных решениях:
- С помощью _mm_prefetch время от времени извлекать данные, если они были выселены. Однако это может привести к излишнему трафику плюс, что мне нужно быть очень осторожным, когда их вводить;
- Попытка выполнить обработку на небольших кусках данных. Однако это будет работать только в том случае, если проблема позволяет это;
- Отключение аппаратных префетов, где это возможно, чтобы уменьшить скорость нежелательных выселений.
Кроме этого, есть ли элегантное решение?
Ответы
Ответ 1
Процессоры Intel имеют что-то, что называется No Eviction Mode (NEM), но я сомневаюсь, что это то, что вам нужно.
Пока вы пытаетесь оптимизировать вторую (ненужную) выборку вывода [], вы подумали о том, чтобы использовать регистры SSE2/3/4 для хранения промежуточных выходных значений, обновлять их при необходимости и записывать их только тогда, когда все обновления, относящиеся к этой части выхода [], выполнены?
Я сделал что-то подобное при вычислении FFT (Fast Fourier Transforms), где часть вывода находится в регистрах, и они перемещаются (в память) только тогда, когда известно, что они больше не будут доступны. До тех пор все обновления происходят с регистрами. Для эффективного использования регистров SSE * вам необходимо ввести встроенную сборку. Конечно, такие оптимизации сильно зависят от характера алгоритма и размещения данных.
Ответ 2
Я пытаюсь лучше понять вопрос:
Если верно, что массив "output" строго предназначен для вывода, и вы никогда не будете делать что-то вроде
output[i] = Foo(newVal, output[i]);
тогда все элементы в выходе [] строго записываются. Если это так, все, что вам нужно было бы "зарезервировать", это одна линия кеша. Не правда ли?
В этом сценарии все записи в 'output' генерируют кеш-заливки и могут конкурировать с кэшами, необходимыми для массивов 'input'.
Разве вы не хотите, чтобы ограничение на вывод кешлайн могло потребляться в отличие от резервирования определенного количества строк.
Ответ 3
Я вижу два варианта, которые могут работать или не работать в зависимости от того, какой вы нацеливаете CPU, и о точном потоке программы:
-
Если output
записывается только и не читается, вы можете использовать потоковые хранилища, т.е. команду записи с подсказкой без чтения, поэтому она не будет выбрана в кеш.
-
Вы можете использовать предварительную выборку с подсказкой, не привязанной по времени (NTA) для input
. Я не знаю, как это реализовано в целом, но я точно знаю, что на некоторых процессорах Intel (например, Xeon Phi) каждый аппаратный поток использует определенный способ кеша для данных NTA, то есть с 8-сторонним кешем 1/8th на поток.
Ответ 4
Я предполагаю, что решение этого скрыто внутри, используемый алгоритм и размер кеша L1 и размер строки кеша.
Хотя я не уверен, насколько мы будем улучшать производительность.
Мы можем, вероятно, ввести искусственные чтения, которые умело уклоняются от компилятора и во время выполнения, также не мешают вычислениям. Одно искусственное чтение должно заполнять строки кеша, сколько необходимо для размещения одной страницы. Поэтому алгоритм должен быть модифицирован для вычисления блоков выходного массива. Что-то вроде тех, которые используются при матричном умножении огромных матриц, выполняемых с использованием графических процессоров. Они используют блоки матриц для вычисления и записи результата.
Как указывалось ранее, запись в выходной массив должна произойти в потоке.
Чтобы ввести искусственное чтение, мы должны инициализировать во время компиляции выходной массив в правильных местах, один раз в каждом блоке, возможно с 0 или 1.