<?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; pawnbot</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/author/pawnbot/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>Знакомство с Numa</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/11/25/numa/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/11/25/numa/#comments</comments>
		<pubDate>Fri, 25 Nov 2011 13:16:12 +0000</pubDate>
		<dc:creator>pawnbot</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Конкурсы и мероприятия]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[Numa]]></category>
		<category><![CDATA[pthreads]]></category>
		<category><![CDATA[RAM]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/11/25/numa/</guid>
		<description><![CDATA[Во время конкурса <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Acceler8</a> были обсуждения векторизации, разворотов циклов, и различных микрооптимизаций. Но, наверное, многие, как и я, сталкивались с проблемой, что какие бы оптимизации они не делали, программа на компьютере с небольшим числом ядер разгонялась, а на MTL скорость лишь неумолимо падала. Тогда стало ясно, что нужно каким-то образом разгружать RAM, иначе какой-либо прогресс невозможен. Я добавил в программу только 2 оптимизации работы с RAM, но благодаря ним становятся возможными и следующие улучшения программы, такие как векторизация.]]></description>
			<content:encoded><![CDATA[<p>Во время конкурса <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Acceler8</a> были обсуждения векторизации, разворотов циклов, и различных микрооптимизаций. Но, наверное, многие, как и я, сталкивались с проблемой, что какие бы оптимизации они не делали, программа на компьютере с небольшим числом ядер разгонялась, а на MTL скорость лишь неумолимо падала. Тогда стало ясно, что нужно каким-то образом разгружать RAM, иначе какой-либо прогресс невозможен. Я добавил в программу только 2 оптимизации работы с RAM, но благодаря ним становятся возможными и следующие улучшения программы, такие как векторизация.</p>
<h2>Numa система на MTL.</h2>
<p>На MTL стоит 256 ГБ оперативной памяти, но в системе 4 процессора Xeon, каждый из них имеет прямой доступ только к своим 64 гб оперативной памяти, а доступ к остальным 192 гб занимает вдвое больше времени.</p>
<blockquote><p>node 0 size: 64485 MB<br />
node 1 size: 64640 MB<br />
node 2 size: 64640 MB<br />
node 3 size: 64640 MB</p>
<p>node distances:<br />
node   0   1   2   3<br />
  0:  10  21  21  21<br />
  1:  21  10  21  21<br />
  2:  21  21  10  21<br />
  3:  21  21  21  10</p></blockquote>
<p>Распределение процессоров по узлам(40 - 79 есть только на логин сервере, где включен HT):</p>
<blockquote><p>node 0: 0-9, 40-49<br />
node 1: 10-19, 50-59<br />
node 2: 20-29, 60-69<br />
node 3: 30-39, 70-79
</p></blockquote>
<p>Естественно хочется избегать ситуаций, когда процессор лезет в чужую память. Возможно, <strong>OpenMP</strong> разбирается с такими проблемами автоматически, но своё решение я писал с помощью pthreads и для них решение проблемы оказалось следующим:</p>
<ol>
<li>При вызове malloc отображение виртуальной памяти на физическую ещё не строится.</li>
<li>Когда какой-то процессор обращается к куску памяти, для которого отображения ещё нет, это отображение строится на память в Numa узле этого процессора.</li>
<li>Поэтому выгодно генерировать в каждом потоке ту часть матрицы, которая будет им чаще всего использоваться, осуществить эту параллельную генерацию нам позволяет наличие периода в матрице. В общем случае пришлось бы делать memset своего куска матрицы в каждом потоке, и только потом генерировать всю матрицу в каком-то одном потоке.</li>
<li>Для того, чтобы потоки не бродили по процессорам и тем более по numa узлам, нужно их привязать к конкретным процессорам. Например, можно привязать поток номер k к процессору номер k, это даст ещё одно преимущество - мы будем знать у каких потоков есть разделяемый кэш и можем более эффективно распределять операции. В случае pthreads привязка потока к процессору осуществляется при помощи библиотеки sched.h:</li>
</ol>
<pre name="code" class="cpp">
CPU_ZERO(&amp;affinity);
CPU_SET(args-&gt;thread_number, &amp;affinity);
sched_setaffinity(0, sizeof(cpu_set_t), &amp;affinity);
</pre>
<h2>Блочный метод.</h2>
<p>Даже разобравшись с Numa системой, векторизация не даст никакого ощутимого выигрыша, пока мы не начнём по максимуму использовать кэш. Если написать стандартный метод Кадана в 3 цикла, то по сути никакие уровни кэша, кроме L3, программа использовать не будет. Естественно эта ситуация нас не устраивает, хочется изменить порядок, в котором мы обрабатываем элементы матрицы, чтобы каждый элемент, который мы положим в кэш, оставался там как можно дольше. Например, можно сделать метод Кадана блочным, т.е. выбрать некоторые размеры блока A и B, и работать с блоком, как с неделимой операцией, алгоритм в таком виде примет сложность 0.5 * (N/A)^2 * (M / B) * (A^2 * B). Естественно, сложность осталась прежней, но (A^2 B) - операции, в которых процессор не будет лезть дальше собственного кэша, и естественно эти операции будут работать эффективнее, чем если бы процессор всегда лез в оперативную память или L3 кэш. Осталось заметить только то, что блоки нужно заводить не одной операцией malloc(), а каждый поток должен сделать malloc() своих блоков. В Linux malloc() автоматически округляет выделенную память до ближайшей степени двойки, что позволит нам избежать эффекта false sharing.</p>
<h2>Что же это дало?</h2>
<p>По сути описанные выше 2 оптимизации не дают огромного прироста в скорости, но после них становится возможной эффективная векторизация, которая и даст желаемый прирост в скорости, в моём случае - в 3 раза. Так же стоит отметить, что при небольших периодах есть гораздо более эффективные методы, например за O(m^3), которые используют гораздо меньше памяти. Данные оптимизации рассчитаны скорее на задачу в общем случае, нежели на конкретную постановку.</p>
<h2>Источники:</h2>
<p>1. <a href="http://num-meth.srcc.msu.ru/zhurnal/tom_2010/pdf/v11r123.pdf"> Об оптимизации вычислительных приложений для многопроцессорных систем с общей неоднородной памятью</a><br />
2. <a href="ftp://82.96.64.7/pub/linux/kernel/people/christoph/pmig/numamemory.pdf"> Memory in a Linux/NUMA System </a></p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/11/25/numa/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

