<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Блоги &#187; Igor Vorobtsov (Intel)</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/author/igor-vorobtsov/feed/" rel="self" type="application/rss+xml" />
	<link>http://software.intel.com/ru-ru/blogs</link>
	<description></description>
	<lastBuildDate>Thu, 24 May 2012 12:16:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.3</generator>
		<item>
		<title>“Хотели как лучше” или “Фортран и немного C: как найти ключ к оптимизации и профилировке”</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/04/04/c/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/04/04/c/#comments</comments>
		<pubDate>Wed, 04 Apr 2012 13:30:02 +0000</pubDate>
		<dc:creator>Igor Vorobtsov (Intel)</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/04/04/c/</guid>
		<description><![CDATA[Производительность… Многие (если не сказать все) разработчики стремятся получить хорошо оптимизированный код и используют для этого различные методики. Наиболее простой и доступный способ — флаги компилятора для оптимизации, коих совсем немало. Ввиду огромного количества различных опций, которые могут переопределять другие, нередко возникает путаница. Как итог — ваше приложение может оказаться неоптимизированным, и результаты вас неприятно [...]]]></description>
			<content:encoded><![CDATA[<p>Производительность… Многие (если не сказать все) разработчики стремятся получить хорошо оптимизированный код и используют для этого различные методики. Наиболее простой и доступный способ — флаги компилятора для оптимизации, коих совсем немало. Ввиду огромного количества различных опций, которые могут переопределять другие, нередко возникает путаница. Как итог — ваше приложение может оказаться неоптимизированным, и результаты вас неприятно удивят.</p>
<p>Хотел бы остановиться на наиболее интересных случаях, с которыми я столкнулся в последнее время.</p>
<h2 class="sectionHeading">Сказ о Фортране</h2>
<p>Итак, начнём с используемых всеми, кто работал с компилятором компании Intel, флагов для оптимизации /Od, O1, O2, O3. В данном случае будем говорить о Фортране (Intel® Visual Fortran Composer XE) и Windows*.</p>
<p>Достаточно подключить флаг /O2, и мы получим весьма неплохую оптимизацию, а производительность приложения вырастет в разы. Что мы делаем дальше? Конечно, хотим «прогнать» наше приложение через VTune™ Amplifier и посмотреть, всё ли так хорошо.</p>
<p>Вот здесь и начинается самое интересное. Для того, чтобы использовать профилировщик, нужно скомпилировать наш код в Release-режиме с отладочной информацией, то есть подключить флаг /debug:full (для плюсового компилятора — /Zi). Подразумевается, что в Release-режиме наши бинарники будут оптимизированы, потому что по умолчанию должен быть установлен флаг /O2, но… на деле всё происходит несколько иначе. Именно для Фортрановского компилятора и среды разработки Visual Studio было придумано следующее — в настройках проекта (Project → Properties → Fortran → Optimization) появился уровень оптимизации «Maximize Speed». Обычно, он устанавливает флаг /O2 неявно. Что значит неявно? Попробую объяснить издалека.</p>
<p>Если перейти в командную строку (Project → Properties → Fortran → Command Line), можно увидеть все опции, которые подключены. Разработчики Фортрановского компилятора позаботились о нас и начиная с версии компилятора 12.0 стали предусмотрительно показывать только те опции, которые мы установили не по умолчанию, то есть поменяли сами. Весьма удобно — сразу понятно, что было изменено в дефолтных настройках проекта. Замечу ещё раз, речь идёт только о Фортрановском компиляторе, в С++ этого нет. Так вот, когда по умолчанию в Release режиме стоит опция «Maximize Speed», /O2 явно не прописывается в командной строке. Всё было бы хорошо, если бы не флаг /debug:full (подключается в Project → Properties → Fortran → Debugging → Debug Information Format). Его специфика в том, что он отключает оптимизацию, то есть делает уровень Od по умолчанию, если явно не добавлен другой уровень.</p>
<p>В обычной командной строке всё работает в соответствии с документацией — написали “ifort -/debug:full /O2”, и бинарники будут оптимизированы и с «дебажной»  информацией, не написали явно /O2 — оптимизация будет отключена при компиляции опцией /debug.</p>
<p>В Visual Studio же возникает путаница. Мы поменяли режим на Release и ожидаем, что теперь наш код будет оптимизирован. Подключили /debug:full (мы же хотим сделать профилировку), и он автоматически, явно добавился в командную строку… а вот /O2 нет. Итог — наше приложение не будет оптимизировано, и VTune покажет не те результаты, которых мы ждём.</p>
<p>Что же делать? Нужно быть очень внимательным и явно подключать флаг /O2 в командную строку (Project → Properties → Fortran → Command Line → Additional Options).</p>
<p>Если говорить о С++, там оптимизация добавляется явно из VS и подобной путаницы не возникает. Тем не менее, следить за флагами /Zi (C++) и /debug нужно внимательно, особенно, если компиляция происходит через командную строку, и не забывать явно прописывать флаг оптимизации. Через VS, соответственно, ещё внимательнее.</p>
<p>Я бы предложил несколько изменить текущее поведение компилятора. Нужно, в первую очередь, обращать внимание на оптимизацию — если она включена (пусть даже и неявно), то другие флаги не должны её переопределять. Либо хотя бы выводить об этом соответствующее уведомление.</p>
<h2 class="sectionHeading">Немного о С/С++</h2>
<p>Ещё один момент. Если мы будем рассматривать поведение С++ компилятора в Linux, возникнут похожие проблемы, особенно ярко выраженные при работе с VTune. Если подключить флаги -g -O2,  будут линковаться дебажные версии библиотек (например, при работе с TBB), которые, понятное дело, не будут отличаться хорошей производительностью. Как решение — ручками прописывать нужные библиотеки через -L.</p>
<p>Надеюсь, в ближайшее время подобное поведение будет пересмотрено. По крайней мере, я прикладываю для этого свои усилия. А пока — «осведомлён — значит вооружён».</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/04/04/c/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cache blocking: техника и тонкости</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/03/02/cache-blocking/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/03/02/cache-blocking/#comments</comments>
		<pubDate>Fri, 02 Mar 2012 06:46:15 +0000</pubDate>
		<dc:creator>Igor Vorobtsov (Intel)</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/03/02/cache-blocking/</guid>
		<description><![CDATA[Недавно столкнулся с интересным вопросом по оптимизации кода с помощью техники блокирования циклов для обеспечения более эффективного использования кэша, и решил поделиться с вами, потому что в русской литературе нашёл не так много информации, как хотелось (я бы сказал, почти ничего). Сразу замечу, что примеры будут на Фортране – по нему и материала меньше, да [...]]]></description>
			<content:encoded><![CDATA[<p>Недавно столкнулся с интересным вопросом по оптимизации кода с помощью техники блокирования циклов для обеспечения более эффективного использования кэша, и решил поделиться с вами, потому что в русской литературе нашёл не так много информации, как хотелось (я бы сказал, почти ничего).<br />
Сразу замечу, что примеры будут на Фортране – по нему и материала меньше, да и разбирался я именно с ним. На самом деле, какой язык – вообще дело не принципиальное.</p>
<p>Итак, рассмотрим типичную «книжную» задачу умножения матриц, решённую классическим способом через 3 обычных вложенных цикла:</p>
<pre name="code" class="fortran">
      INTEGER  I, J, K
      REAL     A(N,N), B(N,N), C(N,N)
      ...
      DO I = 1, N
        DO J = 1, N
          DO K = 1, N
            A(I,J) = A(I,J) + B(I,K) * C(K,J)
          END DO
        END DO
      END DO
</pre>
<p>В случае, когда массив у нас достаточно большой, а размер кэша относительно маленький, возникнут проблемы с производительностью, ещё точнее – промахи кэша. Для понимания данного термина и сути происходящего, нужно рассмотреть понятие и принцип работы кэш-памяти.<br />
Все современные процессоры обладают кэшем – памятью с большой скоростью доступа, предназначенной для того, чтобы минимизировать доступ к ОЗУ. Расположена она между процессором и основной памятью:</p>
<p align="center"><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/cache.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/cache.png" alt="" width="137" height="247" align="center" class="aligncenter size-full wp-image-2006886" /></a></p>
<p>Кэш-память делится на несколько уровней, причём каждый последующий уровень больше по размеру и медленнее по скорости доступа и передаче данных, чем предыдущий. Обычно, эти уровни называют L1, L2 и L3. Есть интересная программулинка, которая позволяет определить подробности о вашем процессоре и, в частности, размеры кэша – <a href="http://technet.microsoft.com/en-us/sysinternals/cc835722.aspx">Coreinfo</a>.</p>
<p>Например, запустив на своём лэптопе (не самом новом), получил следующую информацию:</p>
<blockquote><p>
Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz<br />
Intel64 Family 6 Model 15 Stepping 11, GenuineIntel</p>
<p>Logical Processor to Cache Map:<br />
*-  Data Cache          0, Level 1,   32 KB, Assoc   8, LineSize  64<br />
*-  Instruction Cache   0, Level 1,   32 KB, Assoc   8, LineSize  64<br />
-*  Data Cache          1, Level 1,   32 KB, Assoc   8, LineSize  64<br />
-*  Instruction Cache   1, Level 1,   32 KB, Assoc   8, LineSize  64<br />
**  Unified Cache       0, Level 2,    4 MB, Assoc  16, LineSize  64
</p></blockquote>
<p>То есть, у моего процессора 2 ядра, и 2 уровня кэша – L1 равный 32 KB и L2 равный 4 Mb.</p>
<p>Самой быстрой памятью является кэш первого уровня — L1, причем он разделён на два - кэш инструкций и кэш данных. А вот L2 кэш общий, а значит для каждого из ядер можно использовать необходимое количество памяти. Если использовать все ядра, кэш память разделяется на каждое из них динамически, в зависимости от нагрузки.</p>
<p>Доступ к памяти осуществляется процессором небольшими блоками, которые называют строками кэша, собственно, из них кэш и состоит. Обычно размер строки составляет 64 байта, как и в случае с моей системой. При чтении любого значения из памяти, в кэш попадает как минимум одна строка кэша. Последующий доступ к какому-либо значению из этой строки происходит очень быстро. Переход на другую строчку занимает больше времени, ну а отстутствие данных во всём кэше приводит к очень серёзным потерям в производительности, связанными с выгрузкой/загрузкой данных.<br />
Таким образом, с точки зрения производительности, было бы идеально, чтобы весь наш массив помещался в кэш и доступ к его елементам производился по строкам.</p>
<p>Очень интересную статью по теме кэша и его магических свойств я нашёл <a href="http://habrahabr.ru/blogs/development/93263/">здесь</a>.<br />
В рамках же нашего рассказа остановимся подробнее на том, как с точки зрения написания кода, избежать подобных проблем и оптимизировать его «руками». Как вы уже поняли, поможет нам в этом техника разбиения циклов на блоки (cache blocking, loop blocking, loop tailing).<br />
Идея проста – мы изменяем наш цикл следующим образом:</p>
<pre name="code" class="fortran">
                                      DO IT=1, N, IS
      DO I = 1, N                       DO I=IT, MIN(N, IT+IS-1)
      ...                 =&gt;            ...
      END DO                            ENDDO
                                      ENDDO
</pre>
<p>Разбивая основной цикл на два, мы изменяем итерационное пространство таким образом, что обращаться к элементам массива будем уже не всей исходной матрицы, а её более маленьким блокам.</p>
<p>При этом важный момент здесь – размер блока должен быть таким, чтобы он помещался в кэш. Иначе никакого выигрыша в производительности мы не получим, а наоборот, получим дополнительные расходы на «управление» циклом.</p>
<p>Вспомнив, что исходная задача – умножение матриц, изменим все циклы предложенным образом:</p>
<pre name="code" class="fortran">
DO IT = 1, N, IS
   DO JT = 1, N, JS
     DO KT = 1, N, KS
       DO I = IT, MIN(N, IT+IS-1)
         DO J = JT, MIN(N, JT+JS-1)
           DO K = KT, MIN(N, KT+KS-1)
             A(I,J) = A(I,J) + B(I,K) * C(K,J)
           END DO
         END DO
       END DO
      END DO
    END DO
  END DO
</pre>
<p>При этом, размер IS*JS*KS должен помещаться в кэш (циклы у нас вложенные). При решении конкретной задачи (при известных размерах матриц и кэша), дело останется за малым – рассчитать нужные значения индексов.</p>
<p>Для вычисления размера блока данных, я бы порекоммендовал следующую формулу (для задачи умножения матриц):</p>
<p>Размер блока = 0.8 * 1/3 * размер кэша / размер данных</p>
<p>Здесь 0.8 означает, что вероятнее всего, в кэше будет содержаться ещё что-то совсем ненужное нам в размере 20% от всего кэша, поэтому отбросим этот размер. При этом правило одно – «лучше недобрать, чем перебрать», поэтому ошибка в сторону уменьшения приведёт к меньшим потерям производительности, чем выход за размеры кэша.</p>
<p>Рассмотрим пример – пусть размер матриц N на N=1000, работаем с типом REAL (4 байта), L2 кэш 4 Мб.</p>
<p>Размер блока = 0.8 * 1/3 * 4194304 байт / 4 байт = 279620 байт</p>
<p>Таким образом, мы рассчитали приближенный размер блока данных, но нам нужно вычислить сам индекс для использования в итерациях. Для задачи умножения матриц, нам нужно извлечь кубический корень (3 цикла) из размера нашего блока. Получаем, что индекс в нашей задаче равен 65, учитывая что длина строки кэша 64, выберем именно это значение.</p>
<p>На моей системе время выполнения цикла с обычным алгоритмом и с оптимизированным – 38 секунд против 50. Как видно, выигрыш в производительности более 30%. </p>
<p>Кстати, тесты я делал с отключенной оптимизацией. При подключении уровня O3 (максимального), время выполнения изменилось до 4 и 10 секунд соответсвтенно, таким образом, техника блокирования циклов весьма эффективна для подобных задач.</p>
<p>P.S. Сейчас играюсь с различными системами и оцениваю влияние HT (hyper-threading) технологии на размер блока. Так что продолжение следует...</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/03/02/cache-blocking/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Достоверны ли вычисления?</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/12/07/2006301/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/12/07/2006301/#comments</comments>
		<pubDate>Wed, 07 Dec 2011 14:28:57 +0000</pubDate>
		<dc:creator>Igor Vorobtsov (Intel)</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[точность вычислений]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/12/07/2006301/</guid>
		<description><![CDATA[В последнее время я достаточно много занимаюсь вопросами, связанными с точностью результатов, полученных при вычислениях, и пытаюсь найти ответ на вопрос - можно ли вообще получить точные результаты и как это сделать? Если отвечать на данный вопрос коротко  и ясно – НЕТ, точный результат «в лоб» на машине получить практически невозможно при наличии сколь-нибудь сложной математической задачи, которую мы пытаемся решить.]]></description>
			<content:encoded><![CDATA[<p>В последнее время я достаточно много занимаюсь вопросами, связанными с точностью результатов, полученных при вычислениях, и пытаюсь найти ответ на вопрос - можно ли вообще получить точные результаты и как это сделать? Если отвечать на данный вопрос коротко  и ясно – НЕТ, точный результат «в лоб» на машине получить практически невозможно при наличии сколь-нибудь сложной математической задачи, которую мы пытаемся решить.</p>
<p>Речь идёт не о погрешностях, которые возникают при  использовании того или иного численного алгоритма и метода – речь о модели представления действительных чисел. Ввиду «ограниченности» машин, мы не можем точно представить все действительные числа – только лишь аппроксимировать их.  В математическом анализе, для любого вещественного числа имеется бесконечно много чисел, которые больше или меньше его, а между любыми двумя вещественными числами также находится бесконечно много вещественных чисел. Система же машинных чисел конечна и дискретна, и образует подмножество системы вещественных чисел. Отсюда и возникает погрешность, которая будет накапливаться с количеством математических операций.</p>
<p>Многие скажут, что подобная точность и не нужна – ну подумаешь, в десятом знаке после запятой будет не тройка, а семёрка… а вот если нужна? Если мы решаем задачи, в которых эти значения могут быть весьма критичными (скажем, проблемы устойчивости атомных реакторов и подобные инженерные задачи), и мы должны точно знать, верно ли оно или нет? Кроме того, мы не гарантируем корректность проверки числовых соотношений. Например, если имеется код</p>
<blockquote><p>if x &lt; 0 then A else B</p></blockquote>
<p>и значение х вычисляется приближенно, то и правильность ветвления может оказаться под угрозой.</p>
<p>А может быть, всё-таки, есть способ вычислять точно? Да, такие способы есть, и их, на сегодня, целых 2 – но с понятием «точно» в данных случаях нужно быть очень аккуратным.<br />
Первый подход – давайте заменять точное значение приближенным (что ещё нам остаётся делать), и производить оценку погрешности исходных данных и округлений.  У данного подхода есть целый ряд серьёзных минусов, в частности, для нетривиальных алгоритмов получить оценку оказывается чрезвычайно сложной  задачей. Кроме того, вычисления по формулам, связывающим погрешность исходных данных и погрешность результатов, сами неизбежно производятся с погрешностью.</p>
<p>А вот второй поход, мне показался наиболее интересным – так называемый интервальный подход. Неизвестное точное значение заменяется не единственным приближенным, а множеством элементов. Всё просто – на уровне программиста, можно сказать, мы вводим новый тип данных – interval, у которого есть нижняя и верхняя границы. Производя какие-бы то ни было вычисления, мы оперируем с границами интервалов, округляя нижнюю границу с недостатком, а верхнюю – с избытком. При этом для использования данного подхода, очевидно, необходимо реализовать интервальную арифметику, определив операции сложения, умножения, и т.д.</p>
<p>Использование подобной арифметики приведёт к получению интервала, как результата вычислений, и в этом случае мы сможем гарантировать, что точное значение лежит в данном интервале. Кроме того, сравнивая нижнюю и верхнюю границы, мы сможем точно сказать, какое количество верных значащих цифр у нас есть. Например, при получение результата в виде [0.092312986, 0.092313129], мы можем точно сказать, что 0.09231 – значение, которое не зависит от машинных округлений.<br />
Ввиду математической направленности и ряда других причин, в своей работе я использую Fortran. К большому сожалению, в Интеловской реализации Фортрана интервальной арифметики нет, хотя подобная реализация есть в ряде расширений для языков, например, XSC языков (Pascal-XSC), а так же в реализации Sun. Так что интервальную арифметику я реализовывал сам в виде модуля на языке Фортран.</p>
<p>На сегодня, пожалуй, и всё. Это вводная статья, написанная с целью определения интереса к данному вопросу.<br />
Если тема найдёт отклик в ваших сердцах и головах <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> , я с удовольствием поделюсь своим опытом в вопросе достоверных вычислений и расскажу о реализацию арифметики более подробно.</p>
<p>И напоследок… для тех, кому данная тема показалась интересной, есть простая задачка, наводящая на мысли о правильности ветвления и сравнения действительных чисел (ну и о смысле «бытия» <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> ). </p>
<p><strong>Итак, пусть у нас есть два вещественных числа a и b (на Фортране – real, на С – float). Так вот, равны ли значения a = 1.1 – 1 и b = 0.1? И что меняется, если a=1.5-1, а b=0.5?</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/12/07/2006301/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

