Ви точно отримуєте те, що я називаю кеш- резонансом . Це схоже на псевдонім , але не зовсім те саме. Дозволь пояснити.
Кеші - це апаратні структури даних, які витягують одну частину адреси та використовують її як індекс у таблиці, на відміну від масиву в програмному забезпеченні. (Насправді ми апаратно називаємо їх масивами.) Масив кешу містить рядки кешу даних та теги - іноді один такий запис на індекс у масиві (пряме відображення), іноді кілька таких (асоціативність набору N-шляхів). Другу частину адреси витягують і порівнюють із тегом, що зберігається в масиві. Разом індекс та тег однозначно ідентифікують адресу пам’яті кеш-лінії. Нарешті, решта адресних бітів визначає, які байти в рядку кешу адресуються, разом із розміром доступу.
Зазвичай індекс і тег - це прості бітові поля. Тож адреса пам'яті виглядає так
...Tag... | ...Index... | Offset_within_Cache_Line
(Іноді індекс і тег є хешами, наприклад, кілька XOR інших бітів у біти середнього діапазону, які є індексом. Набагато рідше, іноді індекс і рідше тег, - це такі речі, як прийняття адреси рядка кешу за модулем a Просте число. Ці більш складні обчислення індексу - це спроби боротися з проблемою резонансу, яку я поясню тут. Усі страждають певною формою резонансу, але найпростіші схеми вилучення бітового поля мають резонанс на загальних шаблонах доступу, як ви вже знайшли.)
Отже, типові значення ... існує безліч різних моделей "Opteron Dual Core", і я не бачу тут нічого, що вказує, яка саме у вас є. Вибравши навмання, останнє керівництво, яке я бачу на веб-сайті AMD, Посібник розробника Bios і ядра (BKDG) для сімейних моделей AMD 15h 00h-0Fh , 12 березня 2012 р.
(Сімейство 15h = сімейство бульдозерів, найновіший процесор високого класу - BKDG згадує двоядерність, хоча я не знаю номер продукту, який саме ви описуєте. Але, як би там не було, однакова ідея резонансу стосується всіх процесорів, просто такі параметри, як розмір кешу та асоціативність, можуть дещо відрізнятися.)
З стор.33:
Процесор AMD Family 15h містить 16-Кбайтний 4-провідний кеш-пам'ять L1 з двома 128-бітними портами. Це кеш-пам'ять, що підтримує до двох 128 байтних навантажень за цикл. Він розділений на 16 банків, кожен шириною 16 байт. [...] Тільки одне завантаження може бути виконане з даного банку кешу L1 за один цикл.
Підсумовуючи:
64-байтовий рядок кешу => 6 зміщених бітів у рядку кешу
16KB / 4-way => резонанс 4KB.
Тобто біти адреси 0-5 є зміщенням лінії кешу.
Рядки кешу 16KB / 64B => 2 ^ 14/2 ^ 6 = 2 ^ 8 = 256 рядків кешу в кеші.
(Виправлення: спочатку я прорахував це як 128. що я виправив усі залежності.)
4 способи асоціативного => 256/4 = 64 індексу в масиві кешу. Я (Intel) називаю ці "набори".
тобто ви можете розглядати кеш як масив із 32 записів або наборів, кожен запис містить 4 рядки кешу та їх теги. (Це складніше, ніж це, але це нормально).
(До речі, терміни "набір" та "спосіб" мають різні визначення .)
є 6 бітів індексу, біти 6-11 за найпростішою схемою.
Це означає, що будь-які рядки кешу, які мають абсолютно однакові значення в бітах індексу, бітах 6-11, будуть зіставлені з однаковим набором кешу.
А тепер подивіться на свою програму.
C[dimension*i+j] += A[dimension*i+k] * B[dimension*k+j];
Цикл k - це внутрішній цикл. Основний тип - подвійний, 8 байт. Якщо розмір = 2048, тобто 2K, то послідовні елементи, до яких B[dimension*k+j]
звертається цикл, будуть складати 2048 * 8 = 16K байт. Всі вони зіставляться з одним і тим же набором кешу L1 - вони всі матимуть однаковий індекс у кеші. Це означає, що замість того, щоб у кеші було 256 рядків кешу, доступних для використання, буде лише 4 - "4-напрямкова асоціативність" кешу.
Тобто ви, мабуть, будете пропускати кеш кожні 4 ітерації навколо цього циклу. Не добре.
(Насправді все трохи складніше. Але вищевказане є першим хорошим розумінням. Адреси записів B, згадані вище, є віртуальними адресами. Отже, можуть бути дещо інші фізичні адреси. Більше того, Bulldozer має спосіб передбачуваного кешу, ймовірно, використовуючи біти віртуальних адрес, щоб йому не довелося чекати перекладу віртуальної на фізичну адресу. Але, у будь-якому випадку: ваш код має "резонанс" 16K. Кеш даних L1 має резонанс 16K. Не добре .)]
Якщо ви трохи зміните розмір, наприклад, на 2048 + 1, тоді адреси масиву B будуть розподілені по всіх наборах кешу. І ви отримаєте значно менше помилок кешу.
Це досить поширена оптимізація підкладання масивів, наприклад, для зміни 2048 на 2049, щоб уникнути цього srt-резонансу. Але "блокування кешу - це ще важливіша оптимізація. Http://suif.stanford.edu/papers/lam-asplos91.pdf
Окрім резонансу лінії кеш-пам'яті, тут діють і інші речі. Наприклад, кеш-пам'ять L1 має 16 банків, кожен шириною 16 байт. Якщо розмірність = 2048, послідовний доступ до B у внутрішньому циклі завжди надходитиме до одного і того ж банку. Отже, вони не можуть пройти паралельно - і якщо доступом A стане той самий банк, ви програєте.
Я не думаю, дивлячись на це, що це таке велике, як резонанс кешу.
І так, можливо, може відбуватися псевдонім. Наприклад, STLF (Store To Load Forwarding buffers), можливо, порівнює лише за допомогою невеликого бітового поля і отримує помилкові збіги.
(Насправді, якщо задуматися, резонанс у кеші схожий на псевдонім, пов’язаний із використанням бітових полів. Резонанс викликаний кількома лініями кешу, що відображають один і той же набір, а не поширюються по колу. біт.)
Загалом, моя рекомендація щодо тюнінгу:
Спробуйте заблокувати кеш без подальшого аналізу. Я кажу це, тому що блокувати кеш легко, і дуже ймовірно, що це все, що вам потрібно було б зробити.
Після цього використовуйте VTune або OProf. Або Cachegrind. Або ...
А ще краще - скористатися добре налаштованою бібліотечною процедурою для множення матриць.
O(n^3)
.