<?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; dsambor</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/author/dsambor/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>Первое знакомство с технологией Intel® Cilk™ Plus</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/28/intel-cilk-plus/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/28/intel-cilk-plus/#comments</comments>
		<pubDate>Mon, 28 Nov 2011 07:10:05 +0000</pubDate>
		<dc:creator>dsambor</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[Intel Cilk Plus]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/28/intel-cilk-plus/</guid>
		<description><![CDATA[<p>В конкурсной задаче <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Acceler8 2011</a> требовалось найти подматрицу с наибольшей суммой в матрице, заполненной псевдослучайными значениями. Линейность используемого генератора псевдослучайных чисел позволяла заполнять матрицу параллельно.</p>

<p>В одном из вариантов известного кубического алгоритма в качестве предварительного шага требуется преобразовать матрицу в матрицу столбцов префиксных сумм. Поставим задачу написать эффективный код, который заполнит матрицу и вычислит префиксные суммы столбцов. При этом программа должна использовать как параллелизм на уровне задач (<strong>multicore</strong>), так и на уровне данных (<strong>SIMD</strong> векторизация), и сохранять наглядность последовательного алгоритма.</p>

<p>Технология <strong>Intel Cilk Plus</strong> подкупает именно тем, что обещает решить эти задачи. Например, расширение <strong>C/C++ Extensions for Array Notation (CEAN)</strong>, входящее в состав <strong>Cilk Plus</strong>, позволяет лаконично сформулировать параллельный алгоритм заполнения массива.</p>]]></description>
			<content:encoded><![CDATA[<p>В конкурсной задаче <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Acceler8 2011</a> требовалось найти подматрицу с наибольшей суммой в матрице, заполненной псевдослучайными значениями. Линейность используемого генератора псевдослучайных чисел позволяла заполнять матрицу параллельно.</p>
<p>В одном из вариантов известного кубического алгоритма в качестве предварительного шага требуется преобразовать матрицу в матрицу столбцов префиксных сумм. Поставим задачу написать эффективный код, который заполнит матрицу и вычислит префиксные суммы столбцов. При этом программа должна использовать как параллелизм на уровне задач (<strong>multicore</strong>), так и на уровне данных (<strong>SIMD</strong> векторизация), и сохранять наглядность последовательного алгоритма.</p>
<p>Технология <strong>Intel Cilk Plus</strong> подкупает именно тем, что обещает решить эти задачи. Например, расширение <strong>C/C++ Extensions for Array Notation (CEAN)</strong>, входящее в состав <strong>Cilk Plus</strong>, позволяет лаконично сформулировать параллельный алгоритм заполнения массива.<br />
Следующий код выводит таблицу умножения 16x16:</p>
<pre name="code" class="cpp">
#include &lt;stdio.h&gt;
#include &lt;cstdlib&gt;
#include &lt;cilk/cilk.h&gt;
#include &lt;iostream&gt;

int main(int argc, char* argv[])
{
#define N 16
#define M 16
    int a[M][N];
    cilk_for(int j = 0; j &lt; N; j++) {
        a[0][j] = j + 1;
    }
    cilk_for(int i = 1; i &lt; M; i++) {
        a[i][:] = a[0][:] * (i + 1);
    }
    for(int i = 0; i &lt; M; i++) {
        for(int j = 0; j &lt; N; j++) {
            printf("%4d ", a[i][j]);
        }
        std::cout &lt;&lt; std::endl;
    }
}
</pre>
<p><a href="http://gist.github.com/1380138">exmpl1.cpp</a></p>
<p>Использование <strong>Cilk Plus</strong> здесь позволило сделать параллелизацию и векторизацию так, что код стал даже короче, чем он был бы на обычном <strong>C/C++</strong>.</p>
<p>Нужно отметить, что гибкость <strong>CEAN</strong>-синтаксиса поразительна – легко записываются срезы массивов с произвольным чередованием рядов и/или столбцов. При этом для всех многомерных операций выполняется контроль размерностей операндов, и все операции компилятор обещает по возможности векторизовывать. Если все заявленные возможности действительно работают, то это почти язык <strong>APL</strong>, встроенный в компилятор.</p>
<p>Если нужно, чтобы массив был динамическим, то просто приводим указатель к типу 'int (*)[N]':</p>
<pre name="code" class="cpp">    int (* a)[N]  = (int (*)[N]) _mm_malloc(N * M * sizeof(int), 64);
</pre>
<p>К сожалению, неожиданная проблема возникает, если размер <strong>N</strong> не константен. Так, стоит определить <strong>N</strong> и <strong>M</strong> как переменные, и компиляция оборвется ошибкой:</p>
<blockquote><p>exmpl2.cpp(12): error: a variable captured by a lambda cannot have a type involving a variable-length array<br />
          a[0][j] = j + 1;<br />
          ^</p></blockquote>
<p>Дело в том, что <strong>Cilk Plus</strong> создает для тела цикла <strong>lambda</strong>-функцию <strong>C++0x</strong>, выполнение которой затем рекурсивно разветвляется вызовами <em>cilk_spawn</em>. По-видимому, текущая реализация <strong>lambda</strong>-функций не позволяет включать в замыкание многомерный массив с переменными размерами измерений.</p>
<p>Чтобы обойти эту проблему, приходится заводить псевдонимы для строк исходного массива и использовать <strong>CEAN</strong>-синтаксис с явным диапазоном операций:</p>
<pre name="code" class="cpp">
#include &lt;stdio.h&gt;
#include &lt;cstdlib&gt;
#include &lt;cilk/cilk.h&gt;
#include &lt;iostream&gt;

int main(int argc, char* argv[])
{
    int N = 16;
    int M = 16;
    int * arr  = (int *) _mm_malloc(N * M * sizeof(int), 64);
    cilk_for(int j = 0; j &lt; N; j++) {
        arr[j] = j + 1;
    }
    cilk_for(int i = 1; i &lt; M; i++) {
        int * a = &amp;(arr[i * N]);
        a[0:N] = arr[0:N] * (i + 1);
    }
    int (* a)[N] = (int (*)[N]) arr;
    for(int i = 0; i &lt; M; i++) {
        for(int j = 0; j &lt; N; j++) {
            printf("%4d ", a[i][j]);
        }
        std::cout &lt;&lt; std::endl;
    }
    _mm_free(a);
}
</pre>
<p><a href="http://gist.github.com/1380140">exmpl2.cpp</a></p>
<p>Предположим, что мы хотим заполнить таблицу умножения другим способом, размножив сначала первую строку на всю таблицу, а затем заполнив столбцы значениями префиксных сумм. Тогда код будет таким:</p>
<pre name="code" class="cpp">
#include &lt;stdio.h&gt;
#include &lt;cstdlib&gt;
#include &lt;cilk/cilk.h&gt;
#include &lt;iostream&gt;

int main(int argc, char* argv[])
{
#define N 16
#define M 16
    int (* a)[N]  = (int (*)[N]) _mm_malloc(N * M * sizeof(int), 64);
    cilk_for(int j = 0; j &lt; N; j++) {
        a[0][j] = j + 1;
    }
    cilk_for(int i = 1; i &lt; M; i++) {
        a[i][:] = a[0][:];
    }
    for(int i = 1; i &lt; M; i++) {
        a[i][:] += a[i - 1][:];
    }
    for(int i = 0; i &lt; M; i++) {
        for(int j = 0; j &lt; N; j++) {
            printf("%4d ", a[i][j]);
        }
        std::cout &lt; std::endl;
    }
    _mm_free(a);
}
</pre>
<p><a href="http://gist.github.com/1380143">exmpl3.cpp</a></p>
<p>Здесь для простоты снова предполагается, что размер <strong>N</strong> константен. Если <strong>for</strong>-цикл в строке 17 заменить на <em>cilk_for</em>, то программа станет некорректной, а компилятор об этом не сообшит. Результат работы программы в общем случае будет непредсказуем, но при <strong>M=16</strong> ошибка проявляться не будет – ветвление не вызывается для коротких циклов.</p>
<p>Значит, данный код накопления префиксных сумм векторизован, но не параллелизован. Исправить это можно, разделив матрицу на вертикальные полосы определенного размера. Но тогда, к сожалению, проявляется странная ошибка компилятора <strong>CEAN</strong>-синтаксиса, которая приводит к неверному результату, поэтому здесь пришлось вернуться к обычному внутреннему циклу:</p>
<pre name="code" class="cpp">    int chunks = 2;
    int chunk = N/chunks;   // make sure  N % chunks == 0
    cilk_for(int k = 0; k &lt; chunks; k++) {
        int start = k * chunk;
        int end = (k + 1) * chunk;
        for(int i = 1; i &lt; M; i++) {
            // this brings wrong results:
            // a[i][start:end] += a[i - 1][start:end];

            // although, this works properly:
            for(int j = start; j &lt; end; j++) {
                a[i][j] += a[i - 1][j];
            }
        }
    }
</pre>
<p><a href="http://gist.github.com/1380145">exmpl4.cpp</a></p>
<p>(Использованная версия компилятора: <strong>icpc (ICC) 12.1.0 20110811</strong>.) Данная ошибка компилятора не связана с <em>cilk_for</em> конструкцией, а при большом (динамическом) массиве данных программа начинает вести себя непредсказуемо.</p>
<p>Возвращаясь к исходной задаче, отмечаем, что в ней есть три этапа:</p>
<ol>
<li>вычисление среднего значения псевдослучайных чисел,</li>
<li>заполнение матрицы, </li>
<li>вычисление префиксных сумм.</li>
</ol>
<p>Для вычисления среднего нужно воспользоваться преобразователем (<strong>reducer</strong>), который позволяет корректно складывать промежуточные суммы, избегая конфликтов доступа к общей переменной (<em>race condition</em>), см. исходный код программы, <a href="http://gist.github.com/1380153">fill.cpp</a>.</p>
<p>Алгоритм для остальных двух этапов не отличается от уже разобранных выше. В тексте программы также применяется специальный преобразователь для вычисления псевдослучайного числа в произвольной позиции. Этот преобразователь использует ассоциативность преобразования линейного конгруэнтного генератора и позволяет понизить сложность адресации <em>n</em>-го псевдослучайного числа до <strong>O(log(log(n))</strong> (если заранее приготовлена логарифмическая шкала степеней преобразования). Код этого преобразователя был получен минимальной адаптацией примера <strong>linear-recurrence.cpp</strong>, входящего в состав компилятора <strong>Intel</strong>.</p>
<p>Подводя итоги, можно отметить замеченные преимущества и недостатки технологии <strong>Cilk Plus</strong>.</p>
<p><em>Pros:</em></p>
<ul>
<li>простота перехода от последовательного кода к параллельному</li>
<li>гибкий и эффективный механизм адресации массивов (<strong>CEAN</strong>)
</li>
<li>гибкий механизм <em>divide-and-conquer</em>, дополненный механизмом преобразователей (<strong>reducers</strong>)
</li>
<li>эффективность планирования задач и балансировка нагрузки (<strong>work-stealing</strong>)
</li>
<li>тесная интеграция с компилятором, <a href="http://software.intel.com/en-us/blogs/2011/08/09/parallelism-as-a-first-class-citizen-in-c-and-c-the-time-has-come/">Parallelism as a First Class Citizen in C and C++, the time has come</a>
</li>
</ul>
<p><em>Cons:</em></p>
<ul>
<li>трудности в работе с массивами переменного размера
</li>
<li>инструмент еще очень молодой и, возможно, недостаточно стабильный для ответственных приложений
</li>
<li>отсутствие статической проверки корректности параллелизации
</li>
</ul>
<p>Очевидно, что замеченные ошибки скоро будут исправлены.<br />
Кроме того, поскольку <strong>Cilk Plus</strong> поддерживается на уровне компиляции, есть надежда, что можно ожидать значительный прогресс как в верификации корректности программы, так и в улучшении качества кодогенерации.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/28/intel-cilk-plus/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Помочь компилятору в векторизации? — Иногда приходится...</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/12/2005666/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/12/2005666/#comments</comments>
		<pubDate>Sat, 12 Nov 2011 18:43:23 +0000</pubDate>
		<dc:creator>dsambor</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[compiler]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/12/2005666/</guid>
		<description><![CDATA[Данный пост продолжает тему, затронутую в статье "Помочь компилятору в векторизации? — Лучше просто не мешать" (<a href="http://habrahabr.ru/company/intel/blog/131159">Rus</a>, <a href="http://software.intel.com/en-us/blogs/2011/10/12/mr-compiler-may-i-help-you-with-the-loop-vectorization-not-a-disservice-please"> Eng [1]</a>).

В этой статье приведена ситуация, в которой ручное разворачивание цикла запутало компилятор и привело к тому, что код цикла векторизован не был.

Однако часто встречаются ситуации, в которых компилятор отказывается векторизовывать код, поскольку между итерациями есть зависимость по данным. Например, если в цикле накапливается сумма или ищется максимальное значение из массива.]]></description>
			<content:encoded><![CDATA[<p>Данный пост продолжает тему, затронутую в статье "Помочь компилятору в векторизации? — Лучше просто не мешать" (<a href="http://habrahabr.ru/company/intel/blog/131159">Rus</a>, <a href="http://software.intel.com/en-us/blogs/2011/10/12/mr-compiler-may-i-help-you-with-the-loop-vectorization-not-a-disservice-please"> Eng [1]</a>).</p>
<p>В этой статье приведена ситуация, в которой ручное разворачивание цикла запутало компилятор и привело к тому, что код цикла векторизован не был.</p>
<p>Однако часто встречаются ситуации, в которых компилятор отказывается векторизовывать код, поскольку между итерациями есть зависимость по данным. Например, это происходит, если в цикле накапливается сумма или ищется максимальное значение из массива данных. Обе операции, суммирование и максимум, обладают свойствами асоциативности и дистрибутивности. Поэтому массив можно разделить на независимые параллельные потоки данных, и получить окончательный ответ комбинацией промежуточных результатов.</p>
<p>И все же современные компиляторы не выполняют столь радикальных преобразований, возможно потому что не все операции строго ассоциативны (сложение вещественных чисел ограниченной точности), либо потому что такая оптимизация не считается задачей компилятора (как потом отладчику представить построчное исполнение такого кода?).</p>
<p>С другой стороны, эта задача известна как операция параллельной редукции или свертки и она автоматизирована в популярных библиотеках распараллеливания кода, таких как <strong>OpenMP</strong>, <strong>Intel Cilk+</strong>, и <strong>Intel Threading Building Blocks</strong>. Но поддержки параллелизма на уровне <strong>SIMD</strong> в большинстве подобных сред нет — программист должен вручную расслоить код на уровне процедуры (см. например код редукции, объединяющий <strong>Intel TBB</strong> и <strong>SSE</strong> технологии <a href="http://parallel-for.sourceforge.net/uma-tbb-cpu-sse.html">[2]</a>, или <strong>OpenMP</strong> и <strong>OpenCL </strong><a href="http://parallel-for.sourceforge.net/uma-openmp-opencl.html">[3]</a>). Исключениями, возможно, являются проект <strong>Intel SPMD Program Compiler</strong>, и сочетание <strong>Cilk+</strong> и <strong>Array Building Blocks</strong>, но пока нет уверенности, что две последние технологии бесшовно стыкуются.</p>
<p>Интересно что обобщение сверток — списочный гомоморфизм — является эффективным средством представления параллельных алгоритмов. Изучается также возможность использования этой модели для автоматического анализа последовательных программ с последующей генерацией параллельного алгоритма. Так, в статье <a href="http://ftp.ipl.t.u-tokyo.ac.jp/pub/PLDI2007.pdf">[4]</a> предложен алгоритм нахождения списочного гомоморфизма для последовательного алгоритма, который затем демонстрируется на примере автоматического построения параллельного алгоритма нахождения отрезка массива с максимальной суммой.</p>
<p>Таким образом, функция свертки по произвольному ассоциативному оператору, будучи эффективно поддержанной на уровне компилятора и библиотек, должна стать важным строительным блоком для более сложных параллельных алгоритмов.<br />
Тем не менее, пока требуются определенные усилия чтобы применить эти алгоритмы на современных <strong>SIMD</strong> архитектурах. Возможно, автоматическая векторизация и параллелизация будут широко использоваться только с появлением новых оптимизирующих компиляторов для функциональных языков со строгой типизацией, поскольку для таких языков проще гарантировать корректность выполняемого преобразования алгоритма и можно выявлять параллелизм автоматически, не требуя от программиста понимания вычислительной модели параллельной системы.</p>
<p>Ссылки<br />
[1] <a href="http://software.intel.com/en-us/blogs/2011/10/12/mr-compiler-may-i-help-you-with-the-loop-vectorization-not-a-disservice-please/">"-Mr Compiler, may I help you with the loop vectorization? -Not a disservice, please."</a>, Victoria Zhislina (Intel)<br />
[2] "Parallel For" project, <a href="http://parallel-for.sourceforge.net/uma-tbb-cpu-sse.html">UMA/TBB/SSE example</a><br />
[3] "Parallel For" project, <a href="http://parallel-for.sourceforge.net/uma-openmp-opencl.html">UMA/OpenMP/OpenCL example</a><br />
[4] Kazutaka Morita, Akimasa Morihata, Kiminori Matsuzaki, Zhenjiang Hu, Masato Takeichi. "Automatic Inversion Generates Divide-and-Conquer Parallel Programs" [<a href="http://ftp.ipl.t.u-tokyo.ac.jp/pub/PLDI2007.pdf">PDF</a>], Proceedings of the 2007 ACM SIGPLAN conference on Programming language design and implementation </p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/12/2005666/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

