<?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; Dmitriy Vyukov</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/author/dmitriy-vyukov/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>Свершилось - www.1024cores.net!</title>
		<link>http://software.intel.com/ru-ru/blogs/2011/01/20/www1024coresnet/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2011/01/20/www1024coresnet/#comments</comments>
		<pubDate>Thu, 20 Jan 2011 09:12:05 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Открытый код]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[concurre]]></category>
		<category><![CDATA[lock-free]]></category>
		<category><![CDATA[multicore]]></category>
		<category><![CDATA[multithreading]]></category>
		<category><![CDATA[parallel computing]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2011/01/20/www1024coresnet/</guid>
		<description><![CDATA[http://www.1024cores.net - сайт о lock-free алгоритмах, параллельных вычислениях, многопоточности, масштабируемости, многоядерности и всём таком.]]></description>
			<content:encoded><![CDATA[<p>Свершилось! Я запустил <a href="http://www.1024cores.net/">сайт</a>, посвященный lock-free, wait-free и просто масштабируемым алгоритмым синхронизации, многопоточности, параллельным вычислениям, многоядерныи процессорам, масштабируемой архитектуре программных систем, concurrency, паттернам и анти-паттернам, технологиям и библиотекам многопоточности, сопутствующему инструментарию и связанным темам:</p>
<p><a href="http://www.1024cores.net/">www.1024cores.net</a></p>
<p>Сейчас там уже есть некоторые материалы по <a href="http://www.1024cores.net/home/lock-free-algorithms/introduction">основам разработки алгоритмов синхронизации</a>, освещены некоторые практические темы (<a href="http://www.1024cores.net/home/lock-free-algorithms/reader-writer-problem">reader-writer problem</a>, <a href="http://www.1024cores.net/home/lock-free-algorithms/queues">producer-consumer queues</a>, <a href="http://www.1024cores.net/home/lock-free-algorithms/lazy-concurrent-initialization">lazy concurrent initialization</a>), некоторые статьи по <a href="http://www.1024cores.net/home/scalable-architecture">масштабируемым архитектурам</a>, коллекция моих <a href="http://www.1024cores.net/home/parallel-computing">отчётов с Intel Threading Challenge 2009/2010</a>, и некоторые другие разделы в зачаточном состоянии. Однако сайт ещё очень молодой, и предполагается активное развитие, поэтому заинтересованным предлагаю подписываться на <a href="http://feeds.feedburner.com/1024cores">RSS</a> и/или следовать за <a href="http://blog.1024cores.net/">блогом</a>.</p>
<p>Да, сайт получается больше на английском, надеюсь это не будет сильной преградой для большинства разработчиков. Однако там я собрал и некоторые <a href="http://www.1024cores.net/home/in-russian">свои материалы на русском языке</a>.</p>
<p>Stay tuned and keep threading!</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2011/01/20/www1024coresnet/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Умная производительность</title>
		<link>http://software.intel.com/ru-ru/blogs/2010/01/20/2002989/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2010/01/20/2002989/#comments</comments>
		<pubDate>Wed, 20 Jan 2010 18:20:18 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[i3]]></category>
		<category><![CDATA[Intel Core vPro]]></category>
		<category><![CDATA[Intel Turbo Boost]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2010/01/20/2002989/</guid>
		<description><![CDATA[Мой, субъективный и обрывистый, отчёт с презентации нового поколения процессоров Intel i3/i5/i7 в России]]></description>
			<content:encoded><![CDATA[<p lang="ru-RU">Вчера был на презентации нового поколения процессоров Intel i3/i5/i7 в России. Интересно, что основное внимание на презентации было уделено не увеличенной производительности - более высокая частота, улучшенная микроархитектура, больше исполнительных блоков - это всё понятно. Основное внимание было уделено поддержке новых технологий и интеллекту процессоров.</p>
<p lang="ru-RU">Особенно поразила презентация нового поколения технологии Intel Core vPro, которая предназначена для удалённого управление клавиатурой, дисплеем и мышью компьютера. Во время демонстрации "администратор" (после соответствующей паузы, сопровождающейся проигрыванием лёгкой музыки и словами "Пожалуйста, подождите, всё операторы сейчас заняты", во время которой администратор, как полагается, разложил "Косынку") удалённо подключился к компьютеру "пользователя", на котором - внимание - сломался драйвер сетевой карты (!). После подключения администратор видел изображение с монитора пользователя и мог управлять клавиатурой и мышкой (что само по себе, в общем-то, не ново). После переустановки драйвера сетевой карты, администратор - опять внимание - перезагузил компьютер пользователя (!), и сеанс удалённого управления при этом не прервался. Как говорит презентация - администратор видит то же, что и пользователь - вплоть до "синего экрана" (это, к сожалению, не демонстрировали). Впечетляетъ! Насколько я знаю, такие технологии ранее применялись только для серверного железа, ну и, естественно, вы бы их не получили за $200 вместе с процессором и материнской платой. Такие чудеса на рабочем столе стали возможны благодаря совместной работе чипсета, процессора, видео-адаптера и увеличенного до 8MB ПЗУ BIOS'а.</p>
<p lang="ru-RU">Следующая новинка - новое поколение технологии Intel Turbo Boost. Теперь она может перераспределять энергопотребление не только между ядрами процессора, но так же и между CPU и интергрированным GPU. Т.е. теперь процессор может работать либо в режиме полного пониженного энергопотребления (и CPU, и GPU работают на пониженной частоте); либо повышать частоту ядра/ядер процессора, когда вы что-то "считаете"; либо повышать частоту графического процессора, когда вы играете. Во время демонстрации GPU с номинальной частотой 500MHz то замедлялся до 367MHz, то разгонялся до 767MHz. Раньше при покупке ноутбука надо было выбирать между быстрым CPU/GPU и между экономичным, и уж платить за свой выбор всё время (когда оказывалось надо сконвертировать видео на ноутбуке, предназначенном для просмотра веб-страничек). Теперь можно просто купить "умный" процессор Intel, который будет сам подстраиваться под условия. Малина.</p>
<p lang="ru-RU">Последнее, что хочется отметить - GPU интегрированный в CPU. Технически это пока просто две, большей частью независимых, схемы, напылённых на одну подложку. Но тем не менее, стоимость и энергопотребление у этой пары такие же как и у одного CPU прошлого поколения. Как мне рассказал Алексей Рогачков, для интегрированного GPU не предполагается никаких средств программирования произвольных задач (аналогично CUDA, OpenCL и т.д.), GPU поддерживает только DirectX и OpenGL. К сожалению.</p>
<p lang="ru-RU">Если немного пофантазировать, в будущем хотелось бы видеть первазивную интеграцию CPU и GPU (раз уж они оказались на одной подложке). Так что бы с т.з. программиста они выглядели бы как единый гетерогенный многоядерный процессор, с разделяемыми кэшами и всем таким. В конце концов и то, и то - процессоры общего назначения, просто один считает лучше одни задачи, а другой - другие. Ну так и программы производят разные вычисления. От кого, как не от Intel, ожидать такой идилии? Хотелось бы уже конечно в следующем поколении процессоров, вместе с AVX, 4-way HyperThreading и транзакционной памятью <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p lang="ru-RU">
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2010/01/20/2002989/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Хамелеоны быстрые и очень быстрые</title>
		<link>http://software.intel.com/ru-ru/blogs/2009/09/15/2002127/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2009/09/15/2002127/#comments</comments>
		<pubDate>Tue, 15 Sep 2009 15:47:02 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[concurrency]]></category>
		<category><![CDATA[lock-free]]></category>
		<category><![CDATA[multithreading]]></category>
		<category><![CDATA[The Computer Language Benchmarks Game]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2009/09/15/2002127/</guid>
		<description><![CDATA[Описание моей реализации задачи chameneos-redux для The Computer Language Benchmarks Game]]></description>
			<content:encoded><![CDATA[<p>Программисты всегда были склонны мериться... ммм.... производительностью. На удовлетворение как раз этой насущной потребности и направлен проект <a href="http://shootout.alioth.debian.org">The Computer Language Benchmarks Game</a>. Идея проекта очень простая - имеется ряд задач различного плана, каждый желающий может предложить свою реализацию какой-либо задачи на своём любимом языке; далее эти реализации оцениваются на предмет производительности, читай — времени исполнения (а так же потребляемой памяти и объёма исходного кода). Текущая аппаратная платформа для тестирования — это Intel Core2Quad Q6600, в качестве ОС используется 32/64 битный Linux. Все результаты аккуратно складируются в базу данных, и посетитель сайта имеет возможность посмотреть различные «срезы» информации. Так же имеются такие занятные внешние исследования, как например <a href="http://gmarceau.qc.ca/blog/2009/05/speed-size-and-dependability-of.html">это</a>.</p>
<p>Несколько дней назад я предложил свою реализацию задачи chameneos-redux для GCC C. Если кратко, то суть задачи сводится к следующему. Имеется «место встречи» и несколько одновременно работающих «хамелеонов». Каждый хамелеон «приходит» на место встречи и смотрит, первый ли он пришёл или его уже ждёт другой хамелеон. Если он пришёл первый, то он ждёт второго хамелеона. Если он пришёл второй, то оба хамелеона изменяют свои цвета (в зависимости от их текущих цветов) и уходят. И так по-кругу. Все остальные скучные детали задания можно видеть <a href="http://shootout.alioth.debian.org/u32q/benchmark.php?test=chameneosredux&amp;lang=all">тут</a>.</p>
<p>На момент сабмита моей реализации самая быстрая реализация была за Haskell'ем с результатом 4.59 секунд, следующая за ней — С++ с результатом 4.74 секунды. Так же можно отметить: самая быстрая реализация на Java – 7 сек.; Scala – 15 сек.; Erlang – 111 сек.; Ruby — 131 сек.; Python — 221 сек. Полную таблицу можно видеть <a href="http://shootout.alioth.debian.org/u32q/benchmark.php?test=chameneosredux&amp;lang=all">здесь</a>.</p>
<p>Результат моей реализации — 0.72 секунды.<br />
Полный код можно видеть <a href="http://shootout.alioth.debian.org/u32q/benchmark.php?test=chameneosredux&amp;lang=gcc&amp;id=5">здесь</a>.</p>
<p>Кстати, на первый взгляд моя реализация очень похожа вот на <a href="http://shootout.alioth.debian.org/u32q/benchmark.php?test=chameneosredux&amp;lang=gpp&amp;id=1">эту</a> реализацию на С++ - основной код реализации "встречи" практически идентичен. Однако, реализация на С++ выполняется 8.74 секунд, т.е. более чем в 12 раз медленнее.</p>
<p>Что же позволило добиться столь высокого результата и обойти ближайшего соперника более чем в 6 раз? Задача достаточна интересна с точки зрения параллелизма (или точнее сказать concurrency, но для concurrency я не могу подобрать хорошего русского термина), т.к. фактически она требует не добавления как можно большего параллелизма, а наоборот сведения параллелизма к минимуму. Задача по своей сути требует постоянного взаимодействия потоков практически без какой-либо локальной работы потоков; а специфика современных многоядерных процессоров Intel такова, что взаимодействия потоков очень дороги. Поэтому оптимальная реализация будет использовать лишь один поток ОС и кооперативное планирование легковесных потоков поверх (именно поэтому реализация на Haskell [была] столь быстра — она использует кооперативное планирование легковесных потоков языка для реализации параллелизма; и именно поэтому же столь медленна реализация на Erlang – она пытается распределять хамелеонов по различным потокам ОС, рассчитывая, что они будут совершать какую-то локальную работу). Но, к сожалению, использование кооперативных потоков и собственных планировщиков запрещено правилами, а постоянное переключение ядерных потоков слишком дорого. Поэтому лучшее, что мы можем сделать, - это привязать потоки хамелеонов к двум (из четырёх) ядрам, это даёт нам возможность устраивать встречи без переключений ядерных потоков (один хамелеон работает на одном ядре, и параллельно второй хамелеон — на втором), и при этом свести конкуренцию за разделяемые ресурсы к минимуму.</p>
<p>Итак, основные моменты реализации.<br />
1. Оптимальное распределение потоков по ядрам.<br />
По условию задачи необходимо провести две независимых серии встреч: первая — для 3 хамелеонов, вторая — для 10 хамелеонов. Т.к. было решено привязать все потоки хамелеонов, участвующих во встрече, только к двум ядрам, вырисовывается достаточно логичная схема: на двух ядрах проходит первая серия встреч, и параллельно на оставшихся двух ядрах — вторая серия встреч. Альтернативный вариант — последовательно провести первую и вторую серию встреч на всех четырёх ядрах; так поступили практически все реализации, которые используют физический параллелизм, однако при этом время выполнения увеличивается примерно в 5 раз.<br />
К каким парам ядер привязывать потоки, относящиеся к одной встрече? Всё равно? Далеко не всё равно! Процессор Q6600 уникален тем, что это фактически 2 независимых двух-ядерных процессора, объединенных на одной подложке, и соединённых между собой шиной. Соответственно и стоимость взаимодействия между разными парами ядер существенно различается. Потоки, относящиеся к одной встрече, должны быть привязаны к одному «подпроцессору» в составе Q6600.<br />
ОС Linux тут преподносит свои сюрпризы. Наивное предположение, что смежные ядра являются логическими процессорами 0 и 1 (2 и 3) было сразу развеяно временем выполнения в 3 секунды. Беглый просмотр кода ядра показал, что загрузочный код вычитывает порядок ядер откуда-то из BIOS'а, и потом остальная часть ядра сама слабо представляет топологию процессора. На тестовой машине смежные ядра оказались логическими процессорами 0 и 2 (1 и 3). Однако единственный интерфейс, через который можно получить эту информацию от ядра, есть парсинг текстового файла /proc/cpuinfo (см. функцию get_affinity()).</p>
<p>2. Минимально необходимое количество максимально лёгких взаимодействий.<br />
Условие задачи требует как минимум 3 взаимодействий между потоками (читай — модификаций разделяемых данных) для проведения одной встречи: (1) первый хамелеон сообщает, что он пришёл первый, (2) второй хамелеон сообщает первому, что тоже пришёл (и обмениваются цветами), (3) хамелеоны освобождают место встречи. Так же необходимо вести подсчёт общего числа прошедших встречь, т.к. необходимо завершить работу после совершения N встреч.<br />
Я закодировал место встречи одним машинным словом:</p>
<pre class="cpp" name="code">
struct meeting_place_t
{
  uintptr_t volatile state;
};</pre>
<p>При этом младшие 8 бит отводятся под индекс хамелеона, который ожидает на месте встречи; а оставшиеся биты используются для подсчёта кол-ва прошедших встреч.<br />
Вот (слегка упрощённый) код основной функции chameneos_func() (обратите внимание на использование интринсика компилятора GCC __sync_val_compare_and_swap()):</p>
<pre class="cpp" name="code">state = place-&gt;state;
for (;;)
{
  peer_idx = state &amp; CHAMENEOS_IDX_MASK;
  if (peer_idx) // уже кто-то ждёт?
    xchg = state - peer_idx - (1 &lt;&lt; MEET_COUNT_SHIFT);
  else if (state) // нужно проводить встречи дальше?
    xchg = state | my_id;
  else // N встречь уже проведено
    break;
  prev = __sync_val_compare_and_swap(&amp;place-&gt;state, state, xchg);
  if (prev == state) // CAS удался?
  {
    if (peer_idx == 0)
    {
      // сюда попадает "первый" хамелеон, пришедший на место встречи
      // ожидаем второго хамелеона (упрощено)
      while (chameneos-&gt;meeting_completed == 0) {}
      state = place-&gt;state;
    }
    else
    {
      // сюда попадает "второй" хамелеон, пришедший на место встречи
      // обмениваемся цветами с хамелеоном peer_idx (не показано)
      // и уведомляем его о завершении встречи
      peer-&gt;meeting_completed = 1;
    }
  }
  else
  {
    // CAS провалился, обновляем текущее состояние и пробудем повторить
    state = prev;
  }
}</pre>
<p>3. Эффективная реализация ожидания.<br />
Поскольку мы отводим под потоки, участвующие во встрече, 2 ядра, и не хотим постоянных переключений ядерных потоков, необходимо использовать активное спин ожидание. Однако чисто активное спин ожидание будет плохо работать в следующих случаях. Первый случай - допустим второй хамелеон успешно приходит на место встречи, т.е. успешно выполняет __sync_val_compare_and_swap(); но далее его поток вытесняется до того, как он успевает уведомить первого. Первый хамелеон будет ждать в активном спин-ожидании до конца своего кванта (~10мс), пока ОС не вытеснит и его. Второй случай - допустим на машине периодически работают какие-то сторонние процессы. Если сторонний процесс будет выполняться на одном из наших ядер, то при каждой встрече первый хамелеон будет ждать в активном спин-ожидании до конца своего кванта.<br />
Т.о. ожидание было модифицировано так, что поток добровольно отдаёт управление, если ему приходится ждать слишком долго:</p>
<pre class="cpp" name="code">spin_count = 20000;
while (chameneos-&gt;meeting_completed == 0)
{

  if (spin_count)
    spin_count -= 1;
  else
    sched_yield();
}</pre>
<p>Это даёт одновременно и минимальную латентность активного спин ожидания и защиту от бесцельного съедания потоком всего кванта времени, если что-то "пошло не так". На практике эта модификация дала прирост производительности от 10% до 200% (в зависимости от загрузки машины другими потоками), а так же существенно снизила дисперсию.<br />
В реальном коде Вы так же можете видеть реализацию ожидания для случая, когда под процесс отводится только одно ядро, в таком случае не остаётся ничего делать, кроме как сразу передавать управлению другому потоку.</p>
<p>4. Устранение ложного разделения данных.<br />
На уровне процессора разделение данных происходит не на уровне отдельных байт или слов памяти, а на уровне кэш-линий. Кэш-линия обычно содержит несколько машинных слов, на современных процессорах кэш-линии обычно бывают в диапазоне 16-512 байт, на современных процессорах Intel x86 (IA-32/Intel64) кэш-линия равна 64 байтам. Т.о. если какие-либо две переменные оказываются расположенными в одной кэш-линии (ложное разделение, false sharing), доступы к ним из разных потоков будут приводить к физической передаче данных между ядрами, что является очень медленным - порядка 100-500 таков в зависимости от модели процессоров, характеристик соединителя и топологии. Одним словом - этого допускать нельзя.<br />
Для устранения любой возможности ложного разделения данных я реализовал простую обёртку над функциями управления динамической памятью, и выделяю все структуры данных (хамелеонов и место встречи) с помощью них. Идея очень проста - функция выделения памяти округляет адрес блока памяти вверх до ближайшей границы кэш-линии:</p>
<pre class="cpp" name="code">
#define CL_SIZE 64
void* cache_aligned_malloc(size_t sz)
{
    char*                       mem;
    char*                       res;
    void**                      pos;
    mem = (char*)malloc(sz + 2 * CL_SIZE);
    if (mem == 0)
        exit(1);
    res = (char*)((uintptr_t)(mem + CL_SIZE) &amp; ~(CL_SIZE - 1));
    pos = (void**)(res - sizeof(void*));
    pos[0] = mem;
    return res;
}
void cache_aligned_free(void* res)
{
    void*                       mem;
    void**                      pos;
    assert(((uintptr_t)res &amp; (CL_SIZE - 1)) == 0);
    pos = (void**)((char*)res - sizeof(void*));
    mem = pos[0];
    free(mem);
}
</pre>
<p>Вот, наверное, и всё. Вышеперечисленные 4 аспекта и позволили получить более чем 12-ти кратное ускорение по-сравнению с "аналогичной" программой на С++. Справедливости ради стоит заметить, что эти аспекты никоим образом не связаны с С как таковым или компилятором GCC, они связаны с исконным для С предоставлением доступа к низлежащему аппаратному обеспечению и ОС, а так же с возможностью точно контролировать раскладку и размещение объектов. С++ не уступает С в этих аспектах.<br />
Ничто не бесплатно в нашем мире, вместе с возросшей производительностью увеличился и объём исходного кода. Моя реализация примерно в 4 раза больше реализаций на Haskell и Erlang, и примерно в 1.5 раза больше других реализаций на С/С++. Не последнюю роль в этой сыграл мягко говоря странный интерфейс Linux для получения топологии системы в виде парсинга текстового файла (при использовании Windows этот код был бы не нужен, так же как и функции cache_aligned_malloc()/cache_aligned_free() - они уже встроены в ран-тайм MS Visual C++).</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2009/09/15/2002127/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Оттенки FIFO</title>
		<link>http://software.intel.com/ru-ru/blogs/2009/02/07/fifo/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2009/02/07/fifo/#comments</comments>
		<pubDate>Sat, 07 Feb 2009 18:45:26 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[multi-threading]]></category>
		<category><![CDATA[ordering]]></category>
		<category><![CDATA[scheduling]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2009/02/07/fifo/</guid>
		<description><![CDATA[Контекст — различные системы, в которых есть производители/ потребители/ актёры/ агенты/ процессы и т.д., и в которых так или иначе происходит обмен сообщениями/ задачами/ объектами/ элементами работы и т.д. В таких системах зачастую даются какие-либо гарантии относительно порядка передачи сообщений, и одна из самых распространенных гарантий — это FIFO. Вот его-то мы и препарируем. В [...]]]></description>
			<content:encoded><![CDATA[<p><!-- 		@page { size: 21cm 29.7cm; margin: 2cm } 		P { margin-bottom: 0.21cm } --></p>
<p lang="ru-RU">Контекст — различные системы, в которых есть производители/ потребители/ актёры/ агенты/ процессы и т.д., и в которых так или иначе происходит обмен сообщениями/ задачами/ объектами/ элементами работы и т.д. В таких системах зачастую даются какие-либо гарантии относительно порядка передачи сообщений, и одна из самых распространенных гарантий — это <span lang="en-US">FIFO. </span>Вот его-то мы и препарируем.</p>
<p>В однопоточном окружении всё очень просто - у нас либо есть FIFO, либо нет FIFO совсем. Причин иметь какие-либо промежуточные варианты обычно практически нет. Однако в многопоточном окружении ситуация становится интереснее, т.к. промежуточные варианты могут иметь существенные последствия для производительности и/или масштабируемости. Итак, какие варианты есть в многопоточном окружении (в порядке убывания свойств, т.е. каждый последующий вариант есть подмножество предыдущего):<br />
- полный FIFO, или причинно-следственный FIFO (causal FIFO)<br />
- FIFO для каждого отдельного производителя (per-producer FIFO)<br />
- почти FIFO (best-effort FIFO)<br />
- отсутствие FIFO (no FIFO)</p>
<p>Остановимся на каждом варианте подробнее.</p>
<p><strong>Causal FIFO.</strong><br />
Самое сильное из FIFO свойств, можно сказать "полный FIFO". Формально можно охарактеризовать следующим образом. Гарантируется FIFO порядок на основе happened-before [<a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf">1</a>, <a href="http://en.wikipedia.org/wiki/Happened-before">2</a>] отношения, т.е. если какое-то сообщение отослано раньше другого, то оно будет и потреблено раньше. Актуален этот вариант в основном в ситуации, когда есть только один потребитель, т.к. иначе этот вариант фактически деградирует до best-effort FIFO. В данном случае термин "раньше" накладывает частичный порядок на все сообщения, проходящие через очередь, а не только на сообщения одного производителя (как в варианте per-producer FIFO). Очевидно, что это свойство является надмножеством per-producer FIFO, т.к. для сообщений от одного производителя happened-before порядок согласуется с программным порядком. Реализовывать такую очередь можно двумя способами. Первый - при добавлении сообщения в очередь, поток захватывает некий мьютекс (либо выполняет атомарную RMW (Interlocked) операцию над некой ячейкой памяти), это обеспечивает автоматическое упорядочивание сообщений от нескольких производителей/потоков (из определения мьютекса). Достаточно очевидно (по крайней мере с практической т.з.), что такой порядок будет согласовываться с happened-before. Второй - сообщения не упорядочиваются при добавлении в очередь (допустим внутреннее очередь состоит из множества очередей, по одной для каждого производителя, либо на группу производителей), однако при извлечении сообщения потребитель "вручную" мультиплексирует внутренние очереди, и т.о. обеспечивает соответствие порядка happened-before (например, на основе векторных часов - vector clock <a href="http://en.wikipedia.org/wiki/Vector_clocks">[3]</a>, или Lamport timestamping <a href="http://en.wikipedia.org/wiki/Lamport_timestamps">[4]</a>). Такая реализация может применяться и в нераспределенной системе, однако наиболее интересна для распределенных систем (где производить упорядочивание в момент добавления в очередь проблематично/невозможно).<br />
В идеале, causal FIFO - это то, что всегда бы хотелось видеть конечному пользователю, однако это и самое "дорогое" свойство, т.к. наложение некого глобального порядка на распределенную систему (тут под распределенной системой я имею в виду и многоядерные процессоры в т.ч.) не может быть бесплатным. Именно поэтому, например, Erlang [<a href="http://erlang.org">5</a>, <a href="http://en.wikipedia.org/wiki/Erlang_(programming_language)">6</a>] и не обеспечивает causal FIFO, а обеспечивает только per-producer FIFO (я думаю, что корни этого в том, что изначально Erlang предназначался для физически распределенных систем).</p>
<p><strong>Per-producer FIFO.</strong><br />
Формально можно охарактеризовать следующим образом. Гарантируется FIFO порядок для каждой отдельной пары производитель-потребитель, т.е. если один производитель производит несколько сообщений, и их потребляет один потребитель, то потребитель потребит их именно в том порядке, в котором их произвёл производитель (тут опять же в основном имеет смысл рассматривать ситуацию с одним потребителем, т.к. иначе это свойство деградирует до best-effort FIFO). Гарантия per-producer FIFO целесообразна, когда (1) упорядочивание между производителями не требуется, или (2) система распределенная и упорядочивание между производителями выполнить сложно/невозможно, или (3) конечный пользователь готов мириться с отсутствием упорядочивания между производителями. Как плюс (по сравнению с применением causal-fifo) мы получаем более масштабируемую реализацию. Традиционный вариант реализации - очередь (multi-producer/single-consumer) внутреннее состоит из множества single-producer/single-consumer очередей (по одной на каждого производителя), производитель кладёт сообщения в свою очередь, потребитель "опрашивает" очереди в произвольном порядке (возможно, отдавая предпочтение той, из которой выбиралось сообщение в предыдущий раз). В распределенной системе это - фактически единственный вариант реализации; каждая single-producer/single-consumer очередь тут будет являться, например, TCP соединением.</p>
<p>Рассмотрим практический пример, где causal FIFO и per-producer FIFO различаются существенным образом. Допустим у нас есть агенты А, Б и В; агент А отсылает агенту Б сообщение 1; далее агент А отсылает сообщение 2 агенту В; далее агент В отсылает сообщение 3 агенту Б. При наличии гарантии causal FIFO агнет Б гарантированно получит сообщение 1 раньше сообщения 3 (сообщение 1 "happened-before" сообщения 3). При наличии только гарантии per-producer FIFO (например - Erlang), агент Б может получить сообщение 3 раньше сообщения 1 (т.к. они от разных производителей).</p>
<p><strong>Best-effort FIFO.</strong><br />
Фактически это не FIFO совсем, т.к. никаких конкретных гарантий относительно порядка не предоставляется; "гарантируется" только что реализация будет "стараться" по-возможности обеспечивать FIFO. Однако это - интересная на практике гарантия по следующим причинам. Во-первых, иногда именно она и нужна; например, в контексте некого абстрактного веб-вервера/сервиса мы хотим, что бы запросы обрабатывались "примерно" в FIFO порядке (для обеспечения справедливой (fair) обработки), однако строгих гарантий не требуется, и, если реализация может быть более эффективной иначе, то нас это вполне устраивает. Во-вторых, более сильные FIFO гарантии (per-producer, causal) всё равно деградируют до best-effort в присутствии нескольких потребителей. В-третьих, best-effort FIFO действительно можно реализоваться более эффективно.<br />
В качестве примера можно рассмотреть следующую ситуацию. Веб-сервис. Необходимо реализовать планировщик запросов, желателен некий FIFO-подобный порядок для обеспечения справедливой (fair) обработки. Традиционный подход обычно состоит в создании единой FIFO очереди запросов, защищённой мьютексом; новые запросы добавляются в хвост очереди; рабочие потоки по-необходимости извлекают запросы из головы очереди. Основной недостаток этого подхода - это излишняя централизация данных (а централизованные данные не могут быть масштабируемыми). Альтернативный подход, который пользуется знанием, что никакие конкретные гарантии FIFO нам не нужны, будет следующим. С каждым рабочим потоком ассоциирована single-producer/multi-consumer FIFO очередь запросов; при поступлении нового запроса рабочий поток кладёт его в хвост своей очереди; при необходимости взять следующий запрос для обработки поток берёт его из головы своей очереди, если она не пустая, или из головы очереди какого-либо другого потока иначе (work stealing). Такой дизайн обеспечивает справедливую обработку, т.е. любой запрос будет обработан в конечное время, однако структура полностью распределенная (читай - масштабируемая).</p>
<p><strong>No FIFO.</strong><br />
Вариант отсутствия FIFO (например, LIFO) тоже интересен для рассмотрения, т.к. для некоторых ситуаций вообще не требуется справедливой (fair) обработки, а LIFO в такой ситуации может давать значительно лучшие показатели производительности, из-за лучшей локальности обработки (последний созданный элемент работы, читай - "горячий" в кэше, обрабатывается первым). Как пример можно привести распределенный аллокатор памяти <a href="http://people.cs.vt.edu/~scschnei/streamflow/">[7]</a> - каждый поток имеет приватный пул памяти, но требуется передача блоков/суперблоков памяти между потоками (приватными пулами). Для передачи блоков целесообразно рассмотреть использование очереди, обеспечивающей LIFO порядок; т.к. LIFO будет обеспечивать переиспользование наиболее "горячих" в кэше/памяти блоков.<br />
Другой пример - планировщик "вычислительных" задач (например TBB, TPL, Cilk, когда предполагается, что объём работы конечен, а порядок не важен) <a href="ftp://theory.lcs.mit.edu/pub/cilk/focs94.ps.Z">[8]</a>. Все такие системы используют преимущественно LIFO (опять же из-за лучшей локальности). Классическая схема реализации следующая. С каждым рабочим потоком связан дек (deque) задач (элементов работы). При создании новой задачи поток помещает его в голову своего дека; при поиске следующей задачи для выполнения поток вначале пытается взять задачу из головы своего дека, а если он пуст -  берёт задачу из хвоста дека другого потока (work stealing <a href="ftp://theory.lcs.mit.edu/pub/cilk/focs94.ps.Z">[8]</a>).<br />
В принципе, улучшенная локальность одинакова полезна как для многопоточного окружения, так и для однопоточного (обход дерева в ширину vs обход дерева в глубину). Основная идея тут - что вначале надо определить, требуется ли вообще FIFO как таковое.</p>
<p>Вот, наверное, и всё. Как резюме можно сказать, что как и разработчику системы, так и конечному пользователю полезно различать оттенки <span lang="en-US">FIFO. </span><span lang="ru-RU">Разработчику — для выбора наиболее подходящей реализации и четкого отражения гарантий в документации, да и вообще для осмысленного выбора между ними (особенно это касается </span><span lang="en-US">causal FIFO vs per-producer FIFO</span><span lang="ru-RU">). Конечному пользователю — для того, что бы не делать необоснованных предположений относительно гарантий, предоставляемых системой.</span></p>
<p>[1] Time, Clocks, and the Ordering of Events in a Distributed System. Leslie Lamport. 1978. <a href="http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf">http://research.microsoft.com/en-us/um/people/lamport/pubs/time-clocks.pdf</a><br />
[2] Happened-before, <a href="http://en.wikipedia.org/wiki/Happened-before">http://en.wikipedia.org/wiki/Happened-before</a><br />
[3] Vector clocks, <a href="http://en.wikipedia.org/wiki/Vector_clocks">http://en.wikipedia.org/wiki/Vector_clocks</a><br />
[4] Lamport timestamps, <a href="http://en.wikipedia.org/wiki/Lamport_timestamps">http://en.wikipedia.org/wiki/Lamport_timestamps</a><br />
[5] Erlang, <a href="http://erlang.org">http://erlang.org</a><br />
[6] Erlang, <a href="http://en.wikipedia.org/wiki/Erlang_(programming_language)">http://en.wikipedia.org/wiki/Erlang_(programming_language)</a><br />
[7] Streamflow, <a href="http://people.cs.vt.edu/~scschnei/streamflow/">http://people.cs.vt.edu/~scschnei/streamflow/</a><br />
[8] Scheduling Multithreaded Computations by Work Stealing. Robert D. Blumofe, Charles E. Leiserson. 1994. <a href="ftp://theory.lcs.mit.edu/pub/cilk/focs94.ps.Z">ftp://theory.lcs.mit.edu/pub/cilk/focs94.ps.Z</a></p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2009/02/07/fifo/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>3 базовых вещи относительно параллельных вычислений</title>
		<link>http://software.intel.com/ru-ru/blogs/2008/10/27/344/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2008/10/27/344/#comments</comments>
		<pubDate>Mon, 27 Oct 2008 13:10:51 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[HPC]]></category>
		<category><![CDATA[TBB]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2008/10/27/344/</guid>
		<description><![CDATA[3 базовых вещи относительно параллельных вычислений, и они же - 3 основные ошибки, которые часто допускают программисты при реализации параллельных алгоритмов.  Ошибки в том плане, что они могут серьёзно снижать производительность и приводить не к ожидаемой линейной масштабируемости, а к супер-линейной деградации производительности при увеличении количества процессоров/ядер. Они абсолютно иррелевантны используемой технологии, будь то Threading [...]]]></description>
			<content:encoded><![CDATA[<p>3 базовых вещи относительно параллельных вычислений, и они же - 3 основные ошибки, которые часто допускают программисты при реализации параллельных алгоритмов.  Ошибки в том плане, что они могут серьёзно снижать производительность и приводить не к ожидаемой линейной масштабируемости, а к супер-линейной деградации производительности при увеличении количества процессоров/ядер.</p>
<p>Они абсолютно иррелевантны используемой технологии, будь то Threading Building Blocks, Task Parallel Library, Java Fork/Join, OpenMP, Cilk, Parallel Pattern Library или же что-то кустарного производства. Даже <a href="http://software.intel.com/en-us/articles/intel-concurrent-collections-for-cc">Intel Concurrent Collections</a>, которая позиционируется как "No knowledge of parallel technologies required to write correct programs that execute in parallel", на самом деле подвержена в той или иной степени этим моментам.</p>
<p>Итак, первый момент - <strong>гранулярность элементов работы</strong>. Что бы достичь хорошей производительности, гранулярность элементов работы должна быть тщательно выбрана. Слишком мелкие элементы работы будут вносить колоссальные накладные расходы. Слишком большие - не будут давать достаточного параллелизма.</p>
<p>Рассмотрим более детально. Когда я тестировал на разных компьютерах издержки TBB на синтетическом бенчмарке, то у меня получались издержки примерно в 500-600 тактов на 1 задачу (task). Т.е. если мы вложим в каждую задачу полезной работы на 10 тактов, то получим издержки примерно в 5 000%. Очень ориентировочно минимальный объём полезной работы на задачу должен быть порядка 10 000 тактов (тогда издержки будут примерно 5%). По поводу максимального размера задачи сказать сложнее, но, по крайней мере, общее количество задач должно быть не меньше чем, ну скажем, 16*количество_ядер (для обеспечения балансировки нагрузки), и так же абсолютный размер задачи должен быть в разумных пределах, скажем, не больше 100 мс, если речь идёт о клиентском приложении. Т.к. последняя оставшаяся задача, по несчастливому стечению обстоятельств, может начать выполняться как раз в тот момент, когда все остальные задачи уже как раз выполнены, т.о. время её выполнения будет прибавлено к общему времени выполнения алгоритма (последняя задача как бы не будет распараллелена).</p>
<p><strong>Чрезмерное разделение данных</strong>. Для того, что бы достичь хорошей масштабируемости, каждый поток должен работать преимущественно со своими индивидуальными данными. Такой "невинный" факт, что каждый поток, на каждой итерации изменяет какую-то глобальную переменную (или ограниченный набор переменных) может (и будет) приводить не к ожидаемой линейной масштабируемости, а к супер-линейной деградации производительности. Тут так же надо учитывать тот факт, что аппаратное обеспечение считает "индивидуальными данными" не различные переменные на уровне исходного кода, а различные кэш-линии (т.н. false-sharing).</p>
<p>К этой проблеме более склонны низкоуровневые модели программирования (OpenMP, TBB tasks), в то время как более высокоуровневые модели (алгоритмы TBB - parallel_reduce, parallel_scan; Intel Concurrent Collections) способы содержать в себе больше "интеллекта" для предотвращения проблемы.</p>
<p><strong>Локальность</strong>. Не смотря на то, что подсистема памяти современного компьютера всё ещё называется RAM (random-access memory, память с произвольным доступом), сейчас она является чем-то типа сложной распределенной гетерогенной иерархической системы. К счастью, есть очень простые правила как использовать её эффективно:</p>
<ul>
<li>Предпочитайте последовательный доступ к памяти (иронично по отношению к названию памяти - "память с произвольным доступом").</li>
<li>Используйте все, загруженные в кэш, данные.</li>
<li>Повторно используйте данные, пока они ещё в кэше.</li>
</ul>
<p>Более коротко: локальность и предсказуемость. Причём локальность как в пространстве, так и во времени. Т.е. RAM лучше не использовать как RAM <img src='http://software.intel.com/ru-ru/blogs/wordpress/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
<p>Хотя рекомендация локальности обращений к памяти сейчас одинаково применима и к однопоточному программированию, в многопоточном программировании (например, в модели, основанной на задачах - tasks) сложнее понять, например, будет ли у нас последовательный доступ к данным или нет. И опять же, более высокоуровневые модели менее подвержены этой проблеме.</p>
<p>Ну вот, наверное, и всё. Буду рад услышать любые комментарии и замечания.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2008/10/27/344/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Что такое модель памяти? И с чем её едят?</title>
		<link>http://software.intel.com/ru-ru/blogs/2008/09/25/215/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2008/09/25/215/#comments</comments>
		<pubDate>Thu, 25 Sep 2008 07:17:50 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[atomicity]]></category>
		<category><![CDATA[memory model]]></category>
		<category><![CDATA[ordering]]></category>
		<category><![CDATA[visibility]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2008/09/25/215/</guid>
		<description><![CDATA[Я часто сталкиваюсь с вопросами или недопониманием относительно того, что такое модель памяти, какого плана гарантии она даёт, какого плана гарантии она не даёт и т.д. Да и вообще, модель памяти - это самый фундаментальный момент касательно многопоточности и синхронизации. Поэтому я решил посвятить свой второй пост на ISN именно этому вопросу. Итак к делу. [...]]]></description>
			<content:encoded><![CDATA[<p>Я часто сталкиваюсь с вопросами или недопониманием относительно того, что такое модель памяти, какого плана гарантии она даёт, какого плана гарантии она не даёт и т.д. Да и вообще, модель памяти - это самый фундаментальный момент касательно многопоточности и синхронизации. Поэтому я решил посвятить свой второй пост на ISN именно этому вопросу.</p>
<p>Итак к делу. Модель памяти определяет 3 фундаментальных свойства: <strong>атомарность, видимость и упорядочивание</strong>.</p>
<p><strong>Атомарность (atomicity)</strong>. Что такое атомарность в целом, я надеюсь, понятно. Это - "неделимость" операции в контексте многопоточного исполнения. Или, если переформулировать, может ли другой поток видеть промежуточное состояние операции. Атомарность необходимо рассмотреть для 2 типов операций.</p>
<p>Первый тип - это обычные сохранения из памяти и загрузки в память. Модель памяти специфицирует должны ли обычные сохранения и загрузки быть атомарными или нет. Типовая гарантия, характерная для большинства современного аппаратного обеспечения, - это сохранения и загрузки размером с машинное слово по выровненному адресу - атомарны. Модель памяти может так же предоставлять дополнительные гарантии, например, что сохранения и загрузки размером с пол-слова или с двойное слово тоже атомарны.</p>
<p>Второй тип - это так называемые RMW (read-modify-write) операции. Среди них могут быть - exchange, compare_exchange, fetch_add, increment, load_linked/store_conditional и др. Модель памяти (вместе с набором команд процессора) определяет какие RMW операции доступны и являются ли они атомарными. Обычно современные процессоры предоставляют как минимум атомарную операцию compare_exchange (или аналогичную load_linked/store_conditional), и, возможно, какие-то другие команды, например, fetch_add и exchange.</p>
<p>Многие связывают атомарность только с RMW операциями. Однако это неправильно. Важно понимать атомарность и для обычных сохранений и загрузок, т.к. на них строится большое число алгоритмов.</p>
<p><strong>Видимость (visibility)</strong>. Под видимостью понимается то, через какое время другие потоки увидят изменения, сделанные данным потоком, и увидят ли вообще. Многие почему-то считают это свойство очень важным, и порываются что-то предпринимать для его обеспечения. На практике, это - как раз самое неинтересное свойство для программиста и ничего предпринимать для его обеспечения не надо. Ну точнее так, ничего не надо предпринимать на кэш-когерентных архитектурах (коими являются все распространённые архитектуры: x86, Itanium, PPC, SPARC  и т.д.). Когерентный кэш обеспечивает автоматическое и немедленное распространение всех изменений всем заинтересованным процессорам/ядрам. Т.е. можно считать, что любая запись в память становится немедленно видимой всем остальным потокам.</p>
<p>На не кэш-когерентных архитектурах ситуация иная - изменения автоматически не распространяются, и для их распространения надо вручную предпринимать какие-то специальные меры. Однако не кэш-когерентные архитектуры - это очень узкоспециализированная ниша, и к тому же каждая такая архитектура достаточно уникальна. Поэтому говорить о них "в общем" не имеет смысла, и такие архитектуры я более рассматривать не буду.</p>
<p><strong>Упорядочивание (ordering)</strong>. При однопоточном исполнении аппаратура обеспечивает так называемую sequential self-consistency, т.е. для программы всё выглядит так, как будто все обращения к памяти происходят именно в том порядке, в каком они указаны в программе. На самом деле исполнение может производится в другом порядке, однако, пока речь идёт только об одном потоке, аппаратура маскирует этот факт от программиста. При многопоточном исполнении картина кардинально меняется, и другие потоки могут видеть сохранения в память в порядке отличном от программного.</p>
<p>Упорядочивание - это наиболее важное и сложное свойство. Модель памяти должна определять какие переупорядочивания возможны, а какие - нет. Для обеспечения необходимого упорядочивания аппаратная платформа обычно предоставляет так называемые барьеры памяти, это специальные команды, которые запрещают некоторые типы переупорядочиваний вокруг себя. Барьеры памяти бывают двух типов: двусторонние или связанные с операциями. Двусторонние барьеры памяти запрещают какому-то типу обращений к памяти (сохранениям или загрузкам) перемещаться через барьер "вниз", и одновременно другому (хотя возможно и тому же)  типу обращений к памяти перемещаться через барьер "вверх". Барьеры связанные с операциями всегда связаны с какой-то операцией (не удивительно)  - сохранением, загрузкой или атомарной RMW операцией, и предотвращают перемещение обращений к памяти вверх или вниз относительно операции (или и вверх, и вниз). Относительно барьеров важно понимать один момент: барьеры памяти - это всегда игра двух игроков, т.е. поток, который производит, например, запись, должен выполнить барьер, и поток, который соответственно читает, так же должен выполнить барьер. Достичь какого-либо упорядочивания, если барьер исполняет только один поток, - невозможно.</p>
<p>Ну вот, собственно, совокупность гарантий по каждому из этих 3 пунктов - это и есть основное содержание модели памяти для программиста занимающегося многопоточностью и синхронизацией. Однако с моделью памяти в более широком смысле связаны также и другие свойства. Например такое даже более фундаментальное, но в тоже время и более очевидное свойство как размер указателя (8/16/32/64 бита). Или организация кэшей, их кол-во, размер, ассоциативность и т.д. Так же для некоторых аппаратных платформ у разработчика есть возможность выбирать режим работы/адресации (например TINY, SMALL, LARGE, FLAT). Однако я описываю модель памяти, так сказать, со своей колокольни. Я описал те свойства, которые наиболее релевантны и интересны в контексте многоядерности, и относительно которых сейчас имеется серьёзное недопонимание и/или интерес со стороны разработчиков.</p>
<p>Описание получилось достаточно поверхностным и, так сказать, вводным в вопрос... что в общем-то неудивительно, учитывая сложность и обширность вопроса. Хотелось бы услышать, что здесь описано не достаточно понятно, какие части надо более детализировать, и в какую сторону есть желание двигаться дальше. Ну и естественно замечания, указания на неточности и комментарии приветствуются.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2008/09/25/215/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>Hello, ISN!</title>
		<link>http://software.intel.com/ru-ru/blogs/2008/09/23/hello-isn/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2008/09/23/hello-isn/#comments</comments>
		<pubDate>Tue, 23 Sep 2008 14:15:19 +0000</pubDate>
		<dc:creator>Dmitriy Vyukov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[atomic-free]]></category>
		<category><![CDATA[lock-free]]></category>
		<category><![CDATA[obstruction-free]]></category>
		<category><![CDATA[wait-free]]></category>
		<category><![CDATA[алгоритмы синхронизации]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2008/09/23/hello-isn/</guid>
		<description><![CDATA[Приветствую! Меня зовут Дмитрий Вьюков. Добро пожаловать в мой блог. Дабы сразу устранить возможные недоразумения - я не являюсь сотрудником Intel, просто, так сказать, удостоился чести вести блог на ISN. Блог я собираюсь посветить многопоточности, многоядерности, алгоритмам синхронизации и всему, что с этим связано. Разработка масштабируемых lock-free, wait-free, obstruction-free, atomic-free и всех других что-то-там-free алгоритмов [...]]]></description>
			<content:encoded><![CDATA[<p>Приветствую!<br />
Меня зовут Дмитрий Вьюков. Добро пожаловать в мой блог. Дабы сразу устранить возможные недоразумения - я не являюсь сотрудником Intel, просто, так сказать, удостоился чести вести блог на ISN.</p>
<p>Блог я собираюсь посветить многопоточности, многоядерности, алгоритмам синхронизации и всему, что с этим связано. Разработка масштабируемых lock-free, wait-free, obstruction-free, atomic-free и всех других что-то-там-free алгоритмов синхронизации - это моё хобби. Некоторые из моих разработок Вы можете видеть <a href="http://groups.google.com/group/lock-free">здесь</a>. Так же я разработал и развиваю утилиту под названием <a href="http://groups.google.com/group/relacy">Relacy Race Detector</a>, которая может быть полезна разработчикам алгоритмов синхронизации, и в некоторой степени так же и прикладным разработчикам.</p>
<p>Интересно было бы услышать, есть ли  у кого-то интерес к такой тематике.</p>
<p>Stay tuned!</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2008/09/23/hello-isn/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

