Объединяет ли оборудование несколько операций кода в одну физическую операцию ЦП?

Я прочитал статью 2006 года о том, как процессоры выполняют операции над целыми строками кэша l1, даже в тех случаях, когда вам нужно что-то делать только с небольшой долей того, что содержится в строке l1 (например, загрузка всей строки l1 для записи в логическое значение) переменная явно перебор). В статье поощряется оптимизация за счет управления памятью удобным для кеша способом.

Допустим, у меня есть два int Переменные, которые оказываются последовательными в памяти, и в моем коде я пишу им обоим последовательно.

Объединяет ли аппаратное обеспечение мои две операции кода в одну физическую операцию в одной строке l1 (при условии, что у ЦП есть строка кэша l1, достаточно большая, чтобы содержать обе переменные), или нет?

Есть ли способ предложить такую ​​вещь процессору в C ++ или C?

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

3

Решение

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

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

В случаях, когда несколько процессоров получают доступ к данным, может быть полезно выровнять данные. Использование C ++ alignas атрибут или расширение компилятора может помочь вам получить структуры данных, которые выровнены так, как вы хотите.

Вас может заинтересовать моя статья Переупорядочение процессора — Что на самом деле переупорядочивается? что дает некоторое представление о том, что происходит (по крайней мере, логически) на низком уровне.

5

Другие решения

Это довольно широкий вопрос, но я постараюсь осветить основные моменты.

Да, чтение данных в кеш ТОЛЬКО смотрите на одном bool это немного расточительно — однако процессор обычно НЕ ЗНАЕТ, что вы планируете делать после этого, если вам, например, нужно следующее последовательное значение или нет. Вы можете полагаться на данные, принадлежащие к одному и тому же классу или структуре, расположенные рядом / рядом друг с другом, поэтому использование этих данных для хранения данных, с которыми вы часто работаете вместе, друг с другом, даст вам это преимущество.

Что касается работы «более чем с одним фрагментом данных одновременно», большинство современных процессоров имеют различные формы расширений для выполнения одной и той же операции над несколькими элементами данных (SIMD — одна и та же инструкция, несколько данных). Это началось с MMX в конце 1990-х, и было расширено, чтобы включать 3DNow !, SSE и AVX для x86. В ARM есть расширение «Неон», которое также предоставляет аналогичную функциональность. У PowerPC также есть нечто подобное, чье имя ускользает от меня на данный момент.

У программ на C или C ++ нет возможности немедленно контролировать выбор инструкции или использование кэша. Но современные компиляторы, при правильных настройках, будут генерировать код, который, например, использует инструкции SIMD для суммирования всех int в большем массиве, добавляя 4 элемента за раз, а затем, когда весь лот сделан, горизонтально добавьте 4 значения. Или, если у вас есть набор координат X, Y, Z, он может использовать SIMD для добавления двух наборов таких данных вместе. Это выбор компилятора, но это то, что может сэкономить немало времени, поэтому оптимизаторы в компиляторе модифицируются, чтобы найти случаи, когда это помогает, и использовать эти типы инструкций.

И, наконец, большинство более крупных современных процессоров (x86 начиная с 1995 г., ARM A15, PowerPC) также выполняют суперскалярное выполнение — выполняя более одной инструкции за один раз, и с «выполнением не по порядку» (процессор понимает зависимости команд и выполняет те из них, которые «готовы» выполнить, не совсем в том порядке, в котором они были переданы процессору). Компилятор узнает об этом и попытается «помочь» расположить код так, чтобы процессор получил простую задачу.

2

Весь смысл кеширования состоит в том, чтобы быстро выполнять множество операций с сильно локализованной памятью.

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

Для упрощения по сравнению с реальными процессорами рассмотрим гипотетический процессор, работающий на частоте 2 ГГц (0,5 нс на такт), с памятью, которая занимает 5 нс для загрузки произвольного 64-битного (8-байтового) слова памяти, но только 1 нс для загрузки каждого 64-битное слово из памяти. (Предположим также, что записи аналогичны.) На такой машине переворот в памяти происходит довольно медленно: 1 нс для загрузки инструкции (только если она еще не находится в конвейере — но 5 нс после удаленной ветви), 5 нс загрузить слово, содержащее бит, 0,5 нс для выполнения инструкции и 5 нс для записи измененного слова обратно в память. Копия памяти лучше: приблизительно ноль для загрузки инструкций (поскольку конвейер предположительно делает правильные действия с циклами инструкций), 5 нс для загрузки первых 8 байтов, 0,5 нс для выполнения инструкции, 5 нс для хранения первых 8 байтов и 1 + 0,5 + 1 нс для каждых дополнительных 8 байтов. Местность облегчает жизнь. Но некоторые операции могут быть патологическими: увеличение каждого байта массива приводит к начальной загрузке 5 нс, инструкции 0,5 нс, сохранению начальных 5 нс, а затем 1 + 0,5 + 1 на байт (а не на слово) после этого. (Копия памяти, которая не попадает в границы того же слова, также является плохой новостью.)

Чтобы ускорить этот процессор, мы можем добавить кэш, который улучшает загрузку и сохраняет всего 0,5 нс за время выполнения инструкции для данных, находящихся в кеше. Копия памяти не улучшается при чтении, поскольку она все еще стоит 5 нс для первых 8-байтовых операций и 1 нс для дополнительных слов, но запись происходит намного быстрее: 0,5 нс для каждого слова, пока кэш не заполняется, и при нормальном 5 + 1 + 1 и т. Д. Скорость после его заполнения, параллельно с другой работой, которая использует меньше памяти. Приращение байтов улучшается до 5 нс для начальной загрузки, 0,5 + 0,5 нс для инструкции и записи, затем 0,5 + 0,5 + 0,5 нс для каждого дополнительного байта, за исключением случаев остановки кэша при чтении или записи. Более частое повторение одних и тех же адресов увеличивает долю попаданий в кэш.

Что происходит с реальными процессорами, несколькими уровнями кэша и т. Д.? Ответ прост: все становится сложнее. Написание кода, поддерживающего кэш, включает в себя попытки улучшить локальность доступа к памяти, анализ, чтобы избежать перегрузки кеша, и много профилирования.

2
По вопросам рекламы [email protected]