<?xml version='1.0' encoding="utf-8"?>
      <rss version='2.0'>
      <channel>
      <title>Форум на Исходниках.RU</title>
      <link>https://forum.sources.ru</link>
      <description>Форум на Исходниках.RU</description>
      <generator>Форум на Исходниках.RU</generator>
  	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2533305</guid>
        <pubDate>Sat, 13 Mar 2010 19:09:09 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2533305</link>
        <description><![CDATA[real27: Я реализовал программу(Visual C++ 2008) которая каждую матрицу разбивает на четыре блока и перемножает их между собой,как ее оптимизировать чтобы каждая матрицы разбивалась на оптимальное количество блоков,и производилось умножение?]]></description>
        <author>real27</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425717</guid>
        <pubDate>Thu, 19 Nov 2009 12:52:24 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425717</link>
        <description><![CDATA[vitaly333: <div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '><br>
для маленьких packed-матриц Goto это весьма существенно - они обязательно должны быть непрерывными едиными массивами, а не независимыми наборами строк как в double**<br>
</div></div><br>
Да так и есть, каждый блок запаковывается (копируется в соответствии с выбранной схемой блокирования регистров) из 2-мерного массива double** (исходной матрицы) в непрерывный одномерный рабочий массив double*. <br>
<br>
<div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '>Покажи полностью прогу </div></div><br>
Всё есть в ссылке которую я приводил выше.]]></description>
        <author>vitaly333</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425503</guid>
        <pubDate>Thu, 19 Nov 2009 09:44:00 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425503</link>
        <description><![CDATA[Dethlord: <strong class='tag-b'>leo</strong> верно говориш,можно даже списком вроде они пошустрее матриц]]></description>
        <author>Dethlord</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425456</guid>
        <pubDate>Thu, 19 Nov 2009 08:26:01 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425456</link>
        <description><![CDATA[leo: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=287059&view=findpost&p=2425325'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>vitaly333 &#064; <time class="tag-quote__quoted-time" datetime="2009-11-18T22:49:05+00:00">18.11.09, 22:49</time></span><div class='quote '>Я вот про это и говорю, может где-то чего упустил... Много раз уже проверял, моя реализация сделана точно по статье... </div></div><br>
В статье, например, не затрагиваются такие детали, как формат хранения матрицы. Судя по <a class='tag-url' href='http://forum.sources.ru/index.php?showtopic=283698&st=0' target='_blank'>предыдущей теме</a>, ты используешь double** с построчной аллокацией, а тот же <strong class='tag-b'>albom</strong> - один непрерывный блок double*. Для самих больших матриц это не должно играть существенной роли (если только выравнивание строк не совпадет с алиасингом кэша), а вот для маленьких packed-матриц Goto это весьма существенно - они обязательно должны быть непрерывными едиными массивами, а не независимыми наборами строк как в double**]]></description>
        <author>leo</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425393</guid>
        <pubDate>Thu, 19 Nov 2009 06:38:28 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425393</link>
        <description><![CDATA[Dethlord: Покажи полностью прогу]]></description>
        <author>Dethlord</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425325</guid>
        <pubDate>Wed, 18 Nov 2009 22:49:05 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425325</link>
        <description><![CDATA[vitaly333: <div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '>В таком случае ты мог ошибиться с реализацией goto-алгоритма</div></div><br>
Я вот про это и говорю, может где-то чего упустил... Много раз уже проверял, моя реализация сделана точно по статье... может быть товарищ Goto не упомянул ещё каких-нибудь деталей, влияющих на реализацию...незнаю сложно сказать]]></description>
        <author>vitaly333</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425109</guid>
        <pubDate>Wed, 18 Nov 2009 18:09:34 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2425109</link>
        <description><![CDATA[leo: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=287059&view=findpost&p=2423282'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>vitaly333 &#064; <time class="tag-quote__quoted-time" datetime="2009-11-16T18:27:51+00:00">16.11.09, 18:27</time></span><div class='quote '>Реализации блочных алгоритмов показывают примерно 2-2.6 Гфлопа.<br>
DGEMM из gotoBlas и MKL дают 3.8 - 4.4 Гфлопа (т.е. в любом случае, даже если я и ошибся с подсчетом, goto-алгоритм должен выдавать 80-90% а получается только около 50%). </div></div><br>
В таком случае ты мог ошибиться с реализацией goto-алгоритма ;)]]></description>
        <author>leo</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2423417</guid>
        <pubDate>Mon, 16 Nov 2009 21:29:48 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2423417</link>
        <description><![CDATA[shadeofgray: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=287059&view=findpost&p=2423282'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>vitaly333 &#064; <time class="tag-quote__quoted-time" datetime="2009-11-16T18:27:51+00:00">16.11.09, 18:27</time></span><div class='quote '>Посмотрю. Если вы делали его реализацию не могли бы вы привести какие-то конкретные цифры производительности, которую он показывает?</div></div><br>
Нет, я пока только планирую приступать к работе над ним...]]></description>
        <author>shadeofgray</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2423282</guid>
        <pubDate>Mon, 16 Nov 2009 18:27:51 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2423282</link>
        <description><![CDATA[vitaly333: <div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '><br>
В электронном виде можно найти &#39;Cache-Oblivious Algorithms&#39;, Harald Prokop. Там описан простейший рекурсивный алгоритм умножения матриц и другие алгоритмы. Правда, в оригинальном виде быстродействие у него очень низкое, но если не доводить рекурсию до конца (т.е. до умножения матриц 1х1), а спустившись до матриц 8х8 использовать эффективное ядро, то можно достичь неплохих результатов. 100% производительности процессора не выберешь, но зато полученный код будет работать с одной и той же эффективностью на любом процессоре - с любым размером кэша и без предварительной настройки.<br>
</div></div><br>
Посмотрю. Если вы делали его реализацию не могли бы вы привести какие-то конкретные цифры производительности, которую он показывает?<br>
<br>
<div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '><br>
Интересно, и какие же параметры (кроме частоты) ты берешь для своего атлона х2 ? <br>
</div></div><br>
Для моего атлона я считал так(конечно многоядерность я не учитываю, т.к. речь идет о достижении пика на одном ядре):<br>
ПП = [2412]*[1]*[1]*[2] = 4.8 Гфлопа <br>
Реализации блочных алгоритмов показывают примерно 2 - 2.6 Гфлопа.<br>
DGEMM из gotoBlas и MKL дают 3.8 - 4.4 Гфлопа (т.е. в любом случае, даже если я и ошибся с подсчетом, goto-алгоритм должен выдавать 80-90% а получается только около 50%).]]></description>
        <author>vitaly333</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2423115</guid>
        <pubDate>Mon, 16 Nov 2009 14:57:27 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2423115</link>
        <description><![CDATA[shadeofgray: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=287059&view=findpost&p=2422983'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>andriano &#064; <time class="tag-quote__quoted-time" datetime="2009-11-16T13:25:32+00:00">16.11.09, 13:25</time></span><div class='quote '>Вообще-то если матрицы не влезают в кэш, то самыми затратными операциями будут загрузки из памяти. Именно их и нужно оптимизировать. Учитывая, естественно, что при чтении из памяти одного байта в кэш все равно перемещается не менее нескольких десятков последовательных байтов. Все остальное несущественно.</div></div><br>
Нет, тут ещё два момента:<br>
1. эффективная работа TLB (промахи TLB тоже оказывают существенное воздействие)<br>
2. эффективная работа с уже загруженными в кэш матрицами, что тоже не совсем просто]]></description>
        <author>shadeofgray</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422983</guid>
        <pubDate>Mon, 16 Nov 2009 13:25:32 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422983</link>
        <description><![CDATA[andriano: Вообще-то если матрицы не влезают в кэш, то самыми затратными операциями будут загрузки из памяти. Именно их и нужно оптимизировать. Учитывая, естественно, что при чтении из памяти одного байта в кэш все равно перемещается не менее нескольких десятков последовательных байтов. Все остальное несущественно.]]></description>
        <author>andriano</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422600</guid>
        <pubDate>Mon, 16 Nov 2009 07:32:02 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422600</link>
        <description><![CDATA[shadeofgray: В электронном виде можно найти &#39;Cache-Oblivious Algorithms&#39;, Harald Prokop. Там описан простейший рекурсивный алгоритм умножения матриц и другие алгоритмы. Правда, в оригинальном виде быстродействие у него очень низкое, но если не доводить рекурсию до конца (т.е. до умножения матриц 1х1), а спустившись до матриц 8х8 использовать эффективное ядро, то можно достичь неплохих результатов. 100% производительности процессора не выберешь, но зато полученный код будет работать с одной и той же эффективностью на любом процессоре - с любым размером кэша и без предварительной настройки.]]></description>
        <author>shadeofgray</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422574</guid>
        <pubDate>Mon, 16 Nov 2009 06:59:53 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422574</link>
        <description><![CDATA[leo: <div class='tag-quote'><a class='tag-quote-link' href='https://forum.sources.ru/index.php?showtopic=287059&view=findpost&p=2422463'><span class='tag-quote-prefix'>Цитата</span></a> <span class='tag-quote__quote-info'>vitaly333 &#064; <time class="tag-quote__quoted-time" datetime="2009-11-15T21:37:59+00:00">15.11.09, 21:37</time></span><div class='quote '>Она высчитывается в флопсах(операциях с плав. точкой в секунду):<br>
ПП = [Частота 1 процессора]*[кол-во ядер]*[кол-во АЛУ]*[кол-во арифм. операций с плав. точкой, выполняемых за один такт процессора]</div></div><br>
Интересно, и какие же параметры (кроме частоты) ты берешь для своего атлона х2 ? <br>
Надеюсь, понимаешь, что кол-во ядер имеет смысл учитывать только при многопоточном расчете, т.к. при однопоточном на x2 по этой формуле всегда будешь получать не более 50% ПП. С числом АЛУ и фп-операций за такт тоже не все так просто - нужно учитывать особенности архитектуры, в частности, как я уже упоминал SSE2 только в Core2 и AMD K10 выполняются за одну микрооперацию, а в более ранних моделях за две - если этого не учитывать, то также можно ошибиться ровно в 2 раза]]></description>
        <author>leo</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422463</guid>
        <pubDate>Sun, 15 Nov 2009 21:37:59 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422463</link>
        <description><![CDATA[vitaly333: <div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '><br>
А вообше что по твоему &quot;пик производительности&quot; ты в диспечере задач штоли смотриш<br>
</div></div><br>
Она высчитывается в флопсах(операциях с плав. точкой в секунду):<br>
ПП = [Частота 1 процессора]*[кол-во ядер]*[кол-во АЛУ]*[кол-во арифм. операций с плав. точкой, выполняемых за один такт процессора]<br>
<br>
<div class='tag-quote'><span class='tag-quote-prefix'>Цитата</span> <div class='quote '>Лично мне в последнее время нравится cache-oblivious подход к умножению матриц, в сочетании с использованием оптимизированных ядер для нижнего уровня рекурсии. Не надо танцев с бубном вокруг кэшей разных уровней и разного размера, учета таких тонкостей, как inclusive/non-inclusive кэш и т.д. Алгоритм сам использует кэш довольно-таки эффективным образом.</div></div><br>
А поподробнее можно? Что за алгоритм? Какую производительность показывает?]]></description>
        <author>vitaly333</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422354</guid>
        <pubDate>Sun, 15 Nov 2009 17:05:07 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2422354</link>
        <description><![CDATA[shadeofgray: Лично мне в последнее время нравится cache-oblivious подход к умножению матриц, в сочетании с использованием оптимизированных ядер для нижнего уровня рекурсии. Не надо танцев с бубном вокруг кэшей разных уровней и разного размера, учета таких тонкостей, как inclusive/non-inclusive кэш и т.д. Алгоритм сам использует кэш довольно-таки эффективным образом.]]></description>
        <author>shadeofgray</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2421584</guid>
        <pubDate>Sat, 14 Nov 2009 01:32:39 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2421584</link>
        <description><![CDATA[Dethlord: Я бы использовал рекурсию,многопоточную реализацию и возможно модульную математику.Да и вот циклы For медленные,Я бы через DO сделал.<br>А вообше что по твоему &quot;пик производительности&quot; ты в диспечере задач штоли смотриш?.]]></description>
        <author>Dethlord</author>
        <category>Алгоритмы</category>
      </item>
	
      <item>
        <guid isPermaLink='true'>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2421515</guid>
        <pubDate>Fri, 13 Nov 2009 21:49:59 +0000</pubDate>
        <title>Блочное умножение матриц. Достижение пиковой производительности алгоритма.</title>
        <link>https://forum.sources.ru/index.php?showtopic=287059&amp;view=findpost&amp;p=2421515</link>
        <description><![CDATA[vitaly333: Здравствуйте товарищи-алгоритмисты. Хочу поделится тут с вами своими (и не только) соображениями по поводу матричного умножения. Тема эта как казалось мне уже давно избитая, но когда начинаешь копать глубже при оптимизации умножения всплывают всё новые и новые факты. Вообщем стоит задача: написать высокопроизводительное матричное  умножение так чтобы вычисления с плав. точкой производились на пиковой (или почти пиковой) скорости процессора (в расчет не берется многоядерность).  Матрицы берутся общего вида, достаточно большого размера (не помещаются в L2 кэше данных). Т.е. в матричном виде имеем:<br>
C = C + A*B, где A,B,C есть M x K, K x N и M x N матрицы.<br>
Высокая производительность такого умножения вообще достигается двумя путями:<br>
1) Оптимизация внутреннего цикла, за счет использования векторизации и блокирования регистров – т.е. сокращения кол-ва перемещений  данных между кэшем и регистрами. Последнее достигается за счет использования всех доступных xmm – регистров (8 под x86 и 16 под x64)  и  разворотом внутреннего цикла. При этом данные, загруженные в регистры, повторно используются. Обычно половина из доступных регистров используется для накапливания сумм произведений, остальные для вычислений. Этот вопрос уже поднимался мной в <a class='tag-url' href='http://forum.sources.ru/index.php?showtopic=283698' target='_blank'>этой</a> теме и впринципе ответ на него я получил.<br>
2) Блокирование кэша – сокращение IO – трафика между ОЗУ и кэшем.  Идея заключается в том, чтобы загружать блоки матриц в кэш и там эффективно их перемножать, при этом чтобы данные из одного или двух блоков максимально долго оставались в кэше и многократно использовались.  Главный вопрос, из-за которого я и создал эту тему, состоит в том: <strong class='tag-b'>как выбирать оптимальные размеры блоков и саму схему блокирования кэша, так чтобы вычисления производились на пике вычислений с плав. точкой?</strong> При том, такая схема должна эффективно работать на большинстве существующих архитектур процессоров и не должна зависеть от какого–то конкретного типа процессора.   <br>
Вообще есть ещё и 3-ий путь – это уменьшение кол-ва умножений за счет увеличения кол-ва сложений (алгоритмы типа Штрассена). Но здесь мы рассматривать их не будем так как это отдельная тема для исследований и этот путь идет в разрез с путем блокирования кэша. Хотелось бы достичь высокой производительности алгоритма имеено за счет первых двух. И это можно сделать. В подтверждение тому выскопроизводительные реализации GEMM из оптимизированных BLAS-библиотек (INTEL MKL, ATLAS и GotoBLAS).<br>
Теперь рассмотрим более подробно 2-ой вопрос.<br>
Первое что приходит на ум, это взять 3 блока A,B,C примерно одной размерности, загрузить их в кэш и там перемножить. При этом размер блока берется таким, чтобы эти три блока помещались в L2 кэш. (Lb &#8804; sqrt(L2/3))<br>
<img class='tag-img' src='http://img.webme.com/pic/a/algomath/blockmult.jpg' alt='user posted image'><br>
Тогда алгоритм блочного умножения матриц будет выглядеть примерно так:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">Nb = N/Lb;</div><div class="code_line">For (ib = 0;ib&#60;Nb;ib++)</div><div class="code_line">&nbsp;&nbsp;For (jb = 0;jb&#60;Nb;jb++)</div><div class="code_line">&nbsp;&nbsp; &nbsp;For (kb = 0;kb&#60;Nb;kb++)</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; Cibjb += Aibkb*Bkbjb; перемножение блоков</div></ol></div></div></div></div><script>preloadCodeButtons('1');</script><br>
При стандартной схеме (“строка - столбец”) каждое перемножение первой строки из блока матрицы A на столбцы из блока матрицы B будет самым дорогостоящим, так как в это время ни элементов из блока A, ни элементов из блока B ещё нет в кэше. Умножение 2-ой  и последующих строк блока A на столбцы из того же блока B будет происходить уже гораздо быстрее, так как загружаться в кэш из основной памяти будут  только строки блока A а весь блок B уже будет находится полностью в кэше. <br>
 При такой схеме наиболее долго в кэше будет оставаться блок матрицы C, а элементы из блока матрицы A будут использоваться лишь единожды. Т.е. здесь блокирование матрицы A не имеет смысла. <br>
<br>
Также очень важным для повышения производительности алгоритма является то, чтобы доступ к данным в памяти был строго последовательным (чтобы элементы матриц читались из строк а не из столбцов). <br>
В случае когда матрицы квадратные можно применить транспонирование матрицы B и перемножать схемой “строка на строку”. Такая схема годится для квадратных матриц одного размера. Но когда матрицы общего вида, применять транспонирование матрицы B уже нельзя. Чтобы доступ к элементам был всё время последовательным можно применить алгоритм перемножения блоков матриц, переставив j-ый и k-ый циклы местами:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">For (i = 0;i&#60;Lb;ib++)</div><div class="code_line">&nbsp;&nbsp;For (k = 0;k&#60;Lb;k++)</div><div class="code_line">&nbsp;&nbsp; &nbsp;For ( j = 0;j&#60;Lb;j++)</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp;cij += aik*bkj;</div></ol></div></div></div></div><br>
Этот вариант уже лучше (в плане производительности) но не получается достичь пиковой скорости на одном ядре. Даже с оптимизацией внутреннего цикла (векторизация и блокирование регистров)  достигается только 50% от пика.<br>
Скорее всего квадратно-блочная схема блокирования далеко не оптимальная. В случае квадратных матриц, был также рассмотрен другой вариант блокирования, предложенный товарищем Leo. Это вариант, когда блоки берутся не квадратные, а строчные во всю длину строк матриц. При этом большой строчный блок транспонированной матрицы B заполняет значительную часть L2 кэша и удерживается там достаточно долго, а строки матрицы A меняются одна за другой . После того как  вся матрица A будет помножена на блок B, в L2 кэш загружается следующий строчный блок матрицы B и всё повторяется. <br>
<img class='tag-img' src='http://img.webme.com/pic/a/algomath/blockrowmult.jpg' alt='user posted image'><br>
Алгоритм выглядит так:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">For (ib=0;ib&#60;Nb;ib++)</div><div class="code_line">&nbsp;&nbsp;For (j=0;j&#60;N;j++)</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; Cj,ib += Aj * Bib // умножение строки матрицы A на блок матрицы B</div></ol></div></div></div></div><br>
Но и этот вариант также далек от оптимального. Этот алгоритм также выжимает только половину от пиковой производительности процессора. <br>
Далее я решил узнать как осуществляется блокирование кэша в оптимизированных BLAS – библиотеках и наткнулся на одну очень интересную статью товарища Goto, автора высопроизводительной библиотеки GotoBlas. Статья называется “Anatomy of High-Performance Matrix Multiplication”. В ней есть все ответы на мои вопросы: там подробно описывается как осуществлять блокирование кэша и как выбирать размеры блоков так, чтобы достичь максимальной производительности при умножении матриц. Основная идея там заключается в том, чтобы разложить GEMM  на вложенные “уровни ”. Каждый “уровень” это какая –то процедура. Например, один из возможных вариантов выглядит так: <br>
<img class='tag-img' src='http://img.webme.com/pic/a/algomath/multilevelmult.jpg' alt='user posted image'><br>
GEMM раскладывается на несколько вызовов GEPP(умножение столбцового блока A –“панель” на строчный блок –  также “панель”). В свою очередь GEPP может быть разложена на несколько вызовов GEBP (умножение прямоугольного блока на строчный).<br>
И если, достичь максимальной производительности от процедуры последнего уровня (от GEBP), то GEMM также будет выполнятся на максимальной скорости процессора.<br>
Очень советую прочесть эту статью чтобы быть в курсе.(есть её собственный перевод на русский язык). И далее в статье делается упор на рассмотрение именно этой процедуры  последнего уровня. Если рассмотреть GEBP более подробно то видно: <br>
<img class='tag-img' src='http://img.webme.com/pic/a/algomath/gebp.jpg' alt='user posted image'><br>
что прямоугольный блок (mc *  kc) матрицы A (A1), и два nr – столбца из B (bi) и С(ci) должны находится в L2 кэше. Причем (mc * kc)  матрица должна удерживаться там  достаточно долгое время(пока будет нужна). Кроме того данные, помещенные в L2 кэш должны быть адресованы в TLB L2 кэш. И адреса (mc * kc)  матрицы также должны удерживаться в L2 TLB кэше. Если 2-ое условие не будет соблюдаться , то будет происходить большее кол-во TLB – промахов. А TLB-промах, как пишет Goto, гораздо дороже  кэш промаха. Т.е. имеем:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">(1) mc * kc &nbsp;+ 2*(mc*nr + kc* nr) &#60;=L2 &nbsp;</div><div class="code_line">и</div><div class="code_line">(2) (mc * kc &nbsp;+ 2*(mc*nr + kc* nr)) записей &#60;=TLB L2</div></ol></div></div></div></div><br>
Причина появления двойки в формулах (1) и (2) Goto объясняет следующем: когда потребуется загрузить следующие два сi+1 и bi+1 блочных столбца в L2 кэш и адресовать их L2 TLB, то должны быть заменены именно ci и bi столбцы и соответствующие записи в TLB.   Однако, после завершения вычисления ci = ci + A1*bi может оказаться так  что самые старые  линейки и записи A1 будут заменены на новые bi+1 и ci+1. Коэффициент “2” позволяет хранить линейки и записи, связанные с bi и ci, вместе с линейками и записями A1, bi+1 , ci+1 до того момента , пока не потребуется загрузка и адресация следующих bi+2 и ci+2 , которые заменят уже не нужные данные и адреса из bi и ci. <br>
Обычно, для вычисления параметров мс и kc используется меньшее из (1) и (2).<br>
<br>
Кроме того, как я уже писал, доступ к данным должен осуществляться строго последовательный. Для этого Goto предлагает делать упаковку блоков –копировать специальным образом( в соответствии со схемой блокирования) непоследовательные данные из блока в последовательный одномерный массив.  <br>
Тогда алгоритм  GEMM будет выглядеть примерно так:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">For (k=0;k&#60;K;k+=kc) {</div><div class="code_line">&nbsp;&nbsp;// выполнить k-раз GEPP</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; B1 = Pack Bk ; // запаковать блок kc x N матрицы B в посл. массив B1</div><div class="code_line">&nbsp;&nbsp; &nbsp;For (j=0;j&#60;M;j+=mc) {</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;A1 = Pack Ajk ; // запаковать блок mc x kc матрицы A в посл массив A1</div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Cj += A1*B1; // выполнить GEBP &nbsp;</div><div class="code_line">&nbsp;&nbsp; &nbsp;}</div><div class="code_line">}</div></ol></div></div></div></div><br>
Рассмотрим теперь как умножаются запакованные блоки и как они вообще упаковываются:<br>
<img class='tag-img' src='http://img.webme.com/pic/a/algomath/gebp2.jpg' alt='user posted image'><br>
Блок матрицы A упаковывается так, чтобы каждая mr * kc подматрица блока хранилась последовательно в памяти. Блок матрицы B упаковывает последовательно kc * nr подматрицы. Параметры mr и nr ограничены кол-вом доступных регистров. Обычно половина доступных xmm – регистров используется для хранения mr * nr подматрицы Cj.<br>
Оставшиеся регистры используются для умножения mr – элементов A1 на nr –элементов B1. <br>
Тогда алгоритм GEBP:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">For (i=0;i&#60;N;i+=nr) {</div><div class="code_line">&nbsp;&nbsp;For (j=0;j&#60;mc;j+=mr) </div><div class="code_line">&nbsp;&nbsp; &nbsp; &nbsp;C1 = A1j * B1i &nbsp;// умножение mr * kc блочной строки на kc* nr блочный столбец</div><div class="code_line">&nbsp;C += unpack C1 ; // распаковать С1 в С</div><div class="code_line">}</div></ol></div></div></div></div><br>
Стоит сказать пару слов об произведении блочной строки на блочный столбец:    <br>
Из A1 в регистры последовательно загружается mr – элементов, Из B1  в регистры                            последовательно загружается nr – элементов. Далее они перемножаются и результат перемножения сохранятся в регистрах для  накопления произведений.  Далее на место старых в регистры загружаются следующие mr –элементы из A1. Точно также из B1 следующие nr – элементы.  И всё повторяется. И так kc – раз. <br>
После того как mr * kc блочная строка полностью умножена на kc* nr блочный столбец, сумма произведений, накопившаяся в регистрах, выгружается в последовательный массив C1. Таким образом, мы шагаем в памяти строго последовательно.   <br>
Из посл. картинки видно, что ещё одним условием для эффективной реализации GEBP умножения будет:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">(3) &nbsp;kc * nr + 2*(mr * nr + mr &nbsp;* kc) &nbsp;&#60;= L1</div></ol></div></div></div></div><br>
Причина появления двойки такая же как и для условий (1) и (2) : удержание всего kc * nr  блочного столбца в L1. <br>
Зная кол-во доступных регистров можно  определить mr и nr. (nr также определяется, исходя из пропускной способности между L2 и регистрами и скоростью вычислений с плав. точкой – см. более подробно в статье). <br>
Для 8-ми доступных xmm регистров mr x nr обычно выбирается: 2x4, 2x2 и 4x2. Из условия(3) можно найти kc. А из условий (1-2) -  mc.<br>
Также Goto в своей статье сообщает, что на практике параметры, выбранные по условиям (1-3) не всегда оптимальны (на выбор этих параметров влияет ещё набор ассоциативности и стратегии замещения кэша) и предлагает выбирать их так:<br>
kc –чисел с плав. точкой должны занять половину размера страницы памяти.  <br>
mc выбирается так, чтобы  mc*kc матрица A1 занимала примерно половину меньшего из :<br>
-  памяти, адресуемой TLB L2; <br>
 - L2 кэша.<br>
Например, для моего процессора AMD Athlon64 x2 (L1Cache = 64Kb, L2Cache = 512Kb, TLB L2 = 512 записей, pagesize = 4KB) я вычисляю их так:<br>
<div class='tag-code'><span class='pre_code'></span><div class='code  code_collapsed ' title='Подсветка синтаксиса доступна зарегистрированным участникам Форума.' style=''><div><div><ol type="1"><div class="code_line">mr и nr я взял 2x4.</div><div class="code_line">kc = pagesize / 2 = 4096 Bytes / 2*8 Bytes = 256.</div><div class="code_line">&nbsp;</div><div class="code_line">Проверка:</div><div class="code_line">kc*nr + 2*(mr*nr +mr*kc) &nbsp;&#60;= L1</div><div class="code_line">kc &#60;= (L1-2*mr * nr) / (2*mr + nr) = (64 * 1024 – 2*2 * 4 * 8) Bytes / (2*2 +4) * 8 Bytes &#60;=1022</div><div class="code_line">256 &#60;= 1022.</div><div class="code_line">&nbsp;</div><div class="code_line">(1) (mc x kc) &#60;= L2/2</div><div class="code_line">mc &#60;= L2 / 2 * kc = 512 * 1024 Bytes / 2 * 256 * 8 Bytes = 128</div><div class="code_line">&nbsp;</div><div class="code_line">&nbsp;(2) (mc x kc) &#60;=TLB L2 /2</div><div class="code_line">mc &#60;= TLB L2/2*kc = 512*4096 Bytes / 2*256*8 Bytes = 512</div></ol></div></div></div></div><br>
Меньшее из (1) и (2) есть 128.<br>
<br>
Таким образом для моего процессора у меня получились такие вот параметры:<br>
mr = 2, nr = 4, kc = 256, mc = 128<br>
<br>
Я запрограммировал этот алгоритм умножения матриц, предлагаемый Goto, но добиться максимальной производительности от него у меня опять не получилось. Снова  те же 50% от пика. Также я пробовал брать mr x nr = 2x2, производительность аналогичная.<br>
Хотя этот подход, предложенный Goto, позволяет достичь 90 и более % от максимальной производительности на большинстве архитектур. Это показывают тесты в статье да и сама библиотека GotoBlas даёт неплохие результаты,  производительность которой сравнима с Intel MKL, а в некоторых случаях и превосходит её. Возможно, я что то упустил при реализации. Кому интересна эта тема советую прочитать статью и попробовать реализовать подход предложенный там, возможно у кого-то получится. Саму статью, её перевод и мою реализацию описанного в ней подхода можно скачать <a class='tag-url' href='http://rapidshare.de/files/48678173/gotoMM.rar.html' target='_blank'>здесь</a>. <br>
Также было бы неплохо, если кто-нибудь предложит свой вариант блокирования и выбора размеров блоков.]]></description>
        <author>vitaly333</author>
        <category>Алгоритмы</category>
      </item>
	
      </channel>
      </rss>
	