Когда-то программировал под ARM и там как раз такое решение - располагать константы в дырках между функциями. Но в данном случае компилятор не пользуется таким приемом, а кладет константу только за кодом.
Да. Код у меня расположен , начиная с 0-го адреса. Секция данных сразу за кодом. Размер кода при -О0 1244 байта, при -О2 1246 байт. Я специально для -О2 вырезал часть кода из ассемблерного стартапа , но компилятор не поменял решения.
LUI+ADDI это типовая склейка в одну команду у RISC-V. Вы правы в предположении.Загрузка константы в простом суперскалере на 2 команды за такт займет максимум 4 такта. Иногда компилятор делает LUI+ADDI не перемешивая. А в конце сдвиг и сложение. Теоретически сдвиг на 32 и сложение тоже можно склеить. Не знаю делают ли так, но в этом случае 3 такта.
Чтобы понять какой вариант быстрее нужен не только тест, но еще нужно выбирать и конкретную реализацию процессора. У меня такое впечатление , что компилятор отлаживают на каком-то доступном процессоре и под него все оптимизируется. Опять же Вы упомянули про кэш , а это тоже особенности реализации.
Обратите внимание, что одиночное использование константы дает накладные расходы минимум в 3 слова при загрузке из памяти. В случае встраивания константы в код максимум 5 слов. Если загрузка константы потребует полного формирования адреса команды (AUIPC+ADDI) , то это будут те же 5 слов при загрузке из памяти. Остается вопрос - почему при -О2 якобы более плохой код? Может это лучшее решение ? :)
Похоже я немного попутал.. Это у Вас пример программы и к загрузке имеет отношение только ld t0, pc + (value-start) В этом случае будет что-то похожее на код
Спасибо. Да, 5 тактов из рисунка 6. Но в новом документе видно что есть два варианта: приблизительно 5 и 10 тактов. 5 если через кэш L2. Если без него, то хуже получается. Рисунки 2 и 3 дают 3 такта из памяти L2. Получается 16/3=5.3 байта на такт. Из Ваших цифр ранее я считал так 4+7=11 на 64 байта. Итого 64/11 = 5.8 Практически одинаково.
Ваш тест в большей степени как раз тест на пропускную способность памяти. Шаг адреса равен 16. Если я не ошибаюсь, то получается скорость 5 тактов на чтение из любой память (префетч для SL2 + DDR). Итого 16/5 = 3.2 байта на такт.
Похоже что 16 байт за такт можно получить только когда 32К байт все сидят в кэше L1D. Если чуть больше данных, то далее все должно определяться задержкой подкачки линии из Prefetchera в L1D и тем, как шустро Prefetche будет подкачивать следующий пакет.
Подкачка работает только при обращении к внешней памяти (в XMC). 256 бит для L1D нарисовано потому, что 2х64 по чтению и 2х64 по записи. Ранее Вы написали задержку загрузки линии кэша.
Банки L1D это всего лишь расслоение по 8-ми словам. Ядро каждый такт будет читать 4 из них. IDMA будет писать 2 из них. Пересечение и конфликт неминуем. Правда, если тормозить будет процессор, а не IDMA, то на общее времени исполнения этого пинг-понга конфликты не должны повлиять.
При добавлении 2-го юнита в коде добавятся еще 4 инструкции и все это влезет в один пакет. Адрес в B12 = A12+8 и инкремент будет на 16 байт. Вроде так? 11 тактов на 64 байта это 5.8 байта на такт из памяти L2. Частный случай. У DSP обычно все красиво когда данные внутри какой-то удобной памяти. "Маленький" обьем для Эльбруса у Вас 7 млн байт :) .
Это не очень хороший прием. Трафик у IDMA в 2 раза ниже чем у ядра. Да еще к той же памяти, с которой работает ядро. Т.е. еще и с ядром будет конфликт доступа. Здесь (для сравнения) все лучше делать аналогично Эльбрусу: кэш L1 максимальный 32К, кэш L2 максимальный 512 К. Вместо кэша L3 внутреннюю 4М память.
Да. С этим девайсом было бы интереснее посоревноваться :) Надеюсь, что для случая FFT у Вас будут не только цифры Эльбруса, но и цифры TI. Хотя бы для С66.
Спасибо большое за такой развернутый ответ. Можно сделать вывод , что на С66 теоретически можно 16 байт за такт накапливать? При выравненных адресах загрузки.
Когда-то программировал под ARM и там как раз такое решение - располагать константы в дырках между функциями. Но в данном случае компилятор не пользуется таким приемом, а кладет константу только за кодом.
Да. Код у меня расположен , начиная с 0-го адреса. Секция данных сразу за кодом. Размер кода при -О0 1244 байта, при -О2 1246 байт. Я специально для -О2 вырезал часть кода из ассемблерного стартапа , но компилятор не поменял решения.
LUI+ADDI это типовая склейка в одну команду у RISC-V. Вы правы в предположении.Загрузка константы в простом суперскалере на 2 команды за такт займет максимум 4 такта. Иногда компилятор делает LUI+ADDI не перемешивая. А в конце сдвиг и сложение. Теоретически сдвиг на 32 и сложение тоже можно склеить. Не знаю делают ли так, но в этом случае 3 такта.
Чтобы понять какой вариант быстрее нужен не только тест, но еще нужно выбирать и конкретную реализацию процессора. У меня такое впечатление , что компилятор отлаживают на каком-то доступном процессоре и под него все оптимизируется. Опять же Вы упомянули про кэш , а это тоже особенности реализации.
Компилятор ведет себя по-разному в зависимости от ситуации. Пример ld_K_a0(0xFEDCBA9876543210);. При оптимизации -О0 компилятор дает код
При оптимизации -О2
Обратите внимание, что одиночное использование константы дает накладные расходы минимум в 3 слова при загрузке из памяти. В случае встраивания константы в код максимум 5 слов. Если загрузка константы потребует полного формирования адреса команды (AUIPC+ADDI) , то это будут те же 5 слов при загрузке из памяти. Остается вопрос - почему при -О2 якобы более плохой код? Может это лучшее решение ? :)
приведите пример "трудной" константы. Я напрягу компилятор.
Посмотрел что дает компилятор для константы в статье
lui a5,0x12345
addi a5,a5,1657 # 12345679
lui a0,0x90abd
slli a5,a5,0x20
addi a0,a0,-529 # ffffffff90abcdef
add a0,a0,a5
Похоже я немного попутал.. Это у Вас пример программы и к загрузке имеет отношение только
ld t0, pc + (value-start) В этом случае будет что-то похожее на кодauipc t0,0x0
addi t0,t0,26 # 110 <K_val>
ld t0,0(t0)
У Вас вызов и возврат, и неизвестно сколько придется на этом потерять. "Намного быстрее" хорошо бы привести на каком-то примере.
Посмотрите здесь https://textarchive.ru/c-2726176.html
Получается, что АРВ всегда начинает с L2. Но еще нужно знать стратегию по записи у L1. Возможно она сквозная.
Спасибо. Да, 5 тактов из рисунка 6. Но в новом документе видно что есть два варианта: приблизительно 5 и 10 тактов. 5 если через кэш L2. Если без него, то хуже получается. Рисунки 2 и 3 дают 3 такта из памяти L2. Получается 16/3=5.3 байта на такт. Из Ваших цифр ранее я считал так 4+7=11 на 64 байта. Итого 64/11 = 5.8 Практически одинаково.
Просто у меня доступ к полной статье запрещен :) А статья полезная.
НАшел интересную ссылку https://en.eeworld.com.cn/news/qrs/eic272697.html
Ваш тест в большей степени как раз тест на пропускную способность памяти. Шаг адреса равен 16. Если я не ошибаюсь, то получается скорость 5 тактов на чтение из любой память (префетч для SL2 + DDR). Итого 16/5 = 3.2 байта на такт.
Похоже что 16 байт за такт можно получить только когда 32К байт все сидят в кэше L1D. Если чуть больше данных, то далее все должно определяться задержкой подкачки линии из Prefetchera в L1D и тем, как шустро Prefetche будет подкачивать следующий пакет.
Подкачка работает только при обращении к внешней памяти (в XMC). 256 бит для L1D нарисовано потому, что 2х64 по чтению и 2х64 по записи. Ранее Вы написали задержку загрузки линии кэша.
Банки L1D это всего лишь расслоение по 8-ми словам. Ядро каждый такт будет читать 4 из них. IDMA будет писать 2 из них. Пересечение и конфликт неминуем. Правда, если тормозить будет процессор, а не IDMA, то на общее времени исполнения этого пинг-понга конфликты не должны повлиять.
При добавлении 2-го юнита в коде добавятся еще 4 инструкции и все это влезет в один пакет. Адрес в B12 = A12+8 и инкремент будет на 16 байт. Вроде так? 11 тактов на 64 байта это 5.8 байта на такт из памяти L2. Частный случай. У DSP обычно все красиво когда данные внутри какой-то удобной памяти. "Маленький" обьем для Эльбруса у Вас 7 млн байт :) .
Это не очень хороший прием. Трафик у IDMA в 2 раза ниже чем у ядра. Да еще к той же памяти, с которой работает ядро. Т.е. еще и с ядром будет конфликт доступа. Здесь (для сравнения) все лучше делать аналогично Эльбрусу: кэш L1 максимальный 32К, кэш L2 максимальный 512 К. Вместо кэша L3 внутреннюю 4М память.
Из Вашего замечания непонятно за кого бояться - что Эльбрус проиграет С66 или наоборот. Можете прояснить у кого на FFT нет шансов победить? :)
Да. С этим девайсом было бы интереснее посоревноваться :) Надеюсь, что для случая FFT у Вас будут не только цифры Эльбруса, но и цифры TI. Хотя бы для С66.
Да. Интерфейс к памяти узковат. Здесь еще очень интересен случай когда размеры массива превышают обьем кэша L1D..
Спасибо большое за такой развернутый ответ. Можно сделать вывод , что на С66 теоретически можно 16 байт за такт накапливать? При выравненных адресах загрузки.