Обсуждение:Эльбрус/портирование

Материал из ALT Linux Wiki

t.me/e2k_chat

Леонид Юрьев

Могу ошибаться, но насколько помню в lcc 1.23 было всё до AVX2 (включая SSE4, AES-NI и AVX).
А в 1.25 добавили AVX2 и что-то еще.
Но важно понимать, что это эмуляция средствами компилятора.
Т.е. lcc вставляет снипеты кода и потом их оптимизирует.
Поэтому:
- на e2k доступность этих фичей зависит от компилятора, а не от целевой модели процессора.
- вовсе не гарантируется что некие вычисления с AVX2 будут быстрее AVX и т.д.
- реальная производительность в тактах будет определяться версией компилятора и целевым процессором.

Так или иначе, в случае с lcc достаточно штатно использовать gcc-шные версии этих макросов, т.е. #ifdef __AVX__ и т.п.

--mike (обсуждение) 13:04, 19 ноября 2020 (UTC)

Николай

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

Как создать такое взаимодействие? Индивидуальный подход или совпадение -
не знаю.

Ну, лично мне ещё чай подарили. Вкусный. Но не гаиджиновцы.
Я бы не сказал, что это помогло с приоритетами, но было приятно

--mike (обсуждение) 22:24, 18 декабря 2020 (UTC)

работы по портированию

Дмитрий Щербаков

Рубрика "Распространённые грабли при низкой производительности на Эльбрусе" или просто "Вредные советы".

Заметил на нескольких примерах ещё одну особенность. Не готов сказать, как на других архитектурах и как должно быть, мои знания тонкостей Си и возможностей компилятора ограничены, к сожалению.

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

Если подробнее, рассмотрим некоторую функцию вида

void foo(uint64_t *a, uint64_t *b, uint64_t *c)

Каждый передаваемый массив — небольшого фиксированного размера, например, 4. Функция внутри себя как-то обрабатывает данные и в конце сохраняет результаты в те же массивы. Дополнительных локальных переменных мало, не больше 5, например. Есть 2 тактики реализации такой функции:
1) писать как есть, промежуточные результаты записывая прямо в a[0]-a[3]...
2) или можно создать 12 локальных переменных, скопировать в них значения из массивов в начале функции, провести на локальных переменных вычисления, а в конце записать результаты из локальных переменных по указателям.

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

Проверил поведение на lcc 1.23-1.26, это верно для всех версий (хотя новые и быстрее обычно). Результат может быть немного искажён фактом, что вместо варианта 2 я в последнее время рассматривал вариант:
3) макрос вместо функции.

Но идея остаётся та же — нельзя ли отказаться от записи промежуточных значений?

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

alexanius:

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

Если блок памяти всегда малый, то возможно имеет смысл либо сделать версию функции с параметрами-не указателями.

Как вариант, можно сделать структуру с полями-массивами (не уверен на сколько поможет, но шанс есть).

Кроме того, действительно компилятор не знает пересекаются участки памяти или нет, поэтому лучше добавить __restrict к описанию параметров.