Comments 88
а где вы взяли компилятор и как собирали его под Эльбрус
Всё уже было установлено в системе. Я ничего не ставил.
Писал код в npp, заливал через winscp.
я наверное буду выглядеть как красноглазый извращенец для большинства, но рекомендую разучить tmux + vim. В этом случае комфортно писать и компилировать сразу на удаленном устройстве. Самое странное чем я занимался это программировал прямо на компьютере Lego ev3, где 32МБ ОЗУ и на процессоре MIPS Комдив одноядерном 200МГц.
Ну и на десятках процессоров пожирнее. Практически это означает закинуть три файла .bashrc, .vimrc, .tmux.conf и можно начать работать. Если процессор тянет то можно еще в два действия накатить все привычные плагины vim.
Но всё это, конечно, больно для освоения.
В состав Эльбрус Linux и AltLinux входит компилятор lcc, который по интерфейсу совместим с gcc.
кроссовый lcc есть на dev.mcst.ru, нативный -- в альте
PS: автор, спасибо за статью -- добавил на вики; что меня в своё время поразило -- так это возможность напрямую передать обратную связь разработчикам материнской платы, могу и с разрабатывающими систему команд следующего (точнее, восьмого) поколения состыковать через человека, который запрашивал наше мнение
а инструкции такого ассемблера тоже в поставку входят с описаниями? можно будет генерировать ассемблер например используя Эльбрус или полагаться только на lcc?
типо jit какой-нибудь, java например запуститься в Эльбрусе?
Погуглите, java уже 100 лет работает на Эльбрусе. И тесты есть...
а можно запуститься в виртуалке, чтобы запустить пример из этой статьи в виртуалке не имея реального Эльбруса интересно
Разработчики Эльбруса оперативно дают к нему SSH-доступ по запросу вот тут: https://dev.mcst.ru/access/
Портирование и поддержка Java (OpenJDK) на Эльбрус (e2k) осуществляется компанией УНИПРО c 2011 года.
Не из-за таких сложностей itanium сдох?
Он сдох из-за того, что банально не был совместим с прежним 32-битным софтом и оказался невостребованным. Именно из-за этой глупости Intel, архитектура называется AMD64.
Эльбрус конечно выглядит интересно, но учитывая, что он едва ли может конкурировать по цене с решениями на x86 или ARM, не думаю, что его не постигнет судьба Itanium.
сдох из-за того, что банально не был совместим с прежним 32-битным софтом
"A key feature of the Itanium architecture is IA-32 instruction set compatibility" - Intel Itanium Architecture Software Developer’s Manual.
"A key feature of the Itanium architecture is IA-32 instruction set compatibility"
Относитесь к этому как к авторской гиперболе. Формально там совместимость была, так же, как как и в Эльбрусе. По эффективности - тоже работала аналогично Эльбрусу.
Нет. Это в Itanium 2 добавили (и то через слой совместимости емнип) - родные инструкции несовместимы, и это логично: от ужасов наследия x86 хотели избавиться. Недооценили, однако, количество софта, который в бинарном виде - нельзя просто взять и перекомпилировать.
Нет. Это в Itanium 2 добавили
Не, аппаратной совместимостью Intel хвалится в даташите и на 1 поколение (которое "800 MHz and 733 MHz"). Эмуляция в винде там тоже должна быть, потому что про неё Microsoft в августе 2001 пишет (за год до второго итаника).
Скорее уж в Itanium 2 ничего не меняли в этом аспекте.

_____
Не порядок, что бенчмарков последних итаников так и не появилось. Itanium 9500/9700.
Ну собственно, вы сами же и подтвердили: возможности использовать 32-битный софт на Итаниумах не было. Не в том смысле, что софт вообще никак не запускался, а в том смысле, что конфигурация с 32-битным софтом и Итаниумом не была работоспособна. Никто, кому надо было бы запускать в продакшене софт х86, не стал бы покупать овердорогой серверный Итаниум, чтобы получить производительность второго пня образца 1998 (а если в реальности, а не по брошюре производителя, то первого пня образца 1996).
не стал бы покупать овердорогой серверный Итаниум, чтобы получить производительность второго пня образца 1998
Да кто их знает, они же всё-таки купили Itanium, который изначально не впечатлял. Хотел добавить, что итаник сразу понимал SSE2, но... это ухудшало производительность, а не улучшало (обзор из начала 2001).
ixbt в 2005 тестировал Itanium2 1.3GHz/3MB. Говорит, что до третьего пня падало.
SPEC CPU2000
SPECint/fp_base2000: 1017*/1979 (IA-64)
SPECint/fp_base2000: 306/170 (-70%/-91%) (HW IA-32)**
SPECint/fp_base2000: 569/530 (-44%/-73%) (SW IA-32)***
* "на уровне 2,8 ГГц процессора"
** "соответствует Pentium III 700/450 МГц"
*** "примерно уровень Pentium 4 1,7 ГГц"
____
Вообще, не факт, что между аппаратным слоем совместимости и программным "IA-32 EL" из ~2004 было что-то ещё. HP'шный "overview" из 2002 мог врать.
он сдох из-за конской цены и такой себе производительности на реальном коде (и во втором явно есть вина vliw).
совместимость не такую уж большую роль играет, вон apple несколько раз меняло архитектуру в своих пк, и живее всех живых.
Apple при этом каждый раз писала отличный практически 100% работоспособный эмулятор, чем Intel не озаботился.
А надо понимать, что унаследованная среда Intel - это не только сам по себе последний набор команд (32-разрядный на тот момент), но и окружение ОС, BIOS, особенности периферийных устройств, всякое легаси в виде реального режима и т.д. Даже в наше время от legacy BIOS и загрузки в реальном режиме x86 не могут полностью отказаться. Тем более это относится к серверам, которые волокут всякую специфическую и дорогостоящую периферию.
И самое главное - Apple сама управляет рынком систем на своих процессорах, чего Intel никогда не имел. У пользователей Apple просто нет выбора, переходить ли на новую архитектуру или оставаться на старой.
Даже в наше время от legacy BIOS и загрузки в реальном режиме x86 не могут полностью отказаться.
Это не легаси, а особенность архитектуры. x86 процессор для перехода в защищённый режим должен сперва съесть ряд подготовительной информации - таблицы дескрипторов, таблицы прерываний. Соответственно, он после старта должен находиться в каком-то состоянии, в котором он уже может выполнять код инициализации, но ещё не может работать со всеми активными функциями, требующими инициализацию. Вот, это роль этого состояния и взял на себя реальный режим.
Ну x86 - мягко говоря, не единственный процессор, имеющий таблицу дескрипторов, но только он загружает ОС таким сложным способом, через 16-разрядный режим. Обычно выполняется просто понижение привилегий.
Это не легаси, а особенность архитектуры
а в чём отличие? и да, intel не так давно прелагала отказаться от этой «особенности», именно как от ненужного legacy.
а в чём отличие?
Легаси - это нечто устаревшее и не нужное. А в данном случае пусть и старое, но переиспользованное под актуальную задачу. Убрать-то можно, но выигрыш никакой, кроме необходимости переписывать код инициализации в куче биосов, и держать актуальные кодовые ветки под платформы для старых моделей и под платформы для новых моделей.
Выигрыш в том, что разработчикам периферийных устройств не пришлось бы в 21 веке писать код инициализации в 16-разрядной модели и компилировать хрен знает чем. А также не пришлось бы под реальный режим тратить место на кристалле и коды в системе команд.
Проще было бы один раз переписать биосы (собственно, это и сделано в UEFI), чем трахаться с каждой новой платой контроллера.
Такие сложности будут с любым современным процессором, если задаться целью глубоко оптимизировать код.
Такие сложности будут с любым современным процессором, если задаться целью глубоко оптимизировать код.
Мне кажется, у VLIW (и Эльбруса в частности) здесь даже своеобразное преимущество имеется. На нём ты точно знаешь, что, как и где можно соптимизировать. Ты точно знаешь, что это придётся делать. Но так же точно ты знаешь, что процессор на это закономерно откликнется и покажет рост производительности, то есть твои старания не пропадут втуне.
Это заметно контрастирует с теперешними out-of-order speculative superscalar, где оптимизация уже напоминает игру в догонялки с компилятором и в напёрстки с процессором, поведение которых стали затачивать под высокоуровневые языки. В итоге ты смотришь на код и никогда не уверен: то ли его действительно можно оптимизировать, то ли просто железу сил не хватает, то ли компилятор свинью подложил, то ли сама задача сложная и нет смысла крохи собирать.
ты точно знаешь, что, как и где можно соптимизировать
знаешь, что процессор на это закономерно откликнется и покажет рост производительности
Гхм, с конца 1990-х к процессорам сбоку прикручивают SIMD-расширения, чтобы предсказуемо дробить числа - и где разница?
А там где высокоуровневая бизнес-логика с её нехорошей индирекцией, ветвлениями и промахами кэша - там на одном железе всё равно будет медленно, а в другом - всё равно быстро.
Гхм, с конца 1990-х к процессорам сбоку прикручивают SIMD-расширения, чтобы предсказуемо дробить числа - и где разница?
Оптимизация одним лишь SIMD не ограничивается. Хотя VLIW, по идее, и здесь обязан справляться лучше - попросту в силу числодробительной природы, выполняя по несколько SIMD-команд за такт.
А там где высокоуровневая бизнес-логика с её нехорошей индирекцией, ветвлениями и промахами кэша - там на одном железе всё равно будет медленно, а в другом - всё равно быстро.
Не могу согласиться. Здесь давно уже решает в первую очередь JIT, а не выраженная в железе стратегия исполнения. Для VLIW можно, например, соорудить всеобщий кэш профилировки на уровне всей операционной системы. А при первом исполнении скрипта собирать профиль, согласно которому затем сочинять готовый код своим же VLIW-компилятором. На уровень системы можно даже унести и сам этот JIT, и кешировать уже код, чтобы не сочинять его многократно. Концептуально это будет то же самое, что делает out-of-order speculative superscalar, но с куда более простым железом и меньшим обогревом вселенной.
Концептуально это будет то же самое, что делает out-of-order speculative superscalar, но с куда более простым железом и меньшим обогревом вселенной.
Если разных вариантов много - JIT под разные случаи раздует промахи в icache/itlb (которых в таком коде и при статической компиляции хватает). При этом OoO позволяет подстраиваться не только под сложное ветвление, но и разное время выполнения инструкций (скажем, загрузка из разных уровней памяти); с помощью JIT такое разрулить сложнее.
Но так же точно ты знаешь, что процессор на это закономерно откликнется и покажет рост производительности, то есть твои старания не пропадут втуне.
Это работает, если ваш процессор предназначен для выполнения одной задачи. А если их много параллельно выполняется? А VLIW-компилятор, он же вообще понятия не имеет, что там может быть в параллельных потоках. В этом случае процессор, который сам анализирует потоки выполнения и управляет загрузкой своих вычислительных блоков, куда предпочтительнее.
В этом случае процессор, который сам анализирует потоки выполнения и управляет загрузкой своих вычислительных блоков, куда предпочтительнее.
это вы про smt что ли? всё-таки 90% там от операционной системы зависит
это вы про smt что ли? всё-таки 90% там от операционной системы зависит
Это я хотя бы про гипертрейдинг как способ процессора более эффективно утилизировать свои вычислительные блоки за счёт двух одновременно выполняющихся контекстов потоков.
Это работает, если ваш процессор предназначен для выполнения одной задачи. А если их много параллельно выполняется?
Обычный out-of-order speculative superscalar точно так же сбрасывает конвейер, очищает TLB и лишается кэша при любом переключении контекста, теряя производительность.
Это я хотя бы про гипертрейдинг как способ процессора более эффективно утилизировать свои вычислительные блоки за счёт двух одновременно выполняющихся контекстов потоков.
А есть какие-то непреодолимые препятствия у гиперпоточности на VLIW? Мне кажется, что нет, и даже наоборот: "продавить" часть команд одной инструкции в пустые места другой заведомо проще, чем распиливать команды на уровне железа и распределять их. Команды инструкции по определению независимы, потому что VLIW, сами инструкции тоже независимы, потому что из разных задач.
а переносимость кода? так и не забывайте, если компилятору(а он процессору готовит) давать код который с окружением(высокого уровня - чисто сахар компилятора), мы ругаем эту технологию, а на деле она предсказывает переходы-то, но если убрать окружение конечно там сигфолт на сигфолте, но так никто не пишет же на языках высокого уровня
люди придумали новый ассемблер, прошло много времени чтобы оно заработало, так еще как я понимаю и нету 9 нанометров, а если будет то такой Эльбрус как сегодня уже не будет поддерживаться, потомучто доделают то чего ему не хватало, и тогда перед теми людьми встанет вопрос о портируемости
в истории уже были переходы, с PDP, на новый технологический уровень, и там как не странно всё теже вопросы
сюда же и где-то на хабре читал. оказывается было целое выступление на тему почему не еффективный код лучше чем goto, "premature optimisation"
а переносимость кода? так и не забывайте, если компилятору(а он процессору готовит) давать код который с окружением(высокого уровня - чисто сахар компилятора), мы ругаем эту технологию, а на деле она предсказывает переходы-то, но если убрать окружение конечно там сигфолт на сигфолте, но так никто не пишет же на языках высокого уровня
В идеале код под Эльбрус должен затачиваться директивами вроде #pragma, которые на других платформах и других компиляторах просто игнорировались бы. К тому же условную компиляцию никто не отменял, а asm-вставки у Взрослых Дядь встречаются и по сей день.
уже напоминает игру в догонялки с компилятором
не понял, какое это имеет отношение к vliw? что с ним, что без него, есть только три варианта (и их миксы): или тупой компилятор, выдающий неоптимизированный код, или слишком умный компилятор, генерирующий не тот код, что ты ожидаешь, или ручками на ассемблере
не понял, какое это имеет отношение к vliw? что с ним, что без него, есть только три варианта (и их миксы): или тупой компилятор, выдающий неоптимизированный код, или слишком умный компилятор, генерирующий не тот код, что ты ожидаешь, или ручками на ассемблере
Это имеет отношение к концепции. Современные процессоры как-то сами собой пришли к тому, что программист в подавляющем большинстве случаев не сможет написать asm-код лучше компилятора. Только вот VLIW подразумевал это же самое отношение изначально.
Основной вектор развития современных процессоров (OoO) скорее в том, что ни программист ни компилятор чаще всего не будут вылизывать код под конкретную микроархитектуру, так что процессор сам пытается проанализировать поток инструкций и загрузить свои порты. VLIW жёстко заточен на компилятор - в числодробильном коде это в прицнипе работает (и может быть эффективнее за счёт отказа от OoO логики), но в суровом энтерпрайзе, где код с кучей условных переходов через каждые несколько инструкций, эффективная статическая компиляция на уровне раскладки по портам выглядит нереально.
Основной вектор развития современных процессоров (OoO) скорее в том, что ни программист ни компилятор чаще всего не будут вылизывать код под конкретную микроархитектуру, так что процессор сам пытается проанализировать поток инструкций и загрузить свои порты.
Если программист пишет код как попало, то зачастую это такой код, которому скорость и не нужна. Скриптик там, чтобы файлы перебрать. В остальных случаях вылизывать код рано или поздно приходится. Только почему-то языкам вроде Rust ставить дотошность при разработке во главу угла вполне допускается, а вот VLIW следует непременно смириться и загнуться. Хотя "основной вектор развития современных процессоров" задан прежде всего необходимостью "бежать изо всех сил лишь бы оставаться на месте" - рынок в текущем положении уже не особо привечает любые новации в стратегии вычислений. А повторением пройденного у нас занимается Байкал, а не Эльбрус.
VLIW жёстко заточен на компилятор - в числодробильном коде это в прицнипе работает (и может быть эффективнее за счёт отказа от OoO логики), но в суровом энтерпрайзе, где код с кучей условных переходов через каждые несколько инструкций, эффективная статическая компиляция на уровне раскладки по портам выглядит нереально.
Мне это видится инженерной задачей, причём решаемой - например гиперпоточностью, о чём я сегодня уже писал чуть выше. Хотя да, не скрою, я лицо заинтересованное - уж сильно мне нравится процессор в духе consteval / comptime, ныне весьма модных.
В остальных случаях вылизывать код рано или поздно приходится.
По возможности люди стараются. Но по факту коммерческий софт с разными бинарниками под, скажем, Icelake и Sapphire Rapids - большая редкость, да и open source софт явно перекомпилируют под конкретное железо в основном в HPC центрах из Top 500. VLIW в этом плане отличается в основном отсутствием legacy - иначе ситуации, когда бежит софт, скомпилированный пару поколений процессоров назад (потому что "работает и не трогай"), тоже были бы типичны, при этом влияние на производительность заметно больше.
тогда надо без ускорений! запускать такой код, псевдокод.
Скрытый текст
int add(int a,int b){
return a+b;
}
int fib(int n) {
if (n < 1) {
return n;
}
// Это простой, неоптимизированный цикл while для стресс-теста ВМ
int a = 0;
int b = 1;
int temp = 0;
while (n > 1) {
temp = add(a,b);
//print(temp);
a = b;
b = temp;
n--;
}
return b;
}
int main() {
int result = 0;
int counter = 0;
int iterations = 100000; // 100 тысяч итераций
result=0;
while (counter < iterations) {
result = fib(92); // Вычисляем fib(24) в каждой итерации
counter++;
}
print(result);
print(counter);
return 0;
}
и запускать счетчик тактов, получается что это за счет влив будет быстрее!
моя вм например не нативная, которая без JIT даёт 1.3 милиарда тактов(в топовых вм будет около 900-1 милиарда тактов за счет лучшей организации работы с аргументами), самый наивнейший JIT проще него даже не знаю что можно написать, конечно быстрее просто на порядки быстрее, и вот тогда тут интересно сколько милисекунд это считается на Эльбрусе, сколько тактов!
понимаете, транзисторы и тепловыделение, архитектура, покупая архитектуру с ОоО, вы покупаете такую штуку, где расставили мебель определенным образом, и где в транзисторах заложены потери
можно пойти дальше как нащупать это, для того чтоб это нащупать и понять, можно начать проектировать виртуальную стековую машину, и подходя к целостности стека в момент вызова функции с аргументами начать вводить на фоне общего роста разрешений разрабу, начать ограничивать работу с аргументами, это на фоне того что аргументов может быть больше 3(просто сам факт возможности этого это целый сигнал я считаю, тоесть по общим критериям нужен же общий механизм(это очень важно в этом случае общий механизм предоставляет единый интерфейс доступа к возможности пересылки аргументов понимаете?) пересылки аргументов не ограничивающий разработчика понимаете?), предположим это первое что можно увидеть при проектировании,
а второе что можно увидеть, это то что реализация стека, а именно передача аргументов прямо пропорциональна выполнению процессором количества инструкций в тактах, да их может быть сотни миллионов, но в числе по времени это милиссекунды
глубоко и не нужно как я начинаю понимать, суть в том, что процессор имея ттх должен дать гарантию той коробки, грубо говоря того латенси в общем - отклик, всё остальное это рантайм, как на фрибсд, пофиксились - обновились, и стало легче планировщику, если уходить в дебри оптимизации это возврат к пдп поидее, самая красивая доступная оптимизация, как я увидел, это флаги и команды, но не тот доступ прям в железо, и поидее флаг натив по-хуже генерик, это уже о чем-то говорит
почему я так пишу, раньше когда я не вдавался в дебри этого, я смотрел на отклик мышки, и оказалось что это единственный доступный нюанс понять какой передо мной кристал, я двигаю мышкой система может переварить этот латенси или уже тут подлаги
Ну, автор фактически с нуля написал уже существующую функцию eml_vector_sum_8u библиотеки EML (Elbrus Media Library), аналог Intel MKL (Math Kernel Library), на e2k, и необходимости у рядового программиста так глубоко погружаться в оптимизацию нет.
Смерть Itanium’а — это интересное событие, в отношении которого есть несколько версий (теорий), начиная от озвученной вами и до версии, связанной с поражением во внутрикорпоративной борьбе главного покровителя Itanium в Intel Крейга Барретта, после смещения которого с поста CEO историю с Itanium’ом начали постепенно сворачивать.
Вот это я понимаю статья-подарочек под ёлку. По-моему настолько низкоуровнего погружения в программирование Эльбруса (e2k) ещё не было на Хабре.
спасибо!
и ждем статью про реализацию FFT!
еще такой вопрос, как LLM модели работают с данной архитектурой и asm? есть ли полное, но краткое описание для подгрузки в LLM?
вопрос вот к чему, столкнулся с низкоуровневыми вопросами реализации IP блоков на system verilog, загружал LLM свой код и pdf описание нужных мне IP блоков, после качественной подгрузки данных (но они должны быть не большими) LLM очень помогала с верификацией написанного кода модулей (своих и чужих) и поиском узких мест. Для новичков в новой архитектуре копилот на LLM был бы, возможно, полезен, но ньюансы архитектуры ей нужно как-то компактно подгрузить...
Спасибо огромное за статью
Ссылка от "тех.поддержки" :), вдруг будет интересно
https://gitflic.ru/project/e2khome/lccrt/blob?file=lib%2Firr%2Flccrt_jite2k.c&branch=master
В этом файле реализация интерпретатора на e2k-ассемблере некоторого "типового" байткода. У байткода очень "жирная" кодировка, но зато классический loop + switch оптимизирован по самую маковку.
Самый маразм во всей этой истории - это что ассемблер для этой платформы так и не документирован и поддержка предлагает изучать его по сгенерированному компилятором коду. Это вообще как?!🤦🏼♂️
Можно ещё тут почитать раздел 10.
Отличная статья. Хорошо , что Вы привели примеры кода для С66. Очень познавательно. Еще бы хорошо, если бы Вы далее добавляли и значения производительности на рассматриваемых алгоритмах и для С66. У него и частота схожая. Спасибо..
Это псевдокод для демонстрации идеи. Реальный код на C66 выглядит не так.
Вот так решается эта задача на TMS320C66
(скорость работы - 1 байт за такт; можно и быстрее, надо подумать наилучший вариант):
; A5 = адрес массива
; B0 = количество элементов массива (больше нуля)
; A0 = рабочий регистр (в него читаем байт)
; A1 = текущая сумма байтов
; Две вертикальные черты || означают, что данная инструкция запускается одновременно с предыдущей.
; После запуска инструкции ldbu данные придут в регистр A0 в конце 5 такта (первым тактом будем считать такт запуска ldbu).
zero A1
mvc B0, ILC
nop 3
SPLOOP 1
; --- start of sploop ---
ldbu .D1 *A5++, A0
nop
nop
nop
nop
SPKERNEL 5, 0
|| add .L1 A1, A0, A1
; --- end of sploop ---
Аналогичный вариант без SPLOOP (происходит примерно то же самое, но более явно):
; A5 = адрес массива
; B0 = количество элементов массива (больше нуля, кратно 6)
; A0 = рабочий регистр (в него читаем байт)
; A1 = текущая сумма байтов
; Две вертикальные черты || означают, что данная инструкция запускается одновременно с предыдущей.
; После запуска инструкции ldbu данные придут в регистр A0 в конце 5 такта (первым тактом будем считать такт запуска ldbu).
; Инструкция bdec примерно аналогична loop из x86. Передача управления на метку происходит через 5 тактов после bdec.
zero A0
zero A1
sub B0, 2
; --- start of loop ---
loop:
bdec .S2 loop, B0
|| ldbu .D1 *A5++, A0
|| add .L1 A1, A0, A1
ldbu .D1 *A5++, A0
|| add .L1 A1, A0, A1
ldbu .D1 *A5++, A0
|| add .L1 A1, A0, A1
ldbu .D1 *A5++, A0
|| add .L1 A1, A0, A1
ldbu .D1 *A5++, A0
|| add .L1 A1, A0, A1
ldbu .D1 *A5++, A0
|| add .L1 A1, A0, A1
; --- end of loop ---
add .L1 A1, A0, A1
add .L1 A1, A0, A1
add .L1 A1, A0, A1
add .L1 A1, A0, A1
add .L1 A1, A0, A1Вот такой вариант пока что надумал (сложение 4 байтов за такт).
(аналог SIMD64_1_x3_16bit, только в 1 вычислительный "поток"):
; A5 = адрес массива
; B0 = количество элементов массива (больше нуля)
; A4 = рабочий регистр (в него читаем 4 байта)
; A1:A0 = разреженные байты
; A3:A2 = текущая сумма байтов
zero A3:A2
mvc B0, ILC
nop 3
SPLOOP 1
ldw .D *A5++, A4
nop 4
unpkbu4 .S A4, A1:A0
SPKERNEL 6, 0
|| dadd .L A1:A0, A3:A2, A3:A2Алгоритм такой:
- (ldw) читаем 4 байта
- (unpkbu4) перед каждым байтом ставим нулевой байт (растянули в 8 байт)
- (dadd) сложили в счётчик (регистровая пара = местный аналог SIMD)
Здесь надо ещё дополнительно учесть переполнение (максимум 256 итераций) - обернуть в наружный цикл.
Плюс можно подключить второй DataPath (местный аналог второго ядра) - скорость ещё в 2 раза вырастет (будет 8 байт за такт).
Есть мысль всунуть сюда ещё инструкцию dotpu4на юните M (скалярное умножение двух 4-байтных векторов). Скалярно умножать 4 байта на 4 единицы = сумма четырёх байтов.
Получится такой алгоритм:
- читаем по 8 байт
- первые 4 байта обрабатываем через unpkbu4 + dadd
- вторые 4 байта обрабатываем через dotpu4
Если получится, будет ещё в 2 раза быстрее.
Спасибо большое за такой развернутый ответ. Можно сделать вывод , что на С66 теоретически можно 16 байт за такт накапливать? При выравненных адресах загрузки.
16 байт за такт это абсолютная верхняя граница, потому что там максимум можно 16 байт за такт читать (2 Datapath по 8 байт lddw).
Не уверен, что её можно достичь.

Да. Интерфейс к памяти узковат. Здесь еще очень интересен случай когда размеры массива превышают обьем кэша L1D..
В TMS320C66 есть EDMA (для пересылки данных из памяти в L2), IDMA (для пересылки данных из L2 в L1) и свой Prefetcher.
IDMA и Prefetcher я никогда не использовал.
EDMA - это обычный DMA: ему говорят откуда куда скопировать - он копирует.
Я догадываюсь, что можно отключить кэширование L1D и, с помощью IDMA, вручную организовать перекладку данных L2 <=> L1D
Это не очень хороший прием. Трафик у IDMA в 2 раза ниже чем у ядра. Да еще к той же памяти, с которой работает ядро. Т.е. еще и с ядром будет конфликт доступа. Здесь (для сравнения) все лучше делать аналогично Эльбрусу: кэш L1 максимальный 32К, кэш L2 максимальный 512 К. Вместо кэша L3 внутреннюю 4М память.
Насколько я понимаю, память L1D состоит из 8 банков по 32 бита (на рисунке выше). Доступ к разным банкам делается независимо. Пока ядро читает 16 байт (128 бит = 4 банка), в другие 4 банка можно одновременно писать.
Рассмотрим 2 варианта чтения данных:
- с помощью системы кэширования
- с помощью IDMA
1) Чтение из кэша L1D одной кэшевой линии (64 байта) в 2 datapath (2*8 байт за такт) займёт 4 такта. Плюс ещё 7 тактов задержки на подгрузку из кэша L2 в L1D.
Итого: 11 тактов на каждые 64 байта.
2) Если делать с помощью IDMA, то можно читать по 16 байт каждые 2 такта (максимальная скорость IDMA = 8 байт за такт).
Итого: 8 тактов на каждые 64 байта.
Но возни с этим будет значительно больше, чем с APB у Эльбруса.
Возможно, включение Prefetcher на C66 даст аналогичный эффект меньшими усилиями.

Банки L1D это всего лишь расслоение по 8-ми словам. Ядро каждый такт будет читать 4 из них. IDMA будет писать 2 из них. Пересечение и конфликт неминуем. Правда, если тормозить будет процессор, а не IDMA, то на общее времени исполнения этого пинг-понга конфликты не должны повлиять.
Ну мы ж напишем код так, чтобы ядро читало по 2 слова за такт.
А если конфликт, то там у ядра приоритет больше - IDMA подождёт.
Если хотим получить 16 байт за такт, остаётся надеяться на Prefetcher. Он должен догадываться подгружать данные из L2 в L1D заранее, до того, как мы запросим эти данные.
Нам надо подгружать по 2х64 бит за такт.
Канал между L2 и L1D нарисован в 256 бит. Должно хватить.
Из 4 банков по 32 бита читает ядро, в 4 других пишет Prefetcher.
Подкачка работает только при обращении к внешней памяти (в XMC). 256 бит для L1D нарисовано потому, что 2х64 по чтению и 2х64 по записи. Ранее Вы написали задержку загрузки линии кэша.
Как работает Prefetcher и IDMA, я не знаю.
Получается Prefetcher это про DDR3 => L2, а IDMA это про L2 => L1D. Правильно?
Или Prefetcher подгружает во все уровни кэша, но сами подгружаемые данные должны жить снаружи (не в L2)?
Дополнительную задержку в 7 тактов на чтение из L2 я получил экспериментально.
Если отключить кэш L1D, то чтение из L2 занимает 7 тактов.
Если не отключать кэш L1D, то чтение из L2 занимает 8 тактов.
Лучший вариант, который сумел придумать
(аналог SIMD128_1_x2_16bit, но в 1 вычислительный "поток").
Сложение 8 байт за такт одним DataPath (16 байт за такт в два DataPath).
Алгоритм:
- (lddw) читаем 8 байт
- (dmpyu4) перед каждым байтом ставим нулевой байт
- (dadd x2) складываем 16-битные слагаемые в 16-битные суммы
Максимум 256 итераций.
Обернуть в наружный цикл и скидывать суммы.
Получится примерно 270 тактов на 256 итераций.
; A12 = адрес массива
; B0 = количество групп по 8 байт (1..256)
; A1:A0 = рабочая регистровая пара (в неё читаем 8 байт)
; A3:A2 = константа 0x0101010101010101
; A7::A4 = разреженные байты (восемь 16-битных слагаемых)
; A9:A8 = первые четыре 16-битные суммы
; A11:A10 = вторые четыре 16-битные суммы
mvkl 0x01010101, A2
mvkh 0x01010101, A2
mvkl 0x01010101, A3
mvkh 0x01010101, A3
zero A9:A8
zero A11:A10
mvc B0, ILC
nop 3
SPLOOP 1
lddw .D *A12++, A1:A0
nop 4
dmpyu4 .M A1:A0, A3:A2, A7::A4
nop 3
SPKERNEL 9, 0
|| dadd .S A5:A4, A9:A8, A9:A8
|| dadd .L A7:A6, A11:A10, A11:A10При добавлении 2-го юнита в коде добавятся еще 4 инструкции и все это влезет в один пакет. Адрес в B12 = A12+8 и инкремент будет на 16 байт. Вроде так? 11 тактов на 64 байта это 5.8 байта на такт из памяти L2. Частный случай. У DSP обычно все красиво когда данные внутри какой-то удобной памяти. "Маленький" обьем для Эльбруса у Вас 7 млн байт :) .
Всё верно про добавление второго DataPath (не юнита, юниты это D,M,S,L). Надо будет параллельно продублировать строки цикла с заменой регистров "A" на "B". Получится примерно так:
mvc B0, ILC
nop 3
SPLOOP 1
lddw .D1 *A12++[2], A1:A0
|| lddw .D2 *B12++[2], B1:B0
nop 4
dmpyu4 .M1 A1:A0, A3:A2, A7::A4
|| dmpyu4 .M2 B1:B0, B3:B2, B7::B4
nop 3
SPKERNEL 9, 0
|| dadd .S1 A5:A4, A9:A8, A9:A8
|| dadd .S2 B5:B4, B9:B8, B9:B8
|| dadd .L1 A7:A6, A11:A10, A11:A10
|| dadd .L2 B7:B6, B11:B10, B11:B10Насчёт эффективной обработки "большого" объёма данных ничего не могу сказать.
Обычно данные передавались по DMA из DDR3 в L2 и далее через кэш L1D попадали в ядро. Ускорять этот способ не было необходимости, т.к. были другие места, где можно было ускориться проще и эффективнее. До ускорения подгрузки данных дело просто не дошло.
Исходно я использовал 7 млн байт (на самом деле 6 млн), думая, что это "большие" данные. Поэтому не сразу заметил, что 16-битные счётчики не дают большого ускорения. Ещё два нуля я стал дописывать потом.
В любом случае, у TMS320C66 верхняя граница это 16 байт на такт.
Если мы не придумаем, как использовать L1P для дополнительной поставки данных в ядро.
Генерировать инструкции с данными и поставлять их в L2 по DMA?
Но нужны будут свободные юниты для исполнения этих инструкций.
Если вообще такое можно реализовать.
а сколько будет тактов
Скрытый текст
int add(int a,int b){
return a+b;
}
int fib(int n) {
if (n < 1) {
return n;
}
// Это простой, неоптимизированный цикл while для стресс-теста ВМ
int a = 0;
int b = 1;
int temp = 0;
while (n > 1) {
temp = add(a,b);
//print(temp);
a = b;
b = temp;
n--;
}
return b;
}
int main() {
int result = 0;
int counter = 0;
int iterations = 100000; // 100 тысяч итераций
result=0;
while (counter < iterations) {
result = fib(1476); // < сейчас в коде инты но я прокинул в даблы
counter++;
}
print(result);
print(counter);
return 0;
}
но на дабл, например на 86-64 платформе в режиме интерпретации на стековой вм, 24 милиарда тактов, контрольное число
1.306989e+308 ответ
1.000000e+05 счетчик
Всего тактов: 23454630046 такты
ожидание ответа реально секунды!
У меня сейчас нет доступа ни к Эльбрусу, ни к TMS320C66.
На TMS320C66 я писал на ассемблере, на Си не писал.
На Эльбрусе можно было бы посмотреть.
Сходу я бы сделал такую оценку:
(10^5 вызовов fib) х (1476 циклов внутри fib) х (тактов 10-50 на каждый цикл)
Итого будет где-то (2..8) x 10^9 тактов.
Такты делим на частоту процессора - получаем секунды (никакой магии).
Рекурсии нет, доступа к памяти нет - подвохов никаких не видно.
Я бы предложил сделать рекурсивного Фибоначчи - у него время выполнения растёт экспоненциально:
fib(n) = fib(n-1) + fib(n-2)
как я понял фишка Эльбруса с барабаном это крутим указываемый регистр и пропускаем большое слово, поэтому там не 10 тактов, а поидее 1 такт как я понимаю на 1 проход цикла, по выравниваю дабла наверно
Всё зависит от того, как оно скомпилируется: будет ли честный вызов функции add или оно заинлайнится.
Другие части внутреннего цикла выполняются быстро.
Если делать честный вызов, то оно потребует 1 такт на вызов и 1 такт на возврат в лучшем случае. Наверно. Вызовы функций я не изучал.
В принципе, оно может в такое превратиться:
Ra = 0
Rb = 1
Rt = Ra + Rb
label:
{
loop label
Rt = Ra + Rb
abn
}Здесь abn вращает так: Rt -> Rb -> Ra -> Rt
Скопировал этот код в "fib.c", заменил int на double.
Добавил чтение счётчика тактов: uint64_t t0 = __builtin_ia32_rdtsc()
Собрал: gcc -O2 fib.c && gcc -S -O2 fib.c
Выдало примерно 470 000 000 тактов на i3-2120 (примерно по 3 такта на итерацию).
На современных x86-64 должно ещё быстрее работать.

В ассемблере цикл выглядит так:
.L5:
movapd %xmm0, %xmm2
subl $1, %edi
addsd %xmm1, %xmm0
movapd %xmm2, %xmm1
cmpl $1, %edi
jne .L5Я краем глаза натыкался на информацию о TMS320C71 (здесь, J721E DRA829/TDA4VM Processors Silicon Revision 2.0, 1.1 Technical Reference Manual (Rev. D)).


Заявлено 512-бит на DataPathB.
Один этот DataPath, теоретически, может выполнять сложение 64 байтов за такт.
Где бы раздобыть документацию на него?
Да. С этим девайсом было бы интереснее посоревноваться :) Надеюсь, что для случая FFT у Вас будут не только цифры Эльбруса, но и цифры TI. Хотя бы для С66.
Стоит всё же заметить, что соревнование будет достаточно странным: процессор общего назначения и DSP.
Давно я ждал статью про программирование на амсе для E2K, спасибо.
Вопрос: как инструкция abn определяет какие регистры надо вращать ? Или она вращает сразу весь регистровый файл числовых регистров ? Тогда почему в псевдокоде предполагается что в R10 сохраняется прежднее значение результата и как вращение влияет на другие регистры по которым происходит ссылка на ячейки памяти ? В известном Руководстве есть небольшая заметка про инструкцию abn, но из неё ничего не ясно.
Спасибо за достойную статью! Красиво, интересно и… страшно.
Спасибо большое за статью! Очень ценный материал!
Хочу научиться писать на ассемблере под Эльбрус. Материалов по этой теме маловато, и эта статья конечно поможет разобраться.
Моё знакомство с процессором Эльбрус-8СВ. Оптимизирую сложение массива байтов