W4FhLF IMHO, компилятору здесь банально не хватило регистров. Если надо быстрое умножение, то лучше не использовать операторы [] (ведь это умножение при доступе к каждому элементу, а проивольный доступ здесь не нужен), а заменить их на итераторы по столбцам и строкам.
W4FhLF Не верь глазам своим Использовать time-base анализ таким бесхитростно-наивным способом можно только на уровне дост.больших кусков кода, а на асм-уровне выборки скорее будут приходиться не на саму проблемную команду, а на следующую за ней. Например, если во время тормозной команды деления приходит прерывние на выборку, то проц не отменит деление, а подождет его завершения и выдаст прерывние на следующей команде. Так и тут - скорее всего остановы чаще приходятся на fmul, ожидающую загрузки операнда, а не на безобидную mov edx,[] PS: То, что со статической переменной "ничего не изменилось", лишний раз подтверждает, что дело не в ней, т.к. ее адрес уж точно сидит в кэше, поскольку к нему происходит обращение в каждом цикле и соотв-щая линейка кэша не может быть вытеснена другими данными
Спасибо за ответы ребята. green, я думаю сегодня попробовать переписать процедуру и обращаться не к элементам отдельно, а к столбцам и строкам целиком. leo, но почему так получается, что во втором случае fmul настолько медленнее в конечном итоге работает? И что тогда делать?
Кстати, ещё одна особенность. На стационарном компе(как раз на том, на котором я проводил профайлинг) у меня стоит Athlon 64 x2 4000+ 2.2 ГГц, а на ноуте Core 2 Duo P8400 2.26 ГГц. На ноуте этот алгоритм работает в несколько раз быстрее при прочих равных условиях. Я думаю, что это связано как раз с объёмом кеша или частотой памяти(на ноуте DDR3).
W4FhLF Для ускорения здесь можно использовать SSE (mulps, addps) или соотв. интринсики в MSVC (_mm_mul_ps, _mm_add_ps).
W4FhLF Потому, что я вообще не уверен, что time-base анализ можно использовать для выявления одной критической инструкции, а не участка кода. Это связано с глубокой конвееризацией и внеочередным исполнением команд на современных процессорах, поэтому в момент внешнего прерывания и в планировщике и в исп.блоках может находиться куча уже запущенных команд и соотв-но не понятно к какой конкретно инструкции отнести момент прерывания. А почему в целом второй вариант тормозит понятно - из-за наличия imul для вычисления адресов. Плюс особенности атлона - последовательное вычисление адресов операндов памяти и соотв-но наличие AGI (address generation interlock), когда следующая команда загрузки должна ждать пока вычислится адрес предыдущей. На пеньках и Core такого нет (точнее есть только в отношении сохранения, а не загрузки), поэтому при наличии тормозных операций с адресами они работают быстрее атлонов Кстати какая у тебя размерность матрицы ? Иожет кэш тут вообще ни причем green Напрямую не получится, нужно сначала вторую матрицу транспонировать
Ну я хз конечно насколько корректно это сравнение, но я собрал с параметром /arch:SSE2, все команды заменились на SSE2. Прироста не ощутил. Хотя знаю, что компилятору такое не доверяют.
Тут уж либо универсальность, либо скорость на асм/интринсиках. unsigned->size_t, поменьше вызовов ф-ий, если можно вынести за цикл либо вызвать 1 раз. 6 х get_cols(), конечно это на совести компилера 1 раз её вызвать, но всё же. параллелизовать (разворачивать) цикл по строкам, по частям строки. Обычно это и делают компиляторы.
Это одно и тоже. Во всём цикле нет ни единого вызова(в асм листинге ессно). Там инлайн везде. Смысл? Память одна
На х86-64? Параллезация идёт не по памяти, а по операциям. Операнды которых хранятся в регистрах. Стандартный пример: с=а+б+в+г; или с=а+б, д=в+г, с=с+д; Если по памяти, то надо размеры кэшей учитывать тогда при параллелизации и есть ли смысл в оной. Там это где? Компилятор сегодня один, завтра другой. Хороший код (не бинарный) вне компилятора.
W4FhLF Да, компилятор на полном автомате использует очень ограниченное подмножество SSE-инструкций. И в основном те, которые работают только с одним (младщим) даблом. Т.е. собственно векторизации автоматом не получится.
W4FhLF В таком случае кэш работает одинаково не эффективно как в первом, так и во втором вариантах. Для больших матриц нужно юзать или блочные алгоритмы перемножения или транспонирование второй матрицы. Ну и что - приведенная картинка лишний раз показывает, что на уровне одной асм-инструкции юзать time-base нельзя. Например, в листинге col_data+=coloffset4; макс.число выборок (7669) приходится на быструю регистровую операцию addsd xmm1,xmm2, в то время как на предыдущую mulsd xmm2,[ecx] в сотни раз меньше (31), на последующие addsd вообще 0 (нет данных), а на безобидную add edx,20h почему-то 453. Какой вывод можно сделать из всей этой "каши" ? Только такой, что при конвееризации и внеочередном исполнении команд до 3-х и более штук за такт просто невозможно точно сказать на какой инструкции присходит внешнее прерывание. Поэтому рез-т получается с точностью в несколько инструкций и поэтому можно делать какие-то выводы относительно времени выполнения блока команд ( например col_data+=coloffset4 в целом), но ничего нельзя сказать об одной асм-команде (по кр.мере без доп.анализа) Откуда он возьмется, если классическое перемножение матриц не векторизуется, т.к. производится умножение строки на столбец. Если предварительно транспонировать вторую матрицу и умножать строку на строку, то можно задействовать векторизацию
leo Расскажи в таком случае, какие режимы в CodeAnalyst и VTune следует юзать. А то мне тоже интересна данная тема, но большого опыта работы с профайлером не имею.
Tier А я вообще никакого не имею Прежде чем качать десятки метров фиг знает чего, бегло просмотрел маны\туторы и решил, что игра не стоит свеч PS: На уровне асм-инструкций разве что эмуляция пайпа в CodeAnalyst может что-то дать, хотя зная микроархитектуру АМД и латентности операций, можно и самому достаточно точно сэмулировать все это дело "на бумажке"
leo Я думаю, векторизацию можно эффективно задействовать и без транспонирования. Чуть попозже сделаю пример - сейчас нету времени.
green Во-первых, врядли настолько "эффективно", как при транспонировании Во-вторых, главный тормоз при перемножении больших матриц - это неэффективная работа с памятью при умножении на элементы столбца. Поэтому транспонирование второй матрицы убивает сразу двух зайцев - 1) улучшается кэширование, что обеспечивает основной выигрыш в скорости, 2) можно легко задействовать векторизацию и распараллеливание, что даст доп.выигрыш, который может проявиться только при улучшении кэширования, т.к. иначе он просто утонет в тормозах ожидания загрузки данных из памяти