Про подход говорилось много. Скажем тут. Взять код из статьи и сравнить с тем, что породили вы. Поучительно.
Опять-таки подход-подходом, но и результат тоже бывает важен, так сравнить это дело с Blitz++, uBlasом и ATLASом было бы неплохо.
Вообще статья получилась странная в результате: если мы хотим иллюстрировать технику, то зачем нам все эти ухищрения с неполными блоками, усложняющие код, если мы хотим получить результат - то почему нет сравниения с уже полученными результатами???
ну так без ужасного кода ускорить иногда не получается. Тут не просто избавление от циклов, а их разворачивание, т.е. если вы пишете
for (i=0; i<n; i++){
s+=какие-то вычисления с участием a[i];
}
то иногда можно ускорить вроде бы ничего не меняющим:
for (i=0; i<n; i+=4){
s+=какие-то вычисления с участием a[i];
s+=какие-то вычисления с участием a[i+1];
s+=какие-то вычисления с участием a[i+2];
s+=какие-то вычисления с участием a[i+3];
}
Ускорение будет если все переменные внутри цикла поместятся в регистры процессора.
Заметьте - в теле цикла банальный копипаст..
ну естественно потом оставшиеся 0-3 числа прогнать если останутся.
В большинстве случаев не стоят конечно. И даже противопоказано если там будет 5-10%. Но все-таки этот пример с матрицами чем хорош - прирост больше чем в 10 раз, ради такого уже и можно запариться
Еще можно все вызовы функций заменить на них непорсдественно (inline), где возможно заменить арифметические операции побитовыми и тд
в итоге оптимизация выйдет более чем в 10 раз
если эти несколько десятков строк кода будут отнимать 80% времени вычислений для задачи, выполнение которой рассчитанной на год безусловно, стоит
и во многих других случаях тоже
это две стороны одной монеты - скорость исполнения в очень общем случае обратно пропорциональна объему кода. так что спорить что тут лучше что хуже мне кажется неверно.
неа, на данный момент компиляторы оптимизируют код намного лучше человека, они ведь учитывают размер кэша, количество регистров и т.п. Так что разворачивать циклы и расчитывать, чтобы то что надо попало в кэш когда надо - единственное что остается делать программисту. Остальное сделает gcc
Про VC просто лучше промолчать - он никогда особенно хорош не был, а Intel... Intel делает использует опасные оптимизации, что приводит к тому, что результат невозможно использовать: да вы что-то такое насчитаете быстро, но вот что - большой вопрос... Вот тут ребята, которые занимаются разработкой скоростных библиотек как раз вещей типа перемножения матриц об этом пишут. Inter Fortran - да, тот получше, чем gfortran...
К сожалению или к счастью правда. Человек любую программу мало-мальски ненулевого размера будет доводить до уровня, который обгонит компилятор пару лет - это как раз хватит для того, чтобы вышел новый процессор и пришлось всё начинать с начала...
Другое дело что компилятор строго следит за буквой программы, а не за её логикой, а так как операторы сложения, умножения и прочего в компьютерах ведут себя не так как в математике (скажем с транзитивностью полный швах), то многие оптимизации, которые может сделать человек компилятор сделать либо вообще не может (нормальные компиляторы), либо может - но с непредсказуемыми результами (ICC).
Правило 90/10 Вам, очевидно, не знакомо? Тем более что сейчас это скорее 95/5.
Я умею оптимизить C++ код и работаю в геймдеве.
Про то что компиляторы оптимизируют лучше человека смешно читать.
Компилятор "смотрит" на программу через призму языка. К тому же трансформации/оптимизации которые может делать человек ему не под силу, либо их нельзя заставить работать так, как нужно программисту.
Язык, который не позволяет мне получить в точности то, что я задумал - отстоен по определению.
С++ весь такой убого скалярный и нормально векторизовать программы можно только вручную на intrinsics или asm. Проблема, как ни парадоксально в том, что C++ _недостаточно_ высокоуровневый язык чтобы писать на нём _быстродействующий_ код. Функциональные языки имеют более высокий потенциал к высокопроизводительному коду.
х86 процессоры научились быстро жрать то, что им подсовывают.
Однако существуют ещё и другие процессоры, более требовательные к качеству компиляции. Я в своё время оптимизил GSM декодер под ARM тупо переписав его на асм без особых оптимизаций и получил 10X ускорение.
Асмовая программа не скована ABI и может использовать удобное распределение регистров, которое снизит стековый онанизм.
А то, что генерит PPC-gcc или XLC, вообще, мягко говоря, отвратительно.
А стоимость разработки на ассемблере будет на порядок выше стоимости разработки на С, а работать будет врядли быстрее, если программист на С возьмет в руки VTune.
ПРОГРАММИРОВАНИЕ, процесс подготовки задач для решения их на ЭВМ, состоящий из следующих этапов: составление ''плана решения'' задачи в виде набора операций (алгоритмическое описание задачи); описание ''плана решения'' на языке программирования (составление программы); трансляция программы с языка программирования на машинный язык (в виде последовательности команд, реализация которых техническими средствами ЭВМ и есть процесс решения задачи). Программированием называют также раздел прикладной математики, изучающий и разрабатывающий методы и средства составления, проверки и улучшения программ для ЭВМ.
Математика нужна в Х% всех задач, решаемых компьютером и программированием (в смысле, что знать математику и т.п.).
ПХП для своей области применения хорош, а то,то это скриптовый язык - никто и не скрывает. Скриптам тоже оптимизация нужна. Тут все от программиста зависит.
Лично я считаю что ряд функционала ПХП скриптов лучше вынести в отдельные программы на компилируемом языке (например, шаблонизатор + кэширование шаблонов, синхронизация и агрегация данных нескольких сайтов). Кроме того, считаю использование ПХП + cron считаю ересью, для таких задач польше подойдет bash. Хотя это мое мнение.
во, у нас используется php+cron :) bash не подойдёт, так как bash-а просто нет :) к тому же оказалось удобным использовать один и тот же php-модуль из cron и из apache
думаю, что выносить стоит математику, а то php у нас по нескольку минут считает уже
Кстати, было бы интересно увидеть результаты работы после сборки Intel C++ Compiler"ом - он умеет векторизировать циклы.
SSE инструкции появились девять лет назад, и продолжать их упорно неиспользовать это глупо.
Правильно. Но все же векторизацию вычислений нужно (правильнее) делать с головой (алгоритмом), а не компилятором.
Параллелизация и оптимизация под N-CPU SMP сильно зависит от алгоритма в плане, например, работы с памятью и кэшем. Чтобы не было тормозов необходимо, чтобы изменения не делались одновремнно в участках памяти менее cpu cache line size. Ну и для более высокого уровня кэша тоже оптимизироваться можно, если объем данных большой.
Даже при работе с NVIDIA CUDA необходимо думать о размерах кэшей, а Intel под свои CPU это прямым текстом рекомендует в своим материалах для разработки.
Какой бы ни был офигенный алгоритм с распарралеливанием компилятором - это не панацея и может не давать прироста в производительности из-за ошибок связанных с незнанием принципов работы CPU и кэшей.
Подобные алгоритмы не заточенные под SMP - это не то, чтобы "детский сад, вторая группа", но уже явно "вчерашний день".
Ну и вообще, когда речь идет о работе с целыми числами, то использование SSE на современных CPU - будет в медленнее, чем тупое перемножение алгоритмом. Современные процессоры N операций за такт делают. В Intel Core 2 Duo вроде до 4-х.
"И будем перемножать матрицу из подматриц по обычному правилу. Не сложно математически доказать, что ответ получится такой же как и при обычном перемножении"
ну если расписать все формулы при умножении с подматрицами для одного ij-го элемента, то как раз получится формула для обычного умножения матриц. Тут просто неудобно это расписывать, но это 100% верно
Хорошо, я сейчас обьясню в чём у меня была проблема.
Дело в том, что алгоритм Штрассена (в котором мы разбиваем матрицу на 4 подматрицы а затем обходимся перемножением 7 матриц размером n/2) действует рекурсивно. В результате чего мой код на си под юникс для матрицы 2000x2000 дал результат по времени в 75% процентов по сравнению с подсчётом в лоб. Теоретически же он должен был дать 23%. Но я не оптимизировал его иначе как алгоритмически. Потери времени у меня как раз были на этапе выделения памяти. Так вот вы использовали этот алгоритм, я правильно понял?
нет, тут обычное перемножение влоб с n^3 операций, просто организовано так, чтобы данные чаще всего брались их кэша.
Суть алгоритма: делим матрицу на маленькие подматрицы и перемножаем наши матрицы из подматриц как будто это матрицы из чисел. Вместо чисел перемножаются маленькие матрицы по обычному перемножению матриц.
Возможно Штрассена тоже можно так оптимизировать и он будет работать еще быстрее, чем этот блочный алгоритм.
Ага, всё понятно. Если сделаешь подобную оптимизацию для алгоритма Штрассена - выложи, мне очень интересен результат. Там явно надо грамотно обращаться с памятью :)
И если интересно будет, попробуй ещё поработать с алгоритмом Винограда (именно Винограда, а не Копперсмита—Винограда), там совсем ничего сложного нет, но мне кажется подобная оптимизация заставит его работать лучше, чем просто блочное перемножение. У меня на тех же матрицах он дал результат в 50% от времени перебора в лоб.
Вообще я стараюсь пользоваться scilab, но там пока-что очень неудобно реализовывать сложные алгоритмы - иногда приходится писать на c# (мне он просто нравится).
В своих, исследовательских задачах, я так с оптимизацией не заморачиваюсь. Главное - результат расчёта. С теми же матрицами, если начнёшь оптимизировать, то сдохнуть можно (во всяком случае времени на это обычно нет). Ведь видов матриц тьма, оптимизировать под каждый встречающийся вид - жалко себя и своё свободное время.
Вот стало интересно, а вам зачем такая оптимизация, получить моральное удовлетворение от успеха или, для дела?
Оговорюсь, правда, что если, вдруг появится действительно большая задача (на день-два счёта) - тогда пооптимизирую... хотя, можно сходить на концерт, а компьютер пусть посчитает, на то он и железка. )
такая оптимизация из-за жестких требований в ВУЗе ))
А применение - думаю в вычислениях, например как у Умпутуна, (ежедневная обработка огромного количества данных с вычислением каких-то коэффициентов причем за наименьшее время) можно использовать подобную технику оптимизации
Заметка хорошая с точки зрения того на что стоит обратить внимание.
А вообще-то быстрое перемножение матриц для начала надо реализовать на блоках 2х2. Как Вы думаете, сколько для этого надо операций умножения? 8? А вот и нет, 7. :) А это существенная экономия.
Ну... такая хорошая оптимизация алгоритма - это конечно курто (да и редко в наше время). Но если это критичсекий код, то как говорилось, лучше вручную переписать с SSE (а может и на GPU :) ). Или попытаться реализовать другие алгоритмы умножения, типа алгоритма Штрассена (если конечно его погрешности допустимы). Википедия говорит, что тогда есть шансы некубически выиграть производительность на размерах >128. К тому же он производит впечатление алгоритма неплохо забивающего кеш.
для некомпилируемых языков мне кажется совсем другая история. Там же код наверняка даже не оптимизируется как в компиляторе, хотя точно не могу сказать.
если не сложно - можете набросать маленькую программку на фортране (чтобы еще время считала), я её скомпилирую на своем компе и посмотрю время. Действительно интересно.
Фортран традиционно используется для расчётов, поэтому существует огромное количество оптимизированных умножателей матриц для него. Плюс встроенное умножение матриц в некоторых версиях. Поэтому, сейчас никто сам уже не пишет эти умножения, поэтому Фортран, вроде как, удобнее. Однако тот же BLAS и с C/C++ неплохо дружит. Если интересно, возьмите BLAS или MKL, там в комплекте тесты идут, потестируйте... Но вообще, смысла, скорее всего нет. Фортран не так уж и сильно отличается по вычислительной семантике от Си, чтобы чудес от него ждать.
Отличается. В качестве примера цикл:
for (int i=0;i<size;++i) {
a[i]=b[i]+b[i+1];
}
Может ли компилятор развернуть цикл так, чтобы использовать значение b[i+1], загруженное в регистр на предыдущем шаге? Ответ в Fortran: да, разумеется. Ответ в C/C++: вообще говоря нет. Почему? Очень просто: вдруг (b+1)==a ? Если компилятор может как-то напрячься и понять что это не так (скажем и a и b - статические переменные), тогда - пожалуйста, но в общем случае - нет. Правда у Fortran'а есть свой бич: F90-style массивы. Можно делать срезы других массивов, что изредка бывает удобно, но обычно - страшная головная боль для разработчиков компиляторов и больше ничего. Некоторые компиляторы даже имеют ключ, отключаещий это дело. Почему в супер-пупер-скоростной язык попал такой ужас? Очень просто: векротные компьютеры 80х обрабатывали такие вещи на аппаратном уровне и там это не было потерей в производительности. А в 90е - это стало большой проблемой из-за кешей, но стандарт - есть стандарт...
Так глобальная же оптимизация проводится. И то, чему реально соответсвуют a и b вычисляется и учитывается. Понятно, конечно, что всё в Си можно написать с указателями и так, что любой оптимизатор загнётся. Но можно так и не писать, использовать статические или автоматические массивы... Вообще, обычно, вроде, их хватает, тем более, что c99 позволяет автоматическим массивам иметь вычисляемый в runtime размер. Тут главное, квоты не забыть поправить : ).
А про срезы можно подробнее? Никогда не сталкивался с проблемой такой.
Многие системы программирования позволяют компоновать полученные в результате трансляции фортрановской программы объектные файлы с объектными файлами, полученными от компиляторов с других языков, что позволяет создавать более гибкие и многофункциональные приложения.
То есть математические вычисления можно выполнять отдельно на фортране, и адекватно вплетать в C/C++-приложения.
А вы много 3D игр не на Фортране видели? Вообще, очень сложно судить о том, какие средства используют программисты для написания игр. Исходники закрыты. При этом известно, что 3D игры пишут (именно, не писали, а пишут) даже на Lisp, и ничего, всё шустро бегает. Так что это... Почему бы и не фортран для некоторых функций, связанных с расчётами?
Эх... Когда-нибудь, в старости, я буду сидеть где-нибудь в кресле-качалке, смотреть на звезды, и читать исходник 3д гамы написанный на смеси хаскелла, си и ассемблерных вставок... А скрипты все в той игре будут на перле...
Эх мечты-мечты >_<
Задумаля я о возрасте этих языков и вот что нашёл. Ассемблеру уже исполнилось 56 лет, Си - 35, Perl'у - 20, Haskell'у - 17. Даже страшно подумать, что к Вашей старости не появится чего-нибудь нового : )
Тему только переводить не надо, на Lisp ни один разумный человек частоипользуемое перемножение матриц делать не будет.
А так я всегда "за" чтобы технологии по назначению заюзать. Кодогенерация и все такое.
Вопрос в размере матриц. Если это матрицы 4x4 и 4x1, как в играх, то Lisp способен их представлять и умножать достаточно эффективно. Реализовывать умножение, конечно, смысла никакого нет... Но речь и не об этом, речь о том, что в играх разные языки могут использоваться, в том числе и Фортран.
Для кодогенерации с оптимизацией Lisp не очень хорошо подходит. Работа с графами - это не конёк функциональных языков... Постоянно писать set - удовольствие сомнительное. Или вы о метапрограммировании?
Когда заканчивал универ в 2001 году увлекся немного задачей по поиску алгоритма разложения любой матрицы состоящей из 0 и 1 на произведение двух более мелких. Задача в основном состояла в том, чтобы найти способ минимальных перестановок в матрице, после которых она стала возможна к простому разложению на произведение двух более мелких матриц.
P.S. Это было давно, и после того момента с матрицами вообще не работал, поэтому сорри за каламбур, пост навеял молодость :)
1. исключить многочисленные a[j], a[i], a[k] в теле цикла, надо ходить по указателям ++, а не получать доступ к элементу по индексу.
2. вместо плавающей использовать фиксированную точку + параллельно вычислять за один проход несколько сложений, вычитаний, или умножений, "распараллелить" (в кавычках)
3. кроме этого по настоящему распараллелить вычисления.
Хехе.. Читаем Code Complete http://www.amazon.com/Code-Complete-Prac…
Там в клаве про оптимизацию есть этот пример (с разворачиванием строчек цикла) и другой.
Во втором примере автор пытался так же оптимизировать умножение матриц. Но его переписывания с лихвой перекрыла оптимизация компилятора..
Вывод: стоит ли оптимизировать, если бутылочное горлышко не в этом месте? ;)
Для начала определимся, что матрицы будем хранить в одномерном массиве размера n*n. К элементу A(i, j) будем обращаться как a[i*n+j]. Хранить в одномерном быстрее чем в двумерном массиве, т.к. одно целочисленное умножение и одно обращение к памяти работает быстрее, чем два обращения к памяти.
Спорное утверждение. В release или O3 должно быть одинаково. Потому что это есть одно и тоже.
1. Как с помощью данной функции мне перемножить 1000 матриц 5х5 и 2000 матриц 7х7, или одну 1003х1003?
Алгоритм не универсален, чтобы его применять нужно заранее знать размер матрицы. А если его знать заранее, и в функции прямо указать n - нормальный компилятор сам прекрасно развернет цикл без вашего участия.
2. Если уж лезть дебри, то в проце сравнение целого числа с нулем происходит значительно быстрее сравнения двух целых чисел, поэтому переворачиваем циклы.
3. Если заговорили о многоядерности, тогда бы уж упомянули о варианте разрезания сверхбольших матриц на полоски, и скармливании их разным тредам.
1) как видите не развернул, т.к. ускорение получилось более чем в 10 раз
2) да, я думаю такими подобными хитростями можно еще процентов 5-10 выиграть
3) ну я написал:
В дальнейшем конечно правильно будет с помощью multithread или mpi распараллелить программу и получить прирост еще в 1.8-1.9 раза.
1) Не развернул, потому, что в коде не была заранее известна размерность, а если бы вы написали for(i=5999;i>0;i--){...} и компилили с соответсвующими настройками компилятора - должен развернуть.
2) может даже и меньше чем 5%, зато это ничего не стоит кодеру и не сказывается на читабельности кода.
3) На скольки ядерном камушке? А если матрица целочисленная, и под рукой видяшка Nvidia со 256 потоковыми процессорами? Если уж решать абстрактную задачу, то почему бы не пофантазировать;)
Перемножаем матрицы быстро или простая оптимизация программ