<?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; Alexey Kukanov (Intel)</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/author/alexey-kukanov/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>О потоках и багетах, или предновогодний пост</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/12/29/2006690/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/12/29/2006690/#comments</comments>
		<pubDate>Thu, 29 Dec 2011 14:43:59 +0000</pubDate>
		<dc:creator>Alexey Kukanov (Intel)</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[for fun]]></category>
		<category><![CDATA[openmp]]></category>
		<category><![CDATA[TBB]]></category>
		<category><![CDATA[просто о сложном]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/12/29/2006690/</guid>
		<description><![CDATA[Помните, пару лет назад мы проводили конкурс “Объясни на пальцах”, где предлагалось на понятных примерах объяснить термины из области информатики и программирования? Я тут на днях объяснял на примере поедания багета планирование задач для параллельного цикла и подумал, что с помощью этого примера можно “на пальцах” объяснить и другие понятия из области многопоточного программирования, а также разницу в подходах, применяемых в популярных решениях для параллелизма, таких, как OpenMP и TBB. Тем, кто не любит мучное, могу предложить заменить багет на колбасу или ведро какого-нибудь напитка. Поехали! :)]]></description>
			<content:encoded><![CDATA[<p>Помните, пару лет назад на ISN проводили конкурс “<a href="http://software.intel.com/ru-ru/articles/contest-spell-it-out-2-main">Объясни на пальцах</a>”, где предлагалось на понятных обычным людям примерах объяснить термины из области информатики и программирования? Я вспомнил о нём, когда в форуме пытался объяснить <a href="http://software.intel.com/ru-ru/forums/showpost.php?p=171206">разные подходы к планированию задач параллельного цикла на примере поедания багета</a>, а потом подумал, что с помощью этого примера можно “на пальцах” объяснить и другие понятия из области многопоточного программирования, а также разницу в подходах, применяемых в популярных решениях для параллелизма, таких, как <a href="http://www.openmp.org">OpenMP</a> и <a href="http://threadingbuildingblocks.org">TBB</a>. В любом случае, тема, по-моему, как раз для Нового года ;)  Тем, кто не любит мучное, могу предложить мысленно заменить багет на колбасу, или, предвидя царящие предновогодние настроения, на ведро какого-нибудь напитка. Ну, поехали! <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Итак, вам и вашим гостям (думаю, понятно, что каждый из гостей символизирует один поток в многопоточной программе) надо как можно быстрее съесть (или иным образом употребить) огромный багет, или колбасу, или ведро портвейна. Давайте вначале введём некоторые <strong>базовые понятия</strong>:</p>
<p><em>Общий объём работы</em> – весь багет.</p>
<p><em>Гранулярность </em>– размер куска багета, которым не нужно делиться. Сразу становится понятно, что такое крупногранулярное и мелкогранулярное разбиение, правда? :) Если гранулярность слишком мелкая, разделить кусок с другом дольше, чем съесть его самому.</p>
<p><em>Задача </em>– любое действие, выполняемое участниками. Собственно, в процессе поедания багета две принципиально разных задачи – “разделить кусок на части” и “съесть кусок” (сразу вспоминается “а чего тут сложного, наливай да пей!”). Но, конечно, никто не может помешать вам отвлекаться на телевизор, кофе и иные непродуктивные “задачи”.</p>
<p><strong>Принципы использования потоков</strong>:</p>
<p><em>Параллельный регион OpenMP (team-and-barrier)</em>: купивший багет резервирует стол на фиксированное количество человек. В некоторых компаниях (<em>читай: реализациях</em>) принято начинать есть только когда все друзья соберутся вместе; в других начинать можно сразу, как придёте. Но заканчивать обязательно всем вместе: все ждут последнего, даже если он вообще не явился.</p>
<p><em>Пул потоков</em>: купивший багет разделяет его на куски, которые выкладывает на стол, вывешивает табличку “Багет на халяву!” и ждёт, пока друзья и шапошные знакомые не съедят всё до крошки. Кроме него, никто ничего не ждёт; если со стола взять нечего, можно сразу перейти за соседний.</p>
<p><em>Пул потоков TBB</em>: то же самое, но владелец багета начинает есть куски сам. Если никто не придёт, он и один всё съест <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p><strong>Принципы распределения задач</strong>:</p>
<p><em>Общий пул задач</em>: в середине стола стоит тарелка, на которой лежит багет, туда же кладутся его “свободные” куски. Тянуться до тарелки далековато, а брать и класть нескольким лицам одновременно обычно не разрешается.</p>
<p><em>Поток-диспетчер</em>: багет отдан официанту, который и раздаёт (а иногда и отрезает) куски. У официанта всего две руки, и в одной из них багет <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> , поэтому гостям приходится ждать своей очереди. Официант сам есть не может, но занимает место за столом; если места нет и никто из гостей не хочет уступить, все сидят голодными.</p>
<p><em>Захват работы (work stealing)</em>: рядом с каждым гостем своя тарелка, с которой он берёт и на которую кладёт куски. Если своя тарелка опустела, можно взять кусок с любой другой; обычно никто не стесняется, хоть до чужих тарелок тянуться и дальше.</p>
<p><strong>Принципы деления работы</strong>:</p>
<p><em>Статическое деление </em>(например, <strong><em>#pragma omp for schedule(static)</em></strong>): владелец ведра портвейна режет его на куски заданного размера (гранулярности) и по кругу раскладывает на тарелки всем гостям; в некоторых компаниях гости сами берут куски  с общей тарелки (но при этом только "свои"!). С чужих тарелок есть нельзя. Напомню, что в команде OpenMP при этом все ждут последнего. Подход очень хорошо работает, если все начинают одновременно и едят с одинаковой скоростью, а в багете не попадается инородных предметов <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p><em>Динамическое деление</em> (<strong><em>#pragma omp for schedule(dynamic)</em></strong>): багет лежит на середине стола; каждый, у кого нет еды, отрезает себе кусок заданного размера и ест. Нож (как и остаток багета) только один. Этот подход предпочтительнее статического деления, если багет пропечён неравномерно или кто-то опаздывает к столу.</p>
<p><em>Динамическое деление в <strong>tbb::parallel_do</strong></em>: владелец багета отрезает себе кусок на 4 порции, остаток багета кладёт на свою тарелку. Туда же кладутся три из четырёх порций, а четвёртая отправляется в рот. Гости хватают куски, где найдут; захвативший большой остаток багета повторяет процедуру деления на своей тарелке.</p>
<p><em>Рекурсивное деление </em>(например, <em><strong>cilk_for</strong> </em>или <strong><em>tbb::parallel_for плюс simple_partitioner</em></strong>): любой кусок багета больше заданного размера режется примерно напополам, половины кладутся на тарелку (в TBB – на свою, но можно и на общую). У каждого есть свой нож; в крайнем случае, можно и руками разломить <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p><em>Рекурсивное деление с динамическим выбором гранулярности (<strong>tbb::parallel_for плюс auto_partitioner</strong></em>): Деление напополам прекращается, когда на каждого из едоков приходится по 2-4 куска. Однако, кусок, схваченный с чужой тарелки, снова делится, минимум дважды (то есть, в итоге, начетверо). Начиная с TBB 4.0, хозяин тарелки, с которой утащили кусок, замечает это и оставшиеся куски будет делить помельче.</p>
<p>-----</p>
<p>На сегодня фантазия иссякла. Нескромно надеюсь, что чтение было не только забавным, но и полезным. Всех с наступающим Новым Годом, и пусть на вашем праздничном столе будет что-то помимо багета и портвейна! <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/12/29/2006690/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Весенний выпуск Intel&#174; Threading Building Blocks</title>
		<link>http://software.intel.com/ru-ru/blogs/2010/05/15/intelreg-threading-building-blocks/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2010/05/15/intelreg-threading-building-blocks/#comments</comments>
		<pubDate>Sat, 15 May 2010 01:30:09 +0000</pubDate>
		<dc:creator>Alexey Kukanov (Intel)</dc:creator>
				<category><![CDATA[Открытый код]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[TBB]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2010/05/15/intelreg-threading-building-blocks/</guid>
		<description><![CDATA[В отличие от прошлых лет, на этот раз ежегодный выпуск новой версии TBB пришёлся на весну. ]]></description>
			<content:encoded><![CDATA[<p>На первой майской неделе я в свои неполных 37 с удивлением осознал, что люблю это время года <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>В отличие от прошлых лет, в этом году выпуск новой версии библиотеки <a href="http://software.intel.com/en-us/intel-tbb" target="_blank">Threading Building Blocks</a> пришёлся  как раз на начало мая. И вообще, этот выпуск появлялся на публике вместе с весной :) Первые признаки приближения новой версии обнаружились в марте, когда мы выложили предварительные исходные коды на <a href="http://threadingbuildingblocks.org">http://threadingbuildingblocks.org</a>. На <a href="http://software.intel.com/en-us/forums/intel-threading-building-blocks/" target="_blank">форуме TBB</a> тут же обнаружили несколько проблем, и спустя дней десять мне пришлось пообещать, что будет обновление с исправлениями. Обновление было готово в начале апреля, ещё пару недель тестировалось (в том числе с только что вышедшей официальной версией Visual Studio 2010) и появилось как раз в <a href="http://ru.wikipedia.org/wiki/%D0%94%D0%B5%D0%BD%D1%8C_%D0%97%D0%B5%D0%BC%D0%BB%D0%B8" target="_blank">День Земли</a> и весенних субботников. Ну а 4-го мая версия TBB 3.0 была обнародована официально. По-моему, получилось неплохо <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  Посмотрим, станут ли весенние выпуски нашей новой традицией.</p>
<p>По традиции уже сложившейся, каждая новая версия TBB сопровождается публикациями в <a href="http://software.intel.com/en-us/blogs/category/intel-threading-building-blocks/" target="_blank">нашем блоге на ISN</a> (на английском языке). Там вы найдёте <a href="http://software.intel.com/en-us/blogs/2010/05/04/whats-new-in-intel-threading-building-blocks-30" target="_blank">краткое</a> и <a href="http://software.intel.com/en-us/blogs/2010/05/04/tbb-30-new-today-version-of-intel-threading-building-blocks/" target="_blank">чуть более развёрнутое</a> описание наиболее интересных нововведений, информацию по <a href="http://software.intel.com/en-us/blogs/2010/05/04/transitioning-to-tbb-30/" target="_blank">переходу на TBB 3.0</a> с предыдущей версии,  а также другие заметки, в том числе от моих коллег-разработчиков с нижегородской площадки Intel. Мы планируем разъяснить новые возможности 3.0 более подробно, так что если вам интересно, заглядывайте, или <a href="http://software.intel.com/en-us/blogs/category/intel-threading-building-blocks/feed/" target="_blank">подпишитесь на RSS</a>. На  <a href="http://software.intel.com/en-us/forums/intel-threading-building-blocks/" target="_blank">нашем форуме</a> тоже обсуждается много интересного, а если вы предпочитаете общаться на русском - добро пожаловать в <a href="http://software.intel.com/ru-ru/forums/95/" target="_blank">форум по параллельному программированию</a> или в <a href="http://software.intel.com/ru-ru/forums/intel-parallel-studio/" target="_blank">форум</a> поддержки <a href="http://software.intel.com/ru-ru/intel-parallel-studio-home/" target="_blank">Intel(R) Parallel Studio</a>, где задавать вопросы о TBB тоже вполне уместно.</p>
<p>А теперь вопрос для любознательных: какой у новой версии TBB номер с инженерной точки зрения? Подсказка: ответ надо искать в заголовочных файлах. Разработчиков библиотеки, а также "чёрных поясов" ISN попрошу не торопиться с ответом <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2010/05/15/intelreg-threading-building-blocks/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Миллион шагов на месте</title>
		<link>http://software.intel.com/ru-ru/blogs/2009/11/08/2002466/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2009/11/08/2002466/#comments</comments>
		<pubDate>Sun, 08 Nov 2009 01:24:53 +0000</pubDate>
		<dc:creator>Alexey Kukanov (Intel)</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2009/11/08/2002466/</guid>
		<description><![CDATA[Задавшись в комментариях к записи "33 шага назад" вопросом, каким образом введение параллелизма может поменять асимптотические характеристики алгоритма, я должен был быстрее догадаться, каков правильный ответ. ]]></description>
			<content:encoded><![CDATA[<p>Для тех, кто не в курсе, о чём пойдёт речь, попробую сделать краткое вступление.</p>
<p>В своей записи на здешней блог-площадке Вячеслав Любченко привёл графики времени работы параллельного рекурсивного алгоритма нахождения чисел Фибоначчи в зависимости от порядкового номера числа в последовательности. И в случае реализации алгоритма на TBB время росло экспоненциально, а у реализации, где структура алгоритма описана при помощи конечных автоматов (КА) - линейно!</p>
<p>В чём тут дело, я должен был догадаться раньше, почти сразу. Никакой параллелизм не способен перевести экспоненциальную сложность исходного алгоритма в линейную, кроме ... экспоненциально растущего :) </p>
<p>Исполнение рекурсивного алгоритма определения числа Фибоначчи суть есть обход двоичного дерева с подсчётом его листьев. Последовательное исполнение - обход в глубину, параллельное - обход в ширину. Глубина дерева - N-1, где N - порядковый номер искомого числа Фибоначчи (рассматриваем N&gt;1, считая, что для N&lt;=1 решение известно). Для идеально сбалансированного двоичного дерева на каждом ярусе глубины d содержится 2 в степени d (2^d) узлов; соответственно, количество листьев - 2^(N-1). В нашей задаче дерево не сбалансировано; но можно показать, что и в нашем случае максимальное количество узлов на одном ярусе, как и общее количество узлов, имеет асимптотическую оценку O(2^N). </p>
<p>В КА реализации у Вячеслава каждому узлу дерева соответствует один конечный автомат, а в самой простой реализации на TBB - одна "задача", то есть объект класса, унаследованного от tbb::task. Все автоматы, как и все задачи, могут работать/исполняться независимо друг от друга. Так откуда же разница в поведении?</p>
<p>А разница, на самом деле, в применённых методиках измерения. При оценке поведения КА реализации Вячеслав использовал дискретное время. За один такт дискретного времени все автоматы осуществляют переход из одного состояния в другое, причём их количество на дискретное время не влияет. По сути, время оценивается <em>до</em> этапа отображения (mapping) возможного параллелизма на конкретную аппаратную платформу.</p>
<p>При получении же графиков для TBB и MC# использовалось время "настенное" (wall clock time), измеренное <em>после</em> отображения возможного параллелизма на реальную платформу, в которой фактическая степень параллелизма равна 2 и постоянна. Чуда не случится, от добавления второго (4-го, 32-го) ядра асимптотическое поведение алгоритма не изменится.</p>
<p>Чтобы поведение алгоритма, предсказанное в дискретном времени, совпадало с поведением во времени реальном, необходима такая же степень реального (аппаратного) параллелизма, какова была степень параллелизма при получении оценки. То есть, для достижения асимптотического поведения О(N) необходим аппаратный параллелизм порядка O(2^N). Подумаешь, всего какой-то миллион или около того ядер для вычисления 30-го числа Фибоначчи <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  Причём, если зафиксировать аппаратный параллелизм даже на этом уровне и продолжить увеличивать N, то асимптотическое поведение быстро вернётся к экспоненциальному; для сохранения линейности алгоритма необходимо и дальше экспоненциально наращивать мощность. Представьте себе компьютер, в котором сколь угодно много вычислителей добавляются динамически по мере надобности и сразу же встраиваются в работу программы. Это, конечно, классно, но боюсь, ещё долго останется нереальным.</p>
<p>Чтобы избежать сравнения яблок с томатами, можно для КА реализации алгоритма построить график зависимости <em>реального</em> времени от N. Поскольку переходы для всех автоматов обсчитываются последовательно на одном ядре, реальное время, соответствующее одному дискретному такту, будет сильно разным для моделей из десятка и из миллиона автоматов, а в результате на графике будет та же экспонента (что Вячеслав и подтвердил в комментариях).</p>
<p>Справедливости ради замечу, что Вячеслав предпринял попытку привести измеренное реальное время в форму, пригодную для сравнения с дискретным. Но это, на мой взгляд, невозможно. Его идея об использовании времени работы итеративного алгоритма в качестве нормирующего фактора явно не работает. Докажу от противного: если такое преобразование приводит реальное время в форму, сопоставимую с дискретным, то это должно быть истинно и для КА модели. Реальное время T работы КА программы растёт как O(2^N), при нормировании константой получим время T'=T*O(1), то есть растущее по тому же закону. Соответствия поведению дискретного времени DT ~ О(N) мы не получили, значит, преобразование не достигает желаемой цели.</p>
<p>Стало быть, выводы, которые Вячеслав сделал о TBB как параллельной модели, некорректны, так как исходили из ложных посылок.</p>
<p>В завершение замечу, что TBB и другие модели, которые умеют отображать потенциальный параллелизм на реально присутствующий аппаратный, для задач с рекурсивным параллелизмом будут выдавать решение, масштабируемое с ростом количества доступных ядер - в отличие от последовательной реализации КА модели у Вячеслава, которая, сколько ядер не добавь, будет пока что топтаться на месте. Всеми миллионами конечных автоматов, в ритме дискретного времени :)</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2009/11/08/2002466/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Вкратце: просто и полезно :)</title>
		<link>http://software.intel.com/ru-ru/blogs/2009/07/21/2001739/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2009/07/21/2001739/#comments</comments>
		<pubDate>Tue, 21 Jul 2009 15:28:29 +0000</pubDate>
		<dc:creator>Alexey Kukanov (Intel)</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Social Media]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[ISN blogging tips]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2009/07/21/2001739/</guid>
		<description><![CDATA[Всем, кто пишет блоги на ISN, читать обязательно! :)]]></description>
			<content:encoded><![CDATA[<p>Если вы обратили внимание, краткое описание этого поста в <a href="http://software.intel.com/ru-ru/blogs" target="_blank">блог-ленте ISN</a> не состоит из первого предложения, оборванного посередине многоточием как-то вот так: "Если вы обратили внимание, краткое описание этого поста в блог-ленте ISN не состоит из первого предложения, оборванного [...]"</p>
<p>Ну и что, спросите вы? Просто, пока Дмитрий и Светлана пытаются улучшить наш далеко не идеальный блог-движок (что не слишком просто и не слишком быстро, отчасти в силу требований совместимости с "большим" ISN) и <a href="http://habrahabr.ru/company/intel/blog/64563/#comment_1797638" target="_blank">оправдываются</a> в ответ на критику посетителей, привыкших к более удобным площадкам, мы - те, кто хоть изредка пишут здесь блоги - можем немного позаботиться о наших читателях. Это ведь совсем несложно - кратко описать суть вашего нового поста в паре предложений. А если постараться, можно сделать это короткое описание ещё и привлекательным, заманивающим читателя узнать, что же там, под ссылкой в заголовке.</p>
<p>Технически это проще простого. На форме для создания записи в блоге много разных полей, из них часть скрыта. Нужное нам поле скрыто под кнопкой "Цитата" со стрелкой слева:</p>
<p><img class="size-full wp-image-2001741" src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/clip_image001.gif" alt="" /></p>
<p>Один щелчок мышью, и вот оно, место для ввода того самого короткого и завлекательного анонса. Это, конечно, не тэг "обрезания" контента в произвольном месте, наподобие имеющихся в движках LiveJournal и Habrahabr. Но пока нет лучшего, давайте пользоваться тем, что есть.</p>
<p>Не совсем кстати: из разных переводов слова Excerpt (английское название кнопки) требуемую суть наилучшим образом, на мой взгляд, передаёт слово "Выдержка". Альтернативные варианты: "Коротко", "Вкратце".</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2009/07/21/2001739/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Почему компиляторы не любят параллельный код?</title>
		<link>http://software.intel.com/ru-ru/blogs/2008/03/03/43/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2008/03/03/43/#comments</comments>
		<pubDate>Mon, 03 Mar 2008 16:55:35 +0000</pubDate>
		<dc:creator>Alexey Kukanov (Intel)</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[TBB]]></category>
		<category><![CDATA[Threading Building Blocks]]></category>
		<category><![CDATA[производительность]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2008/03/03/43/</guid>
		<description><![CDATA[С распространением многоядерных процессоров стали множиться попытки написать простую параллельную программу и посмотреть, каков будет выигрыш в производительности. И это, несомненно, радует, но... К удивлению экспериментаторов, их простые параллельные программы порой не только не показывают ускорения по сравнению с обычной, последовательной версией, но даже и проигрывают в скорости! И далеко не всегда автор может (или хочет) разобраться, что к чему.]]></description>
			<content:encoded><![CDATA[<p>С распространением многоядерных процессоров параллельное программирование перестаёт быть экзотикой и превращается постепенно в необходимый навык разработчика ПО. Как косвенное свидетельство тому, стали множиться попытки написать простую параллельную программу и посмотреть, каков будет выигрыш в производительности. И это, несомненно, радует, но...</p>
<p>За последнее время мне довелось наблюдать несколько таких попыток, в которых параллельный код был написан при помощи библиотеки <a href="http://threadingbuildingblocks.org/">Threading Building Blocks</a> (TBB). К удивлению экспериментаторов, их простые параллельные программы порой не только не показывают ускорения по сравнению с обычной, последовательной версией, но даже и проигрывают в скорости! И далеко не всегда автор может (или хочет) разобраться, что к чему.</p>
<p>Разбираться приходилось мне, как одному из разработчиков TBB. А поскольку мне уже не раз предлагали вести сетевой журнал-блог на Intel Software Network, я в конце концов решил попытаться, и начать с анализа таких вот примеров. Они, с одной стороны, просты и понятны: не каждый готов начать параллельные эксперименты со сколь-нибудь сложной программы, а написать цикл, суммирующий элементы вектора – дело недолгое. С другой стороны, они весьма познавательны, причём порой именно в силу простоты.</p>
<p>Начну как раз с суммы элементов вектора. Вопрос о том, почему столь простая программа медленнее в параллельной TBB версии, чем в не-параллельной, возник на <a href="http://softwarecommunity.intel.com/isn/Community/en-US/forums/2471/ShowForum.aspx">форуме TBB</a> (см. тему “<a href="http://softwarecommunity.intel.com/isn/Community/en-US/forums/permalink/30250043/30249884/ShowThread.aspx">performance of parallel_for</a>”). Я приведу здесь лишь часть кода:</p>
<blockquote>
<pre>#define REAL double
#define MAX 10000000
class SumFoo {
    REAL *a;
    REAL sum;
    ...
    void operator()(const blocked_range&lt;size_t&gt;&amp; r) {
        REAL *arr = a;
        for(size_t i=r.begin(); i!=r.end(); i++) sum+=arr[i];
    }
};
void ParallelSumFoo(REAL a[], size_t n, size_t gs) {
    SumFoo sf(a,n);
    parallel_reduce(blocked_range&lt;size_t&gt;(0,n,gs), sf);
    ...
}
class SerialApplyFoo {
    REAL *a;
    size_t len;
    double sum;
public :
    SerialApplyFoo(REAL *array, size_t length) : a(array), len(length){
        sum = 0.0;
        for(size_t i=0; i&lt;len; i++)
            sum += a[i];
    }
    …
};
int main() {
    task_scheduler_init init;
    REAL *array = new REAL[MAX];
    ...
    SerialApplyFoo sf(array, MAX);
    ParallelSumFoo(array, MAX, GRAINSIZE);
    ...
}</pre>
</blockquote>
<p>На двухъядерном процессоре Intel® Core® 2 Duo, <em>вместо ожидаемого двукратного ускорения, параллельный код работал почти вдвое медленнее, чем последовательный</em>; на моём ноутбуке, к примеру, 0.08 сек против 0.044. В чём дело?</p>
<p>Прежде всего, давайте посмотрим, а что произойдёт, если параллельный код запустить последовательно, в одном потоке. С TBB это можно сделать двумя способами: либо явно указав количество потоков (в нашем случае, один) конструктору объекта инициализации библиотеки task_scheduler_init, либо установив параллельному алгоритму так называемый «размер зерна» (grain size) равным количеству итераций цикла. Я воспользуюсь первым способом:</p>
<pre>    task_scheduler_init init(1);</pre>
<pre> </pre>
<p>Результат может показаться неожиданным: 0.21 сек, почти впятеро медленнее, чем исходный последовательный код! Такое замедление явно не из-за использования TBB. Неудивительно, что и на двух ядрах нет выигрыша.</p>
<p>Всё дело в оптимизации, проводимой компилятором. Цикл суммирования в последовательной версии очень прост для оптимизации; компилятор знает о нём всё, начиная от константной длины (передача через аргумент для компилятора не помеха), заканчивая тем, что результат sum должен быть виден только по выходу из конструктора. Поэтому он может сгенерировать очень эффективный машинный код, к примеру, вот такой (я использовал Visual Studio* 2005):</p>
<blockquote>
<pre>            fldz
            lea      eax, DWORD PTR [edi+010h]
            mov      ecx, 0x2625A0h
main+0x105: fadd     QWORD PTR [eax-16]
            add      eax, 0x20h
            sub      ecx, 0x1h
            fadd     QWORD PTR [eax-40]
            fadd     QWORD PTR [eax-32]
            fadd     QWORD PTR [eax-24]
            jnz      main+0x105</pre>
</blockquote>
<p>0x2625A0 – это 2500000, четверть длины массива. Компилятор развернул четыре итерации цикла в одну, снизив расходы на обслуживание цикла. Промежуточные результаты вычислений накапливаются в регистре процессора, и только конечный результат будет записан в память. На каждую итерацию исходного цикла ровно одна операция чтения данных из памяти.</p>
<p>В случае использования TBB для компилятора всё не так просто. Начало и конец цикла определяются полями объекта, переданного по ссылке, длина цикла во время компиляции неизвестна, результат накапливается в поле данных “живого” объекта. Компилятор вынужденно более консервативен, и результат не замедлил сказаться:</p>
<blockquote>
<pre>execute+0x57: fld      QWORD PTR [edi]
              add      ecx, 0x1h
              fadd     QWORD PTR [edx+08h]
              add      edi, 0x8h
              fstp     QWORD PTR [edx+08h]
              cmp      ecx, DWORD PTR [esi+08h]
              jnz      execute+0x57</pre>
</blockquote>
<p>Здесь одна итерация цикла обрабатывает один элемент, но при этом задействует 3 операции чтения (!) и одну – записи. Помимо элемента данных, каждый раз подгружаются из памяти значение конца цикла для сравнения и промежуточная сумма, которая затем сохраняется и на следующей итерации подгружается снова.</p>
<p>Изложив всё это в форуме, я добавил, что тест «отдаёт предпочтение» последовательному коду, и этим, боюсь, направил автора не в ту сторону. В попытке улучшить тест, автор ввёл чтение размера массива с клавиатуры и заменил в не-параллельном коде объект на функцию:</p>
<blockquote>
<pre>REAL SerialSum(REAL *a_, unsigned long int start, unsigned long int end) {
    REAL sum = 0.0;
    for(unsigned long int i=start; i&lt;end; i++)
        sum += a_[i];
    return sum;
}</pre>
</blockquote>
<p>Первое изменение делает тест более реалистичным и несколько усложняет развёртывание цикла; второе никак вообще не влияет, потому что в новой функции промежуточная сумма накапливается в локальной переменной, которая точно так же может храниться в регистре процессора. А чтобы решить проблему, надо в <em>параллельный код</em> внести изменения, которые помогут компилятору применить оптимизацию, а именно – использовать в operator() локальные переменные:</p>
<blockquote>
<pre>    void operator()(blocked_range&lt;size_t&gt; &amp;r) {
        REAL local_sum = 0;
        const size_t end = r.end();
        for(size_t i=r.begin(); i!=end; i++)
            local_sum+=a[i];
        sum += local_sum;
    }</pre>
</blockquote>
<p>И вот какой получен машинный код:</p>
<blockquote>
<pre>              fldz
execute+0x58: fadd        qword ptr [edx]
              add         edx,8
              sub         ecx,1
              jne         execute+58h
              fadd        qword ptr [edi+8]
              fstp        qword ptr [edi+8]</pre>
</blockquote>
<p>Последние две инструкции – добавление локального результата к полю sum. Как и в последовательном коде, мы получили одно обращение к памяти на итерацию, и это сразу сказалось на скорости: теперь параллельный код выполняется 0.033 сек на двухъядерном процессоре. Ускорение достигнуто, но с коэффициентом 1.33, а не 2. Почему? В этом попробуем разобраться в следующий раз.</p>
<p><strong>Вывод:</strong> при разработке тестов на производительность, да и просто программ, необходимо учитывать, что использование классов и функций TBB усложняет компилятору работу по оптимизации, что особенно негативно сказывается на простых алгоритмах. Грамотное использование локальных переменных способствует лучшей оптимизации и быстродействию параллельного кода.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2008/03/03/43/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
	</channel>
</rss>

