LINUX.ORG.RU

Хочу выяснить пару вопросов про современные процессоры и компиляторы.

 , , omp,


0

1

Привет, ЛОР.

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

Библиотеку хочу сделать переносимой (чтоб работала и на микроконтроллерах/DSP и на х86-64, и т.д.), поэтому готовый код от вендоров в топку.

И тут возникли вопросы про векторно-матричные операции:

  • Я правильно понимаю, что для минимизации кеш-промахов надо сделать так, чтобы на соседних итерациях циклов было как можно меньше «скачков» указателей?

  • В каком виде лучше (с точки зрения оптимизации вычислений компиляторами) писать доступ к массивам?

Так:

#define _DO_MM(name, op1, op2)                                                             \
void name(libInt sr,  libInt scr, libInt sc, libFloat *res, libFloat *a, libFloat *b)      \
{                                                                                          \
    libFloat   *vr;                                                                        \
    libFloat   *vb;                                                                        \
    libInt      c;                                                                         \
    libInt      r;                                                                         \
                                                                                           \
    assert(res);                                                                           \
    assert(a);                                                                             \
    assert(b);                                                                             \
                                                                                           \
    while (0 < sr--)                                                                       \
    {                                                                                      \
        r  = scr;                                                                          \
        vb = b;                                                                            \
        c  = sc;                                                                           \
        vr = res;                                                                          \
        while (0 < c--)                                                                    \
        {                                                                                  \
            *vr++ op1 *a * *vb++;                                                          \
        }                                                                                  \
        a++;                                                                               \
                                                                                           \
        while (0 < --r)                                                                    \
        {                                                                                  \
            c = sc;                                                                        \
            vr = res;                                                                      \
            while (0 < c--)                                                                \
            {                                                                              \
                *vr++ op2 *a * *vb++;                                                      \
            }                                                                              \
            a++;                                                                           \
        }                                                                                  \
        res += sc;                                                                         \
    }                                                                                      \
}

_DO_MM(lib_mm,      =, +=)
_DO_MM(lib_add_mm, +=, +=)
_DO_MM(lib_sub_mm, -=, -=)

или Так:

#define _DO_MM(name, op1, op2)                                                             \
void name(libInt sr,  libInt scr, libInt sc, libFloat *res, libFloat *a, libFloat *b)      \
{                                                                                          \
    libInt      i;                                                                         \
    libInt      j;                                                                         \
    libInt      k;                                                                         \
                                                                                           \
    assert(res);                                                                           \
    assert(a);                                                                             \
    assert(b);                                                                             \
                                                                                           \
    for (i = 0; i < sr; i++)                                                               \
    {                                                                                      \
        for (k = 0; k < sc; k++)                                                           \
        {                                                                                  \
            res[sc*i + k] op1 a[scr*i] * b[k];                                             \
        }                                                                                  \
                                                                                           \
        for (j = 1; j < scr; j++)                                                          \
        {                                                                                  \
            for (k = 0; k < sc; k++)                                                       \
            {                                                                              \
                res[sc*i + k] op2 a[scr*i + j] * b[sc*j + k];                              \
            }                                                                              \
        }                                                                                  \
    }                                                                                      \
}

_DO_MM(lib_mm,      =, +=)
_DO_MM(lib_add_mm, +=, +=)
_DO_MM(lib_sub_mm, -=, -=)

З.Ы.: Больно не стукайте…

★★★★★

Последнее исправление: shkolnick-kun (всего исправлений: 1)

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

anonymous
()

Не хочу сказать, что переизобретение BLAS - абсолютно бесполезное занятие (мой коллега как-то обогнал одну не очень важную функцию из Intel MKL, так что это возможно), но замахиваться на то, чтобы обогнать вендорский BLAS на всех платформах без использования ассемблерных вставок - чересчур оптимистично.

Я правильно понимаю, что для минимизации кеш-промахов надо сделать так, чтобы на соседних итерациях циклов было как можно меньше «скачков» указателей?

Да. Cachegrind в помощь.

В каком виде лучше (с точки зрения оптимизации вычислений компиляторами) писать доступ к массивам?

Сравните. Я почти уверен, что с -O3 статистически значимой разницы между двумя вариантами кода может не оказаться.

anonymous
()

+1 к собственноручным бэнчам. Уповать, что компилятор сам сделает заебись не стоит, это точно(хоть тут и [url=https://www.linux.org.ru/forum/development/15725432?cid=15725481]кукарекают[/url] про «современные компиляторы», но это мамкины оптимизаторы тактов на I/O)

P. S. про флакона в первом каменте тоже верно

anonymous
()
Ответ на: комментарий от anonymous

замахиваться на то, чтобы обогнать вендорский BLAS на всех платформах без использования ассемблерных вставок - чересчур оптимистично

Там, где это должно будет работать, не то что BLAS, там даже стандартной библиотеки может не быть. Ну и см. пункт про переносимость.

Я почти уверен, что с -O3 статистически значимой разницы между двумя вариантами кода может не оказаться.

Тут пишут как во втором случае.

Я хочу понять, какая от этого польза в случае хитрого компилятора, который может делать SIMD, или какая от этого польза в случае, если перед внешним циклом вставить #pragma omp parallel for ?

shkolnick-kun ★★★★★
() автор топика
Последнее исправление: shkolnick-kun (всего исправлений: 2)

Библиотеку хочу сделать переносимой (чтоб работала и на микроконтроллерах/DSP и на х86-64, и т.д.), поэтому готовый код от вендоров в топку

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

Я правильно понимаю, что для минимизации кеш-промахов надо сделать так, чтобы на соседних итерациях циклов было как можно меньше «скачков» указателей?

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

  1. Количества уровней кэша
  2. Его размера на каждом уровне
  3. Его ассоциативности
fang
()
Ответ на: комментарий от fang

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

Максимальную не надо, надо «хорошую», т.е. дать компиляторам возможность оптимизировать код под целевые платформы, разные целевые платформы, с 1-2 уровневым кешем и с учетом возможных SIMD-опреаций.

У меня нет такого количества свободного времени, чтобы писать и поддерживать кучу прослоек совместимости.

shkolnick-kun ★★★★★
() автор топика

Я правильно понимаю, что для минимизации кеш-промахов надо сделать так, чтобы на соседних итерациях циклов было как можно меньше «скачков» указателей?

Не должно быть случайных скачков. Если у тебя скачки идут из предсказуемых адресов, ну скажем у тебя читается-пишется a[0] и a[10000]; потом a[1] и a[10001]; потом a[2] и a[10002] ... то у тебя в кеш попадет два куска этих.

Вот тут почитай https://stackoverflow.com/a/16699282 - там ссылок полно набросали

SZT ★★★★★
()

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

peregrine ★★★★★
()

Как ты выравниваешь обратные слеши в макросах? Вручную или …?

anonymous
()

Займись лучше общественно лорополезным делом - обнови нейромодератор.

anonymous
()

Перепиши лучше на булкохрусте с его божественно читабельными макросами.

anonymous
()
Ответ на: комментарий от anonymous

Это точно.

Ну, благо, либу я для своего хобби-проекта буду делать. Если кому ещё пригодится - милости прошу к нашему шалашу.

shkolnick-kun ★★★★★
() автор топика
Последнее исправление: shkolnick-kun (всего исправлений: 1)

Ща придёт царь и пояснит что доступ к кешу первого уровня идёт на частоте ядра, поэтому store buffer не нужен, но всё-таки нужен потому-то [дальше какая-то шизофазия].

anonymous
()
Ответ на: комментарий от shkolnick-kun

для своего хобби-проекта

Самобичевание - грех. Награда должна быть внешней. Гештальты/истерики надо закатывать перед благодарными зрителями (психоаналитик - это не «благодарный зритель»).

anonymous
()

ты общие подвыражения вынеси сначала ручками

res[sc*i + k] op2 a[scr*i + j] * b[sc*j + k]; 

вот тут в цикле изменятся только k, тогда как(если б компилятор не умел их выносить за тебя) - sc * i, a[scr * i+j], sc * j - не меняются и они бы вычислялись каждый раз. они и будут безрадостно вычисляться каждый раз в дебаг моде, в которой ты и будешь отлаживаться в основном. все что внутри цикла вычисляется и не меняется от итерации к итерации называется общим подвыражением и эти вычисления выносятся за пределы цикла, вычисляясь там, где они меняются. и не будет такого месива из индексов.

это

res[sc*i + k] op2 a[scr*i + j] * b[sc*j + k]; 

эквивалентно этому

res[A+k] op2 B * b[C+k]

проще выглядит? быстрее считается? да.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 2)
Ответ на: комментарий от alysnix

ты общие подвыражения вынеси сначала ручками

Дык второй вариант я тупо перевел из первого и запостил. В коде у меня первый вариант был (сначала сделал, потом начал думать, ЛОЛ).

А за совет - спасибо!

shkolnick-kun ★★★★★
() автор топика

Узнал много нового, что в макросы можно засовывать операторы. Является ли хорошей практикой делать макросы, в чем была необходимость, и чем это поможет сэкономить и каких ресурсов?

А по мне так промахи кэша - это непредсказуемость следующих адресов, лежать рядом оно не обязано, я так понимаю в процессорах кэширование идет по отдельным кускам, иное было бы странным, про a[0] и a[10000]; потом a[1] и a[10001]; потом a[2] и a[10002] - хороший пример

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от I-Love-Microsoft

Является ли хорошей практикой делать макросы, в чем была необходимость, и чем это поможет сэкономить и каких ресурсов?

Необходимость в генерации однотипных кусков кода. Это поможет меньше времени тратить на написание однотипных функций и избежать ошибок копипасты.

shkolnick-kun ★★★★★
() автор топика
Ответ на: комментарий от metawishmaster

ну… всегда можно поглумиться, что у «дерзкого и молодого» еще не отрасли тестикулы %))

Православная Сишечка на первом месте? Ouch. У некоторых будет разрыв шаблона. А че тред никто не создал еще?

1. C
2. Java
3. Python
...
...
18. Мертвый (как тут говорили) Perl
...
21. Rust
...
25. Cobol

Ouch x2 – Rust конкурирует с Cobol’ом за место под солнцем :)

anonymous
()
Ответ на: комментарий от anonymous

Ouch. У некоторых будет разрыв шаблона.

Да видимо только у тебя. Впервые tiobe увидел?

Тебе осталось понять, о чем этот рейтинг.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.