<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated on Tue, 24 Nov 2009 22:56:27 -0800 -->
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <atom:link href="http://software.intel.com/ru-ru/articles/feed/" rel="self" type="application/rss+xml" />
    <title>Intel Software Network articles фид</title>
    <link>http://software.intel.com/ru-ru/articles//all</link>
    <description></description>
    <language>ru-ru</language>
    <item>
      <title>Miser – динамически загружаемый аллокатор памяти для многопоточных приложений</title>
      <description><![CDATA[ <p><em>Данный текст является продолжением статьи <a href="http://software.intel.com/ru-ru/articles/multicore-storage-allocation/">Параллельные механизмы выделения памяти</a>.</em></p>
<p>При работе с одним из ранних прототипов Cilk++ довольно быстро стало понятно, что механизм выделения памяти, предоставляемый по умолчанию ран-тайм библиотекой С для Windows, становится узким местом многопоточных приложений. Аллокатор памяти для Windows имеет одну единственную блокировку, которая используется для обеспечения последовательного доступа к его внутренним структурам. Это вполне безопасно, но приводит к серьезным потерям в параллелизме.</p>
<p>Существует множество механизмов выделения памяти, как бесплатных, так и коммерческих. Мы исследовали некоторые из них и обнаружили, что ни один не обладает всеми качествами, которые мы искали. В итоге мы решили написать свой собственный. Поскольку конечный продукт был приложением для Windows, то мы сначала занялись реализацией для Windows.</p>
<h2>Функции, заимствованные от Hoard</h2>
<p>Мы решили создать новый аллокатор на основе принципов, заложенных в <a target="_blank" href="http://www.cs.umass.edu/%7Eemery/hoard/asplos2000.pdf">Hoard: A Scalable Memory Allocator for Multithreaded Applications</a>  Эмери Бергера (Emery D. Berger), Кэтрин МакКинли (Kathryn S. McKinley), Роберта Блюмоф (Robert D. Blumofe) и Пола Вилсона (Paul R. Wilson). Hoard был разработан с целью минимизировать блокировки, соревнование, false sharing (ложное общее использование) и дрейф памяти. Детальное описание Hoard можно найти на сайте <a target="_blank" href="http://www.hoard.org/">Hoard</a>.</p>
<p>Мы назвали наш новый отводчик памяти «Miser», чтобы сыграть на его отношении к Hoard (игра слов, Hoard означает «запас», Miser означает «жадина», <em>прим. пер.</em>). Мы создали реализации Miser как для Linux, так и для Windows. В данной заметке описываются общие структуры данных в обеих версиях, некоторые особенности реализации для Windows и, в конце, несколько замечаний, относящихся к Linux.</p>
<h3 class="sectionHeading">Суперблоки</h3>
<p>Основной единицей распределения памяти в Miser является суперблок (superblock). Суперблок содержит заголовок, массив для хранения размеров каждого затребованного блока памяти и массив равных по размеру блоков памяти, или корзин (bins), которые и используются при выделении памяти. Корзины имеют размер степени двойки, от 8 до 256 байт. Исследования показали, что такой размер удовлетворяет более чем 98% всех случаев. Выделение блоков большего размера перепоручается стандартным механизмам языка С. Массив корзин располагается в конце суперблока, поэтому блоки памяти, возвращаемые пользователю, являются естественно выровненными.</p>
<h3 class="sectionHeading">Глобальный пул памяти и пулы потоков</h3>
<p>Miser, как и Hoard, создает собственный пул памяти для каждого потока. Поскольку пул является собственностью потока, к нему можно обращаться без блокировки. Пул состоит из суперблоков, которые грубо отсортированы по размеру оставшегося в них свободного места. Использование локальных потоковых пулов памяти является фундаментальным принципом оптимизации производительности приложений:</p>
<ul type="disc">
<li>Пул памяти потока позволяет Miser выполнять бОльшую часть запросов по выделению памяти без блокировки.</li>
<li>Пул памяти потока минимизирует false sharing. Пока блоки не передаются между потоками, все обращения к памяти на одной линии кэша исходят от одного и того же потока.</li>
</ul>
<p>Глобальный пул не принадлежит потокам. Если локальный пул потока не может удовлетворить запрос на память, то он берет еще один суперблок из глобального пула. Если глобальный пул пуст, Miser берет дополнительную память у операционной системы. По мере освобождения памяти в локальном пуле суперблоки переходят обратно в глобальный пул, чтобы предотвратить дрейф памяти.</p>
<h2>Дополнительные функции</h2>
<p>Хотя функциональность Hoard является хорошей базой, в Miser мы добавили следующие требования:</p>
<ul type="disc">
<li>Он должен иметь возможность загружаться динамически. Это позволяет использовать компоненты Miser в виде общей (shared) библиотеки, не требуя от приложения загружать библиотеку при старте.</li>
<li>Он должен уметь узнавать блоки, выделенные не им, и передавать их системной библиотеке С.</li>
<li>Он должен быть потокобезопасным и быстрым.</li>
<li>Он должен уметь одновременно поддерживать разные версии рантайм библиотеки С, которые использует приложение.</li>
</ul>
<h3 class="sectionHeading">Динамическая загрузка Miser</h3>
<p>Формат исполняемого файла или динамической библиотеки (DLL) Windows определяется форматом исполняемых файлов Windows Portable Executable File Format. Почти все модули Windows импортируют функции из других модулей. Эти зависимости описываются парой таблиц: Import Name Table и Import Address Table. Когда модуль загружается в память, загрузчик проставляет адреса входных точек модуля в Import Address Table. Все вызовы функций из модуля косвенно осуществляются через эту таблицу.</p>
<p>Когда Miser загружается приложением, он пробегает все модули приложения и перенаправляет указатели в  Import Address Table для каждой функции выделения памяти из библиотеки С на свои собственные. Эта технология была впервые описана Мэттом Пьетреком (Matt Pietrek) в статье <a href="http://msdn.microsoft.com/en-us/library/ms809762.aspx">Peering Inside the PE: A Tour of the Win32 Portable Executable File Format</a>. Поскольку изменение пункта таблицы IAT представляет собой  запись 32 бит, операция атомарна; пункт таблицы может являться адресом либо динамической библиотеки С, либо Miser.</p>
<p>После того, как Miser загружается в процесс, его уже нельзя выгрузить, потому что хотя Miser и может восстанавливать изначальные указатели функций библиотеки С в IAT, сама библиотека С не может управлять памятью, отведенной в Miser.</p>
<h3 class="sectionHeading">Распознавание блоков, выделенных отличными от Miser инструментами</h3>
<p>Когда Miser загружается, некоторый объем памяти может быть уже выделен. Miser не может заменить эти блоки памяти блоками из своих ресурсов. Поскольку Miser переводит все вызовы free() and _msize() на себя, то он должен уметь опознавать блоки, распределенные библиотекой С, и перенаправить соответствующие вызовы нужной версии библиотеки С.</p>
<p>ОС Windows предоставляет несколько пользовательских API для управления памятью. Одним из самых фундаментальных является функция VirtualAlloc(), которая отводит память 64К блоками по границам 64К. Miser разбивает их на суперблоки по 4Кбайта, выровненных по границе 4К. Это не случайно: таков размер страницы памяти в 32-разрядной Windows. В начале суперблока содержится заголовок, включающий «магическое число» и другую контрольную информацию. Т.о. маскируя нижние 12 битов адреса памяти мы получаем заголовок суперблока, содержащего данный блок памяти. Существует два метода проверки суперблока:</p>
<ul>
<li>Заголовок суперблока содержит правильное «магическое число».</li>
<li>Заголовок суперблока содержит указатель на локальный пул памяти потока, который владеет этим суперблоком, и индекс массива указателей на суперблоки, принадлежащие локальному пулу. Элемент массива должен соответствовать адресу суперблока. </li>
</ul>
<p>Если хотя бы один из этих тестов не проходит, Miser передает вызов библиотеке Windows С.</p>
<p>Дополнительным плюсом «распознавания» блоков памяти, выделенных библиотекой С, является возможность Miser передавать стандартным средствам все запросы на блоки более 256 байт.</p>
<h3 class="sectionHeading">Поддержка разных версий библиотеки С</h3>
<p>Каждый модуль, загруженный приложением, может требовать ту или иную версию библиотеки С. В дополнение к взаимной поддержке, встроенной в ОС, последние версии библиотеки С включают номер версии в имени DLL. Miser поддерживает все версии библиотеки С начиная с Visual Studio .NET 2002. Поскольку каждая версия библиотеки С имеет свою собственную кучу (heap), Miser запоминает, за какую версию библиотеки С он зацепился и перенаправляет запросы соответственно.</p>
<h3 class="sectionHeading">Скорость и потокобезопасность</h3>
<p>Потокобезопасность и скорость работы очень тесно связаны. Чем больше блокировок использует поток, тем больше вероятность, что ему придется дожидаться своей очереди. Для большинства операций Miser не использует блокировок – как правило, для удовлетворения запроса хватает ресурсов локального пула. Блокировку приходится применять в следующих случаях:</p>
<ol type="1">
<li><strong>Доступ к глобальному пулу</strong> – только один поток может обращаться к глобальному пулу за раз. Любой поток, получающий доступ к глобальному пулу, сначала должен установить на него блокировку. Фактически, доступ к глобальному пулу является последовательным.</li>
<li><strong>Освобождение блока, распределенного из другого локального пула</strong> – в отличие от Hoard, Miser не блокирует заголовок суперблока. Блоки, освобождаемые в другом локальном пуле, размещаются в очереди на освобождение этого локального пула с помощью интерблокированных инструкций (interlocked instructions). Удаленная очередь на освобождение очищается до того, как будет отведен новый блок, в надежде, что освобожденный блок будет использован новым запросом.</li>
<li><strong>Поддержка _msize() для блоков, отведенных другим потоком</strong> – Функция _msize() позволяет приложениям Windows запрашивать у библиотеки С размер блока памяти. Возвращаемое значение представляет собой размер блока в байтах, на момент выделения памяти. Если блок был выделен с помощью Miser в текущем потоке, то мы можем пользоваться информацией суперблока, принадлежащего локальному пулу памяти. Однако, если суперблок принадлежит локальному пулу другого потока, то нужно заблокировать локальный пул перед попыткой проверки суперблока через массив суперблоков.</li>
<li><strong>Поддержка списка суперблоков</strong> – Каждый локальный пул потока содержит массив суперблоков, которые он распределил. Заголовок каждого суперблока содержит указатель на локальный пул, которому этот суперблок принадлежит, а также индекс из массива суперблоков пула. Эти данные используются для проверки того, что адрес памяти принадлежит блоку, который распределил Miser. Так как другой поток может потребовать доступ к массиву суперблоков, чтобы выполнить свой вызов _msize(), перед обновлением массив суперблоков необходимо заблокировать.</li>
</ol>
<p>Необходимо отметить, что хотя Miser и поддерживает Cilk++, он ни коим образом не привязан к компилятору или динамическим библиотекам Cilk++. Так же хорошо он будет работать в любом многопоточном приложении, использующем любую параллельную технологию.</p>
<h2>Ограничения Miser в Windows</h2>
<ul type="disc">
<li>Miser не поддерживает функции библиотеки С, которые выделяют память с выравниванием по границе адреса.</li>
<li>Miser подразумевает, что программа работает правильно. Он не оставляет никаких полей между блоками в попытке обнаружить запись вне границ отведенной памяти. Отведенные блоки располагаются вплотную друг к другу. Miser содержит размеры отведенных блоков в отдельной структуре, чтобы сохранить естественно выровненное расположение массивов корзин.</li>
<li>Из-за зависимости от таблицы Import Address Table, Miser можно использовать только с DLL модулями библиотеки С. Модули, скомпилированные статически,  не проходят через IAT, и Miser не может их подхватить. </li>
</ul>
<h2>Производительность</h2>
<p>Ниже приведен пример выигрыша в производительности, которого можно достичь с помощью Miser. Пример взят из статьи <a href="http://software.intel.com/en-us/articles/multicore-enabling-the-n-queens-problem-using-cilk">Multicore-enabling the N-Queens Problem Using Cilk++</a>.</p>
<p><img height="342" width="510" src="http://software.intel.com/file/23125" alt="Выигрыш в производительности с помощью Miser" border="0" /></p>
<h2>Miser в Linux</h2>
<p>Хотя Miser изначально писался для Windows, после того, как в некоторых тестах он показал более высокую производительность, чем Hoard, его решено было портировать на Linux. Внутренний код остался практически без изменений, за исключением некоторых системных вызовов (например, пришлось заменить VirtualAlloc() на mmap() и т.д.).</p>
<p>Что касается интерфейса, то механизм доступа к функциям был переписан. Библиотека Miser экспортирует malloc(), free(), calloc() и т.д., и подключение какого-либо бинарного файла заставляет загрузчик искать их. К тому же, предварительная загрузка с помощью установки LD_LIBRARY_PATH заставляет объекты находить в первую очередь аллокаторы Miser, и лишь затем библиотеки С. Miser использует системную функцию  malloc() при обработке больших запросов, и она находит их в текущей библиотеке с помощью dlsym().</p>
<p>Miser не подменяет собой стандартные средства выделения памяти в Linux, если он загружается после того, как модуль обнаружил функции работы с памятью. Однако, возможно использовать его функции malloc() с помощью dlsym() и получать «malloc» из библиотеки, но, как и в Windows, память, отведенная с помощью Miser, должна освобождаться также с помощью Miser. Системная функция free() не будет знать, что делать с памятью, распределенной Miser.</p>
<h2>Дополнительные ресурсы по Cilk++</h2>
<ul>
<li><a href="http://software.intel.com/en-us/articles/intel-cilk/">Скачать Intel® Cilk++ SDK</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/multicore-storage-allocation/">Параллельные механизмы выделения памяти</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/visualizing-parallel-speedup-with-cilkview/">Визуализация «параллельного ускорения» с помощью Cilkview</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/four-reasons-why-parallel-programs-should-have-serial-semantics/">Четыре причины, почему параллельные программы должны иметь последовательную семантику</a></li>
</ul>
<hr height="1" />
<p>Данная статья является переводом блога Бэрри Тэнненбаума (Barry Tannenbaum).<br />Оригинал доступен по адресу <a href="http://www.cilk.com/multicore-blog/bid/7812/Miser-A-Dynamically-Loadable-Memory-Allocator-for-Multi-Threaded-Applications">http://www.cilk.com/multicore-blog/bid/7812/Miser-A-Dynamically-Loadable-Memory-Allocator-for-Multi-Threaded-Applications</a>.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications</link>
      <pubDate>Tue, 24 Nov 2009 03:11:19 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Визуализация «параллельного ускорения»  с помощью Cilkview</title>
      <description><![CDATA[ <p>В комплекте Cilk++ v.1.1  идет новое средство, которое помогает визуализировать производительность  приложений – Cilkview. Cilkview запускает файл приложения и собирает данные о производительности  заданных участков кода. Затем эти данные комбинируются с оценочными значениями работа/время  (work/span), и на основании  полученных результатов строится график.</p>
<p>Рассмотрим следующий параллельный алгоритм расчета чисел  Фибоначчи:</p>
<pre name="code" class="cpp">int fib (int n) {
  int a, b;
  if (n &lt; 2) return n;
  a = cilk_spawn fib(n - 1);
  b = fib(n - 2);
  cilk_sync;
  return a + b;
}
  </pre>
<p><a href="http://en.wikipedia.org/wiki/Directed_acyclic_graph">Направленный ациклический граф (НАГ)</a>, представляющий дерево вызовов fib(35), <a href="http://www.cilk.com/multicore-blog/bid/5365/What-the-is-Parallelism-Anyhow">является двоичным деревом с множеством параллельных  узлов</a>, что является знаком широкого параллелизма. По функциям видно, что  они сильно загружают процессор, а обращения к памяти ограничиваются страницей  и имеют наивысшую локальность. Конечно, это ужасный способ расчета чисел  Фибоначчи, но он хорошо подходит в качестве простого примера широкого класса  алгоритмов, которые легко распараллеливаются. Чтобы позволить Cilkview составить прогноз  об ускорении, необходимо в области вызова функции вставить следующий код:</p>
<pre name="code" class="cpp">  #include &lt;cilkview.h&gt;
  int cilk_main () {
    cilk::cilkview cv;
    cv.start();
    fib(35);
    cv.stop();
    cv.dump("fib-35");
    return 0;
  </pre>
<p>start() и stop() показывают Cilkview интересующие нас  участки кода. dump() ассоциирует собранные данные с меткой, которую будет использовать Cilkview при постройке  графа. Теперь нужно скомпилировать программу и запустить Cilkview для постройки  графа:</p>
<p>% cilkview ./fib</p>
<p><img border="0" width="416" height="468" src="http://software.intel.com/file/23344" /></p>
<p>Cilkview  исполняет программу на одном ядре, рассчитывая объем работы и потраченное время  (<a href="http://software.intel.com/en-us/articles/what-the-is-parallelism-anyhow-1">work and span</a>) в обозначенном  участке кода. Чем круче наклон графика, тем более линейно ускорение алгоритма,  вплоть до линии «теоретического предела» (Application Parallelism).  Теоретическое ограничение в  данном случае не присутствует на графике, потому что в нашем примере оно  слишком велико.</p>
<p>Cilkview сделал  такой же прогноз в отношении данного алгоритма, что и мы. Более пологая линия соответствует  реалистичному прогнозу производительности, - ускорение чуть меньше, и связано  это с оверхедом Cilk++. Оба графика очень близки (почти неразличимы), следовательно Cilkview прогнозирует  суперлинейное масштабирование алгоритма.</p>
<p>Чтобы проверить это, Cilkview может выполнить проверочные запуски. Для  этого при запуске в командной строке необходимо указать опцию "-trials all". Никакого дополнительного кода не  требуется:</p>
<p>% cilkview -trials all ./fib</p>
<p><img border="0" width="415" height="466" src="http://software.intel.com/file/23345" /></p>
<p>Красными звездочками обозначены данные испытаний. По умолчанию Cilkview по одному разу  запускает программу с количеством работников (workers) Cilk от 1 до <em>n, </em>где <em>n</em> – количество ядер.  Приведенный график был получен на компьютере с 8 ядрами. Как и было предсказано,  тестовые запуски масштабировались почти линейно. Некоторые вариации можно  отнести за счет того, что с каждой конфигурацией было проведено только по  одному тесту.</p>
<p>А что по поводу более распространенных алгоритмов, например,  сортировки? Ниже приведен простой параллельный алгоритм quicksort:</p>
<pre name="code" class="cpp">void qsort (int *begin, int *end) {
  if (begin == end) return;
  --end; // end was one past the end of the range.
  int *middle = std::partition(begin, end, std::bind2nd(std::less(), *end));
  using std::swap;
  swap(*end, *middle);
  cilk_spawn qsort(begin, middle);
  qsort(++middle, ++end);
}
</pre>
<p>На первый взгляд кажется, что этот алгоритм также можно сильно распараллелить.  Даже несмотря на то, что здесь много обращений к памяти, алгоритм quicksort хорошо работает  с кэшем. Но нужно помнить, что в НАГ присутствует немалый последовательный узел  в корне первой partition(). Таким образом, после рекурсивного вызова, каждый из дочерних  процессов должен будет исполнять partition() на своем участке массива до того, как можно будет что-то делать  параллельно. На самом деле бОльшая часть работы в quicksort делается в partition(), то есть последовательно.  Анализ Cilkview наглядно это показывает:</p>
<p><img border="0" width="416" height="466" src="http://software.intel.com/file/23343" /></p>
<p>На графике показана работа qsort над массивом из 10.000.000 целых чисел. Видно, что уровень идеального  параллелизма (горизонтальная линия) достаточно низок и вместился в график. Результаты  тестирования дают похожую картину:</p>
<p><img border="0" width="417" height="467" src="http://web.cilk.com/blog/win-cilk/qsort.png" /></p>
<p>Теперь рассмотрим следующий код параллельной процедуры memcpy:</p>
<pre name="code" class="cpp">void *parallel_memcpy (void *dst, void *src, size_t length) {
	size_t *idst = (size_t*)dst;
    size_t *isrc = (size_t*)src;
    
    size_t ilen = length / sizeof(size_t);
    size_t rem = ilen * sizeof(size_t);
    
    cilk_for (size_t i = 0; i &lt; ilen; ++i){
    	idst[i] = isrc[i];
    }
    
    char *cdst = (char*)dst;
    char *csrc = (char*)src;
    
    for (size_t i = rem; i &lt; length; ++i;) {
    	cdst[i] = csrc[i];
    }
    
    return dst;    
}
</pre>
<p>Очевидно, что здесь возможен очень «широкий» параллелизм. Вся работа  выполняется в цикле cilk_for,  автоматический выбор зерна должен сократить издержки скрытых cilk_spawn по отношению к  объему работы, выполненному на каждой итерации. Концептуально в плане  параллелизма memcpy() похожа на fib(). НАГ похож на граф fib() за исключением того, что здесь дерево сбалансировано. К сожалению,  предсказание, основанное на данных work/span,  абсолютно расходятся с данными тестов (parallel_memcpy() запускалась на массиве из 400 миллионов  байт):</p>
<p><img border="0" width="417" height="466" src="http://software.intel.com/file/23348" /></p>
<p>Почему же результаты тестов разошлись с прогнозами? Cilkview принимает в  расчет данные о работе и затраченном времени и оценивает издержки окружения. Но  здесь ограничительным фактором был не объем параллелизма. Расхождения  объясняются другими факторами, и в данном случае это работа с памятью. Когда  процессоры начали выстраиваться  в  очередь для обращения к памяти, скорость работы сразу снизилась. Компьютер, на  котором запускалась программа, имеет два контроллера памяти, а на обычном  компьютере картина масштабирования была бы еще печальней. Предсказание  параллелизма Cilkview полезно для понимания того, имеет ли ваш алгоритм достаточно  параллелизма, чтобы эффективно масштабироваться. График, на котором результаты  тестов оказываются ниже нижней границы предсказаний, указывает, что алгоритм  сталкивается с ограничениями другой природы.</p>
<p>Последний, более удачный пример, - это <a href="http://www.cilk.com/multicore-blog/bid/6248/A-Tale-of-Two-Algorithms-Multithreading-Matrix-Multiplication">параллельный алгоритм  умножения матриц</a>, разработанный Маттео Фриго и Мэфью Стиле (Matteo Frigo, Matthew  Steele). Алгоритм  задумывался как альтернатива другому, более интуитивному параллельному  алгоритму умножения матриц, но который не очень эффективно использует кэш.</p>
<p><img border="0" width="416" height="467" src="http://software.intel.com/file/23349" /></p>
<p>Конечно, алгоритм на самом деле не дает ускорения, превосходящего линейное, как  это видно на графике в случае с 8 работниками. Это результат девиаций, ведь на  каждом уровне запускается только один тест.</p>
<p>Cilkview идет в  одном пакете вместе с Cilk++ v.1.1  как для Windows, так и для Linux. Данное средство включает и другие функции (например, можно инструментировать   разные участки кода, создавать файлы .csv, устанавливать  количество ядер, для прогноза, и т.д.). Мы надеемся, что Cilkview поможет  пользователям подтвердить или опровергнуть их интуитивные ожидания о  возможностях параллельных алгоритмов. К тому же, он рисует прекрасные  иллюстрации.</p>
<h2>Дополнительные ресурсы по Cilk++</h2>
<ul>
<li><a href="http://software.intel.com/en-us/articles/intel-cilk/">Скачать Intel® Cilk++ SDK</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/multicore-storage-allocation/">Параллельные механизмы выделения памяти</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications/">Miser – динамически загружаемый аллокатор памяти для многопоточных приложений</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/four-reasons-why-parallel-programs-should-have-serial-semantics/">Четыре причины, почему параллельные программы должны иметь последовательную семантику</a></li>
</ul>
<hr height="1" />
<p>Данная статья является переводом блога Вилла Лейзерсона (Will Leiserson).<br />Оригинал доступен по адресу <a href="http://www.cilk.com/multicore-blog/bid/9769/Visualizing-Parallel-Speedup-with-Cilkview">http://www.cilk.com/multicore-blog/bid/9769/Visualizing-Parallel-Speedup-with-Cilkview</a>.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/visualizing-parallel-speedup-with-cilkview</link>
      <pubDate>Tue, 24 Nov 2009 02:58:59 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/visualizing-parallel-speedup-with-cilkview#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/visualizing-parallel-speedup-with-cilkview</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Четыре причины, почему параллельные программы должны иметь  последовательную семантику</title>
      <description><![CDATA[ <p>Некоторые среды параллельного программирования требуют от разработчика заново переосмыслить фундаментальные основы программирования, чтобы научиться «думать» параллельно. Cilk++ использует другой подход. Один из основных принципов Cilk++ это то, что программы на Cilk++ имеют <em>последовательную семантику</em>, то есть программа может пониматься (и выполняться) как последовательная программа. Ключевые слова Cilk++ были разработаны таким образом, чтобы заставить эту <em>последовательность</em> выглядеть подобно параллельной Cilk++ программе.</p>
<p>Этот простой, но мощный принцип влияет не только на внешний вид программ на Cilk++. Он также облегчает процесс разработки и тестирования программ на Cilk++ и позволяет Cilk Arts создавать мощные, эффективные и доказательно корректные инструменты.</p>
<p>Последовательную семантику (Serial semantics) не надо путать с последовательной консистентностью (sequential consistency), которая является концепцией, изобретённой Лесли Лампором (Leslie Lamport), и которая широко обсуждалась в литературе по параллельным вычислениям. Параллельная вычислительная система с последовательно консистентной памятью гарантирует, по словам Лампора, «что результат любого выполнения будет таким же, как если бы операции всех процессоров выполнялись в некотором последовательном порядке и операции каждого индивидуального процессора появляются в этой последовательности в порядке, определяемом программой».</p>
<p>В противоположность этому, последовательная семантика означает, что программа может выполняться на одном потоке как простая последовательная программа, – чтобы понимать, или выполнять программу, не нужна концептуальная параллельная модель.</p>
<h2>Последовательная семантика Cilk++</h2>
<p>Для начала посмотрим, как в Cilk++ реализована последовательная семантика.</p>
<p>В Cilk++ присутствуют три ключевых слова: cilk_spawn, cilk_for, и cilk_sync. Каждое из них имеет интуитивно понятную последовательную интерпретацию.</p>
<table cellpadding="5" cellspacing="0" class="tableformat1">
<tbody>
<tr>
<th width="159" valign="top">
<p>Ключевое слово</p>
</th><th width="300" valign="top">
<p>Параллельное значение</p>
</th><th width="339" valign="top">
<p>Последовательное значение</p>
</th>
</tr>
<tr>
<td width="159" valign="top">
<p>cilk_spawn</p>
</td>
<td width="300" valign="top">
<p>Позволяет вызываемой функции работать параллельно с вызывающей функцией</p>
</td>
<td width="339" valign="top">
<p>Обычный вызов функции, когда вызывающая программа ждёт, пока функция вернёт результат</p>
</td>
</tr>
<tr>
<td width="159" valign="top">
<p>cilk_sync</p>
</td>
<td width="300" valign="top">
<p>Ожидание завершения параллельной работы</p>
</td>
<td width="339" valign="top">
<p>Ничего не делает (нет никакой параллельной активности)</p>
</td>
</tr>
<tr>
<td width="159" valign="top">
<p>cilk_for</p>
</td>
<td width="300" valign="top">
<p>Позволяет циклу выполнять итерации параллельно</p>
</td>
<td width="339" valign="top">
<p>Исполняет стандартный цикл for, в котором итерации выполняются по одной за раз</p>
</td>
</tr>
</tbody>
</table>
<p>Как видно, программа на Cilk++ может легко становиться последовательной: просто нужно поменять cilk_for  на for и удалить операторы cilk_spawn и cilk_sync. В системе разработки Cilk++ есть опция компилятора, позволяющая строить последовательную программу. При использовании Cilk++ для Linux для «отрубания» функций Cilk++ используется опция [-fcilk-stub]. При использовании Cilk++  для Windows запускайте  cilkpp с опцией [/cilkp cpp].</p>
<p>Вы можете сказать: «Ладно, твоя умная параллельная программа может стать последовательной. Ну и что? Если бы мне нужна была последовательная программа, то я бы просто использовал C++!». Хорошо, что вы подняли этот вопрос! Параллельная модель с последовательной семантикой имеет четыре больших преимущества перед программными моделями без последовательной семантики.</p>
<ol>
<li><strong>Эквивалентная последовательная программа на C++ может легко отлаживаться и анализироваться с помощью существующих средств разработки</strong>. Это идеальный способ отладки параллельной программы, без необходимости беспокоиться о параллельном взаимодействии, и без необходимости учитывать множество стеков и счётчиков программ. Так же, как и в старом добром C++, все символы и отладочная информация доступны для стандартных системных и сторонних средств. Ведь средства, которые легко применить к последовательным программам, могут потребовать специальных версий для работы с параллельными программами. К таким средствам относятся профайлеры производительности, средства анализа кода и безопасности, контроля утечки памяти и любые другие, которые могут работать с программами на C++.</li>
<li><strong>Последовательная семантика программ на Cilk++ позволяет Cilk Arts создавать лучшие средства анализа производительности и корректности программ Cilk++. Эти средства дают твёрдую гарантию правильной работы программы. </strong>Например, наш детектор состояния гонки Cilkscreen прогоняет тестовую программу в последовательном режиме на одном процессоре. Используя информацию о параллельной структуре программы (то есть о местах, в которых происходит распараллеливание и синхронизация), Cilkscreen имеет возможность анализировать все возможные режимы работы программы, при любом количестве процессоров. Другими словами, Cilkscreen замечает потенциальное состояние гонки в любом из всех возможных режимов работы программы. Последовательная семантика Cilk++ позволяет Cilkscreen анализировать параллельную программу, когда она работает на одном «работнике» (worker, так в Cilk++ называется выполняющий поток) на одном процессоре.</li>
<li><strong>Последовательная семантика Cilk++ даёт реальное преимущество при тестировании и анализе качества программ. </strong>Cilk++ разрабатывался с таким расчётом, чтобы параллельные программы, написанные на нём, могли быть детерминированными и надёжно воспроизводили результаты, аналогичные последовательной версии. Эта гарантия позволяет использовать традиционные средства тестирования, которые предполагают, что при наличии одних и тех же входных данных во множественных запусках программа всегда возвращает один и тот же результат. Последовательная семантика Cilk++ устраняет необходимость учитывать нюансы тайминга и планирования при оценке корректности программ и позволяет легко проводить сравнение вывода программы при множественных запусках. Повторяемость – одно из основных свойств, которое делает программы на Cilk++ надежными и легкими в тестировании.
<div></div>
<p> </p>
</li>
<li><strong>Последовательная семантика обеспечивает высокую производительность, одновременно позволяя разрабатывать программу без необходимости указывать количество процессоров, на которых она будет выполняться. </strong>Планировщик Cilk++ заботится об эффективном распределении нагрузки в программе, независимо от количества доступных процессоров. Когда часть параллельных вычислений выполняется на одном процессоре, Cilk++ может выполнять их как обычных код C++, полностью используя все опции оптимизации компилятора, которые предлагает C++. Показывая хорошую производительность на одном ядре, Cilk++ обеспечивает программе с достаточным параллелизмом хорошее ускорение, как на большом количестве процессоров, так и не очень. </li>
</ol>
<h2>В чём соль?</h2>
<p>Возможно, всё это звучит слишком хорошо, чтобы быть правдой, и, возможно, теперь вы подумаете: «Ну ладно, а в чём соль?». Вот несколько вещей, которые я не упомянул. Во-первых, поскольку имеются некоторые виды параллелизма, которые не имеют последовательной семантики, на Cilk++ нельзя написать программу, использующую такие стратегии параллелизации. Например, Cilk++ не поддерживает моделей производитель/потребитель, программный пайплайнинг или передачу сообщений. Тем не менее, хотя мы и вынуждены кое от чего отказываться ради последовательной семантики, опыт показывает, что лучше меньше, да лучше. Четыре преимущества, описанных выше, вполне восполняют наши потери.</p>
<p>Есть ещё один момент. В третьем пункте я сказал, что программы на Cilk++ могут быть детерминированными, а не что они такими являются. Почему? Результат работы программы с гонкой (смотреть «<a href="http://software.intel.com/en-us/articles/are-determinacy-race-bugs-lurking-in-your-multicore-application">Are Determinacy Races Lurking in Your Multicore Application</a>") зависят от того, как программа «разложилась» на несколько процессоров. Состояние гонки, приводящее к такому недетерминированному поведению, обычно является результатом ошибки. В большинстве случаев вы обычно застреваете в этом месте, пытаясь обнаружить ошибку гонки, тщательно проверяя код. Преимущество Cilk++ в том, что существует инструмент, который  помогает находить и устранять гонки – Cilkscreen.  Cilkscreen возвращает программе детерминированность, и делает это весбма эффективно именно благодаря последовательной семантике. «Соль» с том, что Cilkscreen использует последовательный режим как тест правильности параллельного выполнения.</p>
<p>Иногда программы на Cilk++ намеренно недетерминированы. Например, некоторые поисковые программы используют таблицы хэшей для запоминания результатов промежуточных поисков, чтобы избежать повторного выполнения. В таком контексте хэш-таблица может потребовать использования мьютекса (блокировка взаимного исключения) для безопасного чтения и обновления строк параллельными ветвями вычислений. Cilkscreen гарантирует обнаружение потенциальных гонок в операциях, защищённых блокировкой.</p>
<p>На практике мы обнаружили, что большинство программ на Cilk++ требуют очень мало блокировок. В частности, параллельные конструкции  Cilk++ устраняют необходимость в блокировках, которые требуют другие параллельные модели для коммуникаций между потоками и синхронизации. Более того, во многих обычных ситуациях, в которых блокировка кажется необходимой для устранения гонки, Cilk++ предлагает использовать <span style="text-decoration: underline;">гиперобъекты</span> – новые структуры данных, которые могут разрешать состояние гонки на глобальных переменных, не жертвуя при этом производительностью или детерминизмом. Избегая блокировок и используя гиперобъекты, Cilk++ обходит стороной многие тонкие места производительности, вызываемые блокировками, такие как соревнование блокировок (lock contention), которое может существенно замедлить выполнение параллельных программ, или дедлоки, которые могут подвесить программу посередине работы.</p>
<h2>Последнее слово</h2>
<p>Подводя итоги, можно сказать, что последовательная семантика Cilk++ обеспечивает три ключевых преимущества: производительность, надёжность и продуктивность.</p>
<ul>
<li>Масштабируемая производительность, обеспеченная моделью проектирования,  которая не подразумевает предварительного  знания программистом количества имеющихся процессоров. Разработчик может использовать традиционные последовательные средства настройки производительности для нахождения горячих мест и повышения как последовательной, так и параллельной производительности.</li>
<li>Надёжность, основанная на принципах повторяемости результатов и  детерминизма и возможности построить эффективный и гарантированно правильный детектор состояний гонки, базирующийся на однозначном соответствии параллельной и последовательной семантик.</li>
<li>Наконец, программисты могут работать более продуктивно, используя знакомые им парадигмы в новых разработках, и, естественно, старый последовательный код может быть легко преобразован в Cilk++: его не надо переписывать, как в случае с концептуально отличными программными моделями, такими,  как модель параллельных данных или с модель обмена сообщениями.</li>
</ul>
<h2>Дополнительные ресурсы по Cilk++</h2>
<ul>
<li><a href="http://software.intel.com/en-us/articles/intel-cilk/">Скачать Intel® Cilk++ SDK</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/multicore-storage-allocation/">Параллельные механизмы выделения памяти</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications/">Miser – динамически загружаемый аллокатор памяти для многопоточных приложений</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/visualizing-parallel-speedup-with-cilkview/">Визуализация «параллельного ускорения» с помощью Cilkview</a></li>
</ul>
<hr height="1" />
<p>Данная статья является переводом блога Steve Lewin-Berlin. <br />Оригинал доступен по адресу <a href="http://www.cilk.com/multicore-blog/">http://www.cilk.com/multicore-blog/</a>.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/four-reasons-why-parallel-programs-should-have-serial-semantics</link>
      <pubDate>Tue, 24 Nov 2009 02:53:20 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/four-reasons-why-parallel-programs-should-have-serial-semantics#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/four-reasons-why-parallel-programs-should-have-serial-semantics</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Параллельные механизмы выделения памяти</title>
      <description><![CDATA[ <p>При распараллеливании приложений на С/С++ часто оказывается, что функция malloc() (или new) является узким местом, ограничивающим максимальную производительность приложения. В данной статье объясняются четыре основные проблемы, которые может решить хороший параллельный механизм выделения памяти:</p>
<ol>
<li><strong>Потокобезопасность</strong></li>
<li><strong>Дополнительные издержки (</strong><strong>Overhead)</strong></li>
<li><strong>Соревновательность </strong></li>
<li><strong>Дрейф памяти </strong></li>
</ol>
<h2>Потокобезопасность</h2>
<p>Базовые механизмы выделения памяти (storage allocators) не являются потокобезопасными, хотя в результате недавних усилий разработчиков данная проблема решается для многих параллельных платформ. В двух словах, из-за гонок во внутренних структурах данных механизма выделения памяти возникает вероятность неправильного поведения программы, когда два потока одновременно пытаются занять или освободить память. Если потоки имеют неограниченный доступ к механизму распределения памяти, как показано ниже, то они начинают «наступать друг другу на пятки», что приводит к ненормальному поведению системы.</p>
<p class="center"><img height="227" width="287" src="http://software.intel.com/file/23158" /></p>
<p>Простым решением данной проблемы является применение мьютексной блокировки (mutex, mutual exclusion, взаимное исключение) к аллокатору перед вызовом malloc() или free(), что позволяет только одному потоку получить доступ к внутренним структурам данных механизма выделения памяти за раз.</p>
<p class="center"><img height="314" width="287" src="http://software.intel.com/file/23159" /></p>
<p>Если механизм выделения  памяти является потокобезопасным, блокирующий протокол встраивается непосредственно в его логику.</p>
<h2>Издержки и соревнование</h2>
<p>У потокобезопасных механизмов выделения памяти с использованием блокировок возможны две проблемы: во-первых, выделение и освобождение памяти могут замедляться вследствие дополнительных издержек на установку блокировки. Во-вторых, при доступе к аллокатору может возникнуть соревнование, что приводит к замедлению работы приложения и ограничению его масштабируемости. Соревнование само по себе  не является большой проблемой при 2 или 4 ядрах, но, по закону Мура, скоро можно ожидать десятков и даже сотен ядер в одном процессоре, и тогда «соревновательность» может сильно подкосить масштабируемость.</p>
<p>Обе проблемы можно решить с помощью распределенного механизма выделения памяти, который создает локальный пул памяти при каждом потоке, как это показано на рисунке.</p>
<p class="center"><img height="359" width="286" src="http://software.intel.com/file/23160" /></p>
<p>Распределенный аллокатор позволяет выделять и освобождать память в основном в пределах локальных пулов. В редких случаях, когда локальный пул истощается, поток может получить дополнительную память (большими кусками), из глобального пула. Таким образом, решается проблема соревновательности, поскольку потоки редко обращаются к глобальному пулу. Решается также и проблема издержек, потому что при обращении к локальным пулам блокировки не требуются.</p>
<h2>Дрейф памяти</h2>
<p>К сожалению, использование локальных потоков создает другую проблему, особенно на платформах, в которых память активно распределяется между потоками, или в которых происходит активное перераспределение вычислительной нагрузки между потоками. Представим, что поток А постоянно требует выделения  памяти в своем локальном пуле и затем передает полученную память потоку Б, который использует ее в своем локальном пуле. Когда пул потока А истощается, он запрашивает дополнительную память из глобального пула. Эта память снова передается потоку Б, и он опять же использует ее в своем локальном пуле. С течением времени локальный пул памяти потока Б неограниченно разрастается, создавая что-то вроде утечки памяти, при которой объем виртуальной памяти приложения все время растет.</p>
<p>Этот дрейф памяти можно разрешить двумя путями. Одно решение состоит в том, что поток, чей локальный пул становится слишком большим, отдает часть памяти глобальному пулу. Другое – все потоки, освобождая память, возвращают ее тому пулу, из которого она была отведена. Оба этих метода можно реализовать с низкими издержками, и они оба дают удовлетворительное решение проблемы дрейфа памяти.</p>
<h2>Выводы</h2>
<p>Существуют и другие проблемы, возникающие в параллельных механизмах выделения памяти. Например, ложное общее использование памяти <em>(</em><em>false </em><em>sharing) </em>является особенно разрушительным состоянием, при котором два потока обращаются к независимым блокам памяти, которые случайно оказываются на одной и той же линии кэша, что ведет к перегрузке протокола когерентности кэша в процессоре. Аллокатор памяти, не уделяющий должного внимания границам линий кэша и раздающий разным потокам блоки памяти, лежащие на одной линии, может вызывать ложное разделение, которое трудно отловить, поскольку логика кода говорит, что потоки обращаются к независимым блокам памяти.</p>
<p>Можно привести два примера параллельных отводчиков памяти – это <a target="_blank" href="http://www.cs.umass.edu/%7Eemery/hoard/">Hoard</a>, написанный Эмери Бергером (Emery Berger) из университета Массачусетса, и <a href="http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications/">Miser, </a>распространяемый <a href="http://www.cilk.com">Cilk Arts</a> как часть пакета <a href="http://www.cilk.com/multicore-products/cilk-solution-overview/">Cilk++</a>.</p>
<h2>Дополнительные ресурсы по Cilk++</h2>
<ul>
<li><a href="http://software.intel.com/en-us/articles/intel-cilk/">Скачать Intel® Cilk++ SDK</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/miser-a-dynamically-loadable-memory-allocator-for-multi-threaded-applications/">Miser – динамически загружаемый аллокатор памяти для многопоточных приложений</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/visualizing-parallel-speedup-with-cilkview/">Визуализация «параллельного ускорения» с помощью Cilkview</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/four-reasons-why-parallel-programs-should-have-serial-semantics/">Четыре причины, почему параллельные программы должны иметь последовательную семантику</a></li>
</ul>
<hr height="1" />
<p>Данная статья является переводом блога Чарльза Лизерсона (Charles Leiserson).<br />Оригинал доступен по адресу <a href="http://www.cilk.com/multicore-blog/bid/7904/Multicore-Storage-Allocation">http://www.cilk.com/multicore-blog/bid/7904/Multicore-Storage-Allocation</a>.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/multicore-storage-allocation</link>
      <pubDate>Tue, 24 Nov 2009 02:07:07 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/multicore-storage-allocation#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/multicore-storage-allocation</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Интервью с Анатолием Кузнецовым, автором библиотеки BitMagic C++ Library</title>
      <description><![CDATA[ <h2>Аннотация</h2>
<p>В этой статье Анатолий Кузнецов, старший инженер по разработке программного обеспечения в NCBI, отвечает на вопросы и рассказывает об открытой библиотеке BitMagic C++ Library.</p>
<h2>Введение</h2>
<p>Регулярно просматривая ресурсы интернета связанные с тематикой 64-битного программирования, я неоднократно встречал упоминание библиотеки BitMagic и то, что она получила существенные преимущества от использования 64-битности. Я решил связаться с автором библиотеки и предложить ему рассказать в интервью о своих исследованиях и разработках.</p>
<p><strong>Вопросы задает: Андрей Карпов</strong> - сотрудник компании "Системы программной верификации", занимающийся созданием инструмента <a href="http://www.viva64.com/ru/pvs-studio/">PVS-Studio</a> для проверки современных приложений на языке Си++.</p>
<p><strong>На вопросы отвечает: Анатолий Кузнецов</strong> - старший инженер по разработке программного обеспечения в NCBI. Является разработчиком открытой библиотеки <a href="http://bmagic.sourceforge.net/">BitMagic C++ Library</a>.</p>
<h3 class="sectionHeading">Анатолий, расскажите, пожалуйста, немного о себе. Какими проектами Вы занимаетесь?</h3>
<p>Я старший инженер по разработке программного обеспечения, в данный момент я работаю в группе поиска и визуализации биомолекулярной информации <a href="http://www.ncbi.nlm.nih.gov/">NCBI</a> (National Center for Biotechnology Information). Помимо своей основной деятельности я являюсь основным разработчиком и архитектором открытой библиотеки BitMagic C++ Library.</p>
<p>По образованию я инженер-экономист, выпускник Нижегородского Университета им. Лобачевского.</p>
<h3 class="sectionHeading">Что такое библиотека BitMagic?</h3>
<p>Библиотека BitMagic разрабатывалась как универсальная библиотека шаблонов для работы с компрессированными битовыми векторами. Библиотека решает несколько задач:</p>
<ul type="disk">
<li>Предоставляет битовый контейнер, по-настоящему совместимый по идеологии с STL. Это значит, что контейнер должен поддерживать итераторы, аллокаторы памяти, взаимодействовать с алгоритмами и другими STL контейнерами.</li>
<li>Библиотека умеет эффективно оперировать с очень длинными и разреженными векторами.</li>
<li>Предоставляется возможность сериализации векторов для последующей записи их в базы данных или пересылке по сети.</li>
<li>Разработчику предоставляется набор алгоритмов для реализации теоретико-множественных операций и подсчета расстояний и метрик подобия в многомерных двоичных пространствах.</li>
<li>Значительное внимание уделяется оптимизации под распространенные системы ускорения расчетов, такие как <a href="http://www.viva64.com/go.php?url=270">SSE</a>.</li>
</ul>
<h3 class="sectionHeading">При решении каких задач BitMagic представляет наибольший интерес для разработчиков?</h3>
<p>Библиотека получилась довольно универсальная, и перечислить все возможные применения, наверное, будет не просто. В данный момент библиотека наиболее интересна в следующих областях:</p>
<ul type="disk">
<li>Построение битовых и инвертированных индексов для полнотекстовых поисковых систем, ускорение операций реляционной алгебры (AND, OR, JOIN, и так далее).</li>
<li>Разработка нестандартных расширений и индексов для существующих СУБД (Oracle Cartridges, MS SQL extended stored procedures). Такие расширения, как правило, помогают интегрировать в СУБД научные, географические или какие-то другие нестандартные данные.</li>
<li>Разработка алгоритмов анализа данных (data mining). </li>
<li>Разработка бездисковых индексов и баз данных (in-memory databases).</li>
<li>Разработка систем точного разграничения доступа с большим количеством объектов (базы данных повышенной секретности с разграничением доступа к отдельным полям и колонкам).</li>
<li>Системы управления задачами (на вычислительном кластере), системы real-time отслеживание состояния задач, хранение состояний задач описываемых как конечные автоматы (Finite State Machines).</li>
<li>Задачи представления и хранения связанных графов </li>
</ul>
<h3 class="sectionHeading">Какова история создания библиотеки BitMagic? Что послужило поводом к ее созданию?</h3>
<p>Я и мои коллеги продолжительное время занимались задачами, связанными с большими базами данных, системами анализа и визуализации. Самую первую рабочую версию, демонстрирующую возможности битовых векторов показал Максим Шеманарев (он является разработчиком великолепной библиотеки 2D векторной графики Antigrain Geometry: <a href="http://www.antigrain.com/">http://www.antigrain.com</a>). Потом некоторые идеи по эквивалентному представлению множеств были описаны инженером из Европы Koen Van Damm, работавшим над парсерами языков программирования для верификации сложных систем. Были и другие источники. Все это я решил как-то систематизировать и представить в виде библиотеки пригодной для многократного повторного использования в разных проектах.</p>
<h3 class="sectionHeading">На каких условиях распространяется библиотека BitMagic? Где можно ее скачать?</h3>
<p>Библиотека свободна для коммерческого и некоммерческого использования, доступна в виде исходных текстов. Единственным ограничением является требование упоминания библиотеки и авторов при использовании в конечном продукте.</p>
<p>С материалами можно ознакомиться тут: <a href="http://bmagic.sourceforge.net/">http://bmagic.sourceforge.net</a>.</p>
<h3 class="sectionHeading">Правильно ли я понимаю, что библиотека BitMagic получает существенные преимущества при компиляции в 64-битном варианте?</h3>
<p>Действительно, библиотека использует серию оптимизационных приемов ускоряющих работу в 64-битных системах или системах с SIMD командами (128-bit <a href="http://www.viva64.com/go.php?url=271">SSE2</a>).</p>
<p>Факторы, ускоряющие выполнение алгоритмов:</p>
<ul type="disk">
<li>широкое машинное слово (логические операции исполняются над широким словом);</li>
<li>программисту (и компилятору) доступны дополнительные регистры, не так критичен дефицит регистров (есть такой наследственный недостаток у архитектуры x86);</li>
<li>выравнивание памяти часто ускоряет работу (128-битное выравнивание адресов дает неплохой результат);</li>
<li>ну и конечно возможность помещать в память одной программы больше объектов и обрабатываемых данных. Это всем понятный и бесценный плюс 64-битного варианта.</li>
</ul>
<p>В данный момент наиболее быстрым вариантом является использование 128-bit SSE2 оптимизации в 64-битной программе. Такой режим совмещает удвоенное количество <a href="http://www.viva64.com/terminology/x86_rus.html">x86</a> регистров и широкое машинное слово для выполнения логических операций.</p>
<p>64-битные системы и программы переживают сейчас настоящий ренессанс. Перевод программ под 64 бита будет происходить быстрее, чем в переход с 16 на 32. Этому будет способствовать выход на массовый рынок 64-битных версий Windows и доступный инструментарий (такой как разрабатываете ваша компания). В условиях постоянного роста сложности систем и объема используемого в них кода такой инструментарий, как <a href="http://www.viva64.com/ru/pvs-studio/">PVS-Studio</a>, является хорошим подспорьем, так как сокращает трудозатраты и увеличивает скорость вывода продуктов на рынок.</p>
<h3 class="sectionHeading">Расскажите о методах компрессии, используемых в BitMagic.</h3>
<p>Текущая версия 3.6.0 библиотеки использует несколько методов сжатия.</p>
<ol>
<li>"Битвектора" в памяти разбиты на блоки. Если блок не занят или занят полностью - он не аллoцируется. То есть программист свободен устанавливать биты в очень далеком от нуля диапазоне. Установка бита 100,000,000 не вызывает взрыв в потреблении памяти, часто свойственный векторам с плоской линейной моделью.</li>
<li>Блоки в памяти могут иметь эквивалентное представление в виде площадок - гапов. Фактически это вариант RLE кодирования. В отличие от RLE, наша библиотека не теряет возможности выполнять логические операции или адресовать отдельные биты (random bit access).</li>
<li>При сериализации "битвекторов" применяется набор других методов: преобразование в списки целых чисел (отражающих нули или единицы) и кодирование списков методом Elias Gamma Coding. При использовании данных методов теряется возможность адресовать отдельные биты, но для записи на диск это не так критично, по сравнению с сокращением затрат на хранение и ввод-вывод. </li>
</ol>
<h3 class="sectionHeading">Не могли бы Вы привести пару примеров кода, демонстрирующих использование библиотеки BitMagic?</h3>
<p>Один из примеров просто создает 2 вектора, проводит их инициализацию и выполняет логическую операцию AND. Далее используется класс enumerator для итерирования и печати значений, сохраненных в векторе.</p>
<pre name="code" class="cpp">#include &lt;iostream&gt;
#include "bm.h"
using namespace std;
int main(void)
{
    bm::bvector&lt;&gt;   bv;    
    bv[10] = true; bv[100] = true; bv[10000] = true;
    bm::bvector&lt;&gt;   bv2(bv);    
    bv2[10000] = false;
    bv &amp;= bv2;
    bm::bvector&lt;&gt;::enumerator en = bv.first();
    bm::bvector&lt;&gt;::enumerator en_end = bv.end();
    for (; en &lt; en_end; ++en) {
        cout &lt;&lt; *en &lt;&lt; endl;
    }
    return 0;
}
</pre>
<p>Следующий пример демонстрирует сериализацию векторов и использование режима компрессии.</p>
<pre name="code" class="cpp">#include &lt;stdlib.h&gt;
#include &lt;iostream&gt;
#include "bm.h"
#include "bmserial.h"
using namespace std;
// This procedure creates very dense bitvector.
// The resulting set will consists mostly from ON (1) bits
// interrupted with small gaps of 0 bits.
//
void fill_bvector(bm::bvector&lt;&gt;* bv)
{
    for (unsigned i = 0; i &lt; MAX_VALUE; ++i) {
        if (rand() % 2500) {
            bv-&gt;set_bit(i);
        }
    }
}
void print_statistics(const bm::bvector&lt;&gt;&amp; bv)
{
    bm::bvector&lt;&gt;::statistics st;
    bv.calc_stat(&amp;st);
    cout &lt;&lt; "Bits count:" &lt;&lt; bv.count() &lt;&lt; endl;
    cout &lt;&lt; "Bit blocks:" &lt;&lt; st.bit_blocks &lt;&lt; endl;
    cout &lt;&lt; "GAP blocks:" &lt;&lt; st.gap_blocks &lt;&lt; endl;
    cout &lt;&lt; "Memory used:"&lt;&lt; st.memory_used &lt;&lt; endl;
    cout &lt;&lt; "Max.serialize mem.:" &lt;&lt; 
            st.max_serialize_mem &lt;&lt; endl &lt;&lt; endl;;
}
unsigned char* serialize_bvector(
  bm::serializer&lt;bm::bvector&lt;&gt; &gt;&amp; bvs, 
  bm::bvector&lt;&gt;&amp; bv)
{
    // It is reccomended to optimize 
    // vector before serialization.
    bv.optimize();  
    bm::bvector&lt;&gt;::statistics st;
    bv.calc_stat(&amp;st);
    cout &lt;&lt; "Bits count:" &lt;&lt; bv.count() &lt;&lt; endl;
    cout &lt;&lt; "Bit blocks:" &lt;&lt; st.bit_blocks &lt;&lt; endl;
    cout &lt;&lt; "GAP blocks:" &lt;&lt; st.gap_blocks &lt;&lt; endl;
    cout &lt;&lt; "Memory used:"&lt;&lt; st.memory_used &lt;&lt; endl;
    cout &lt;&lt; "Max.serialize mem.:" &lt;&lt; 
             st.max_serialize_mem &lt;&lt; endl;
    // Allocate serialization buffer.
    unsigned char*  buf = 
        new unsigned char[st.max_serialize_mem];
    // Serialization to memory.
    unsigned len = bvs.serialize(bv, buf, 0);
    cout &lt;&lt; "Serialized size:" &lt;&lt; len &lt;&lt; endl &lt;&lt; endl;
    return buf;
}
int main(void)
{
    bm::bvector&lt;&gt;   bv1;    
    bm::bvector&lt;&gt;   bv2;
   //  set DGAP compression mode ON
    bv2.set_new_blocks_strat(bm::BM_GAP);  
    fill_bvector(&amp;bv1);
    fill_bvector(&amp;bv2);
    // Prepare a serializer class 
    // for best performance it is best 
    // to create serilizer once and reuse it
    // (saves a lot of memory allocations)
    //
    bm::serializer&lt;bm::bvector&lt;&gt; &gt; bvs;
    // next settings provide lowest serilized size 
    bvs.byte_order_serialization(false);
    bvs.gap_length_serialization(false);
    bvs.set_compression_level(4);
    unsigned char* buf1 = serialize_bvector(bvs, bv1);
    unsigned char* buf2 = serialize_bvector(bvs, bv2);
    // Serialized bvectors (buf1 and buf2) now ready to be
    // saved to a database, file or send over a network.
    // ...
    // Deserialization.
    bm::bvector&lt;&gt;  bv3;
    // As a result of desrialization bv3 
    // will contain all bits from
    // bv1 and bv3:
    //   bv3 = bv1 OR bv2
    bm::deserialize(bv3, buf1);
    bm::deserialize(bv3, buf2);
    print_statistics(bv3);
    // After a complex operation 
    // we can try to optimize bv3.
    bv3.optimize();
    print_statistics(bv3);
    delete [] buf1;
    delete [] buf2;
    return 0;
}
</pre>
<h3 class="sectionHeading">Какие планы по развитию библиотеки BitMagic?</h3>
<p>Очень хочется реализовать несколько новых методов компрессии векторов с возможностью параллельной обработки данных.</p>
<p>В связи с массовым выходом Intel Core i5-i7-i9 целесообразно выпустить версию библиотеки для SSE 4.2. Компания Intel добавила несколько интересных возможностей, которые можно эффективно использовать. Самым интересным является аппаратная поддержка расчета числа битов (Population Count).</p>
<p>Ведутся эксперименты с nVidia CUDA и другими GPGPU. Графические карты сегодня предоставляют возможность выполнения целочисленных и логических операций - есть возможность использовать их ресурсы для алгоритмов работы с множествами и компрессии.</p>
<h2>Библиографический список</h2>
<ol>
<li>Elias Gamma encoding of bit-vector Delta gaps (D-Gaps). <a href="http://bmagic.sourceforge.net/dGap-gamma.html">http://bmagic.sourceforge.net/dGap-gamma.html</a></li>
<li>Hierarchical Compression. <a href="http://bmagic.sourceforge.net/hCompression.html">http://bmagic.sourceforge.net/hCompression.html</a></li>
<li>D-Gap Compression. <a href="http://bmagic.sourceforge.net/dGap.html">http://bmagic.sourceforge.net/dGap.html</a></li>
<li>64-bit Programming And Optimization. <a href="http://bmagic.sourceforge.net/bm64opt.html">http://bmagic.sourceforge.net/bm64opt.html</a></li>
<li>Optimization of memory allocations. <a href="http://bmagic.sourceforge.net/memalloc.html">http://bmagic.sourceforge.net/memalloc.html</a></li>
<li>Bitvector as a container. <a href="http://bmagic.sourceforge.net/enum.html">http://bmagic.sourceforge.net/enum.html</a></li>
<li>128-bit SSE2 optimization. <a href="http://bmagic.sourceforge.net/bmsse2opt.html">http://bmagic.sourceforge.net/bmsse2opt.html</a></li>
<li>Using BM library in memory saving mode. <a href="http://bmagic.sourceforge.net/memsave.html">http://bmagic.sourceforge.net/memsave.html</a></li>
<li>Efficient distance metrics. <a href="http://bmagic.sourceforge.net/distopt.html">http://bmagic.sourceforge.net/distopt.html</a></li>
</ol> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/BitMagic-Library</link>
      <pubDate>Mon, 16 Nov 2009 01:53:32 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/BitMagic-Library#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/BitMagic-Library</guid>
      <category>Параллельное программирование</category>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
    <item>
      <title>Секреты использования Intel® Parallel Inspector для поиска ошибок многопоточности</title>
      <description><![CDATA[ <h2>Аннотация</h2>
<p>В статье будет сделан обзор особенностей нового  инструмента для диагностики ошибок многопоточности Intel® Parallel Inspector, который является одним из компонентов пакета продуктов  для разработки многопоточных приложений Intel® Parallel Studio. Тесная  интеграция с Microsoft* Visual Studio обеспечит легкий старт новичкам и сделает  более продуктивной работу опытных разработчиков на языках C/C++ под Windows.</p>
<h2>Введение</h2>
<p>Intel®  Parallel Inspector входит  в состав набора  Intel® Parallel Studio. Inspector может  быть установлен и проинтегрирован в Microsoft Visual Studio* как  часть набора, так и отдельно. Инструмент помогает разработчикам обнаружить  ошибки в многопоточной программе на этапе ее написания и верификации, улучшая  корректность и стабильность ее исполнения. Специфичный функционал инструмента и  возможность его использоывния из командной строки подразумевают применение в  процессе автоматизированного тестирования. Но, благодаря удобному графическому  интерфейсу, Inspector  используется разработчиками и в повседневной практике при проверке тех или  инных участков проекта.  Кроме того,  анализ готовых проектов на наличие ошибок многопоточности часто «открывает  глаза» на другие имеющиеся в коде проблемы, что заставляет задуматься о  подходах к разработке многопоточных программ.</p>
<p>В данной статье мы рассмотрим особенности использования Inspector’а для поиска ошибок, специфичных для многопоточных  программ. Инструмент также позволяет находить и ошибки доступа к памяти, но эта  тема рассмотрена в отдельной статье.</p>
<h2>Как  обнаруживаются ошибки многопоточности</h2>
<p>Как и другие инструменты, анализирующие корректность  исполнения программы, Inspector не производит разбор и анализ синтаксиса  исходного кода, а работает с объектами бинарного кода, исполняемыми в системе.  Поэтому, для анализа исполняемого кода «на лету», Inspector  использует специальные методы внедрения в  процесс и перехвата вызовов системных и потоковых API. Данные собираются  динамически, и анализируются только те участки кода, которые исполнялись при  работе программы. Именно поэтому при тестировании рекомендуется запускать такие  сценарии, которые бы охватывали тестируемый код как можно более полно (свойство  полноты теста). Ввиду того, что инструмент не имеет априорной информации о количестве  создаваемых потоков, их параметрах, времени жизни, разделяемых данных, методах  работы с данными, функциях синхронизации и так далее, он выстраивает  собственную динамическую модель исполнения приложения, основными элементами  которой являются события, например, системные вызовы и доступ к разделяемым  между потоками данным.</p>
<p>Для внедрения в процесс иследуемого приложения, перехвата  системных вызовов и анализа всех инструкций чтения/записи памяти и их адресов  на уровне бинарного кода в Inspector’е применяется модификация приложения с помощью  динамической бинарной инструментации (рис.1). В основе инструментатора лежит  утилита Pin - Dynamic Binary Instrumentation Tool (<a href="http://www.pintool.org">http://www.pintool.org</a>),  которая внедряется в анализируемый процесс непосредственно перед стартом,  позволяя отслеживать выполнение практически любых инструкций и предоставляя API доступа к содержимому регистров, контексту выполнения  программы, символьной и отладочной информации.</p>
<p><img border="0" height="152" width="505" alt="Схема взаимодействия инструмента и приложения" src="http://software.intel.com/file/23574" /></p>
<p><em>Рис.1. Схема взаимодействия инструмента и приложения</em></p>
<p>Для составления адекватной модели инструмент должен  отслеживать все вызовы потоковых API и функции выделения/освобождения памяти  динамически. В Windows таких API-функций более 170. Причем, возможно, не все  функции поддерживаются инструментом, так как за основу взяты только те, которые  опубликованы и описаны разаботчиком операционной системы. В связи с этим  существует вероятность возникновения ошибок типа False Positive и False  Negative, то есть ложное обнаружение ошибок, либо ошибочное необнаружение  проблем. Иногда таких ошибок может быть достаточно много.</p>
<p>В виду того, что ошибки многопоточности по существу – это  неудачно расположенные во времени события, происходящие в одновременно  работающих потоках, то строящаяся и анализирующаяся модель исполнения  многопоточной программы тоже имеет протяженность «во времени». Однако отсчетами  во временной шкале являются не секунды, а события, изменяющие состояние модели.  Это позволяет обнаруживать не только ошибки, произошедшие во время  тестирования, но и те, которые не произошли, но могли бы произойти при  каких-либо других временных параметрах системы.   Например, создание потока может быть первым отсчетом во «временной» шкале.  Все вызовы функций синхронизации являются такими отсчетами, так же как и доступ  к разделяемым переменным. А в промежутках между ними находятся участки (frames), в течение которых состояние считается неизменным, а  доступ к данным - либо безопасным, либо нет, в зависимости от значения  предыдущих отсчетов.</p>
<p>В качестве примера приведем ситуацию с доступом к  глобальной переменной из двух потоков. На шкале уже есть отсчеты,  соответствующие созданию обоих потоков, и один отсчет модификации переменной в  потоке T1. Если в следующем «фрейме» зарегистрирован доступ к переменной из  потока T2, то он будет признан опасным, и будет сформировано сообщение об  ошибке. При этом совершенно не важно, насколько близко или далеко друг от друга  в реальном времени эти события произошли. Если же доступ к переменной в потоках  T1 и T2 разделяет отсчет определенного типа, например вызов функции  синхронизации в T1 или функции уничтожения потока T1, то следующий фрейм будет  признан для операции с переменной безопасным, как и остальные, до тех пор, пока  не будут зарегистрированы события, способные повлиять на возможность ошибочной  модификации глобальной переменной.</p>
<p>В течение исполнения приложения Inspector накапливает  сообщения об ошибках и информацию о событиях, которые будут представлены  пользователю. Некоторые из этих событий можно просмотреть в реальном времени  еще до завершения работы программы.</p>
<p>В Intel® Parallel Inspector используется оригинальный подход к представлению  структуры обнаруженных ошибок и анализируемых проблем в коде. Существуют три  категории представления:</p>
<p><strong>Observation</strong> (замер или наблюдение). Некоторое изменение состояния,  зафиксированное для определенного участка кода, например: создание объекта  синхронизации, доступ к объекту в памяти,   захват блокировки и т.д.</p>
<p><strong>Problem</strong>(проблема).  Несколько логически объединенных наблюдений, которые обусловили проблему,  например: чтение непроинициализированной памяти, внеочередной захват  блокировки, незащищенный доступ к разделяемым данным.</p>
<p><strong>Problem</strong><strong>Set</strong> (совокупность проблем). Набор проблем, в которых  участвует одно или несколько общих наблюдений. Это может быть группа событий  несинхронизированного чтения/записи незащищенных данных или потенциальная блокировка  в цикле.</p>
<p>Взаимоотношение между этими категориями удобно  рассматреть на примере графа (рис.2).   Белые узлы графа представляют Проблемы. Они могут быть рассмотрены как  уникальные случаи ошибок в программе. Красные узлы представляют Наблюдения. Каждая  из Проблем связана с несколькими Наблюдениями локальной связью, отражаемой  линией на графе. Иногда одно Наблюдение участвует в двух Проблемах. Например,  если проблема «гонок» (data races) в параллельном коде возникла из за несихронизированного  доступа по записи глобальной переменной, то наверняка существует еще несколько  проблем, связанных с несинхронизированным доступом к этой переменной.</p>
<p><img border="0" height="378" width="646" alt="Взаимоотношение категорий представления" src="http://software.intel.com/file/23575" /></p>
<p><em>Рис.2. Граф, отражающий взаимоотношение категорий  представления</em></p>
<p>Глобальный граф взаимоотношений может быть рассмотрен в  различных ракурсах. Давайте проанализируем Наблюдение O2. O2 участвует в трех различных Проблемах: P2 { O1,O2,O9}, P5{O2,O3,O4} и P6{O2,O3}. Мы  экстраполируем взаимосвязь на все эти Наблюдения, потому что если все три  проблемы решаются в случае изменения налюдения O2, то его модификация также  окажет влияние на остальные Наблюдения.</p>
<p>В графе могут быть Проблемы, которые стоят особняком (P3) и никак не связаны с общими для других Проблем  Наблюждениями.</p>
<p>Еще один тип взаимоотношений представляют опосредованные  отношения между P1 и P4, которые, возможно, имеют различные пути их решения.  Хотя, если представить себе, что O3 и O9 – это аллокация объектов, добавление  необходимых элементов сихронизации для этих объектов могло бы разорвать эту  сложную связь.</p>
<p>В любом случае пользователю не нужно анализировать  глобальный граф взаимоотношений, так как результаты представлены  пользовательским интерфейсом в более удобной для восприятия табличной форме.  Хотя, если проанализировать любую из найденных проблем в списке, мы всегда  сможем восстановить такой граф.</p>
<h2>Интерфейс</h2>
<p>Еще до запуска приложения пользователь должен выбрать  уровень анализа, который определяет детализацию получаемой информации и  накладные расходы на исследование. Последние обусловлены использованием  технологии динамического бинарного инструментирования. Приложение будет  исполняться дольше, чем если бы оно было запущено вне анализа. В обычной  практике для пользователей время анализа не является критичным, так как он  проводится один раз в текущей сборке проекта. Тем более, что если тестирование  автоматизированное, то длительный тест можно оставить на ночной период. Однако  возможность управления глубиной анализа тоже необходима, и пользователю  предоставляется четыре уровня анализа, в зависимости от его глубины:</p>
<ul>
<li>Уровень ti1 – позволяет  обнаруживать взаимные блокировки потоков. Глубина стека функций равна единице.  Это самый «легкий» тип анализа, который позволяет быстро проверить программу,  которая почему-то иногда «виснет»; чтобы понять, являются ли ошибки  многопоточности причиной проблем.</li>
<li>Уровень ti2 –  дополнительно к первому уровню позволяет обнаруживать конкуренцию доступа к  незащищенным данным, «гонки». Это основной уровень, на котором относительно  быстро обнаруживаются большинство ошибок многопоточности. Глубина стэка выбрана  минимальной, так как на этом уровне мы обнаруживаем, что ошибки есть, не вникая  особенно, где они располагаются. При этом накладные расходы относительно  невысокие. Уровень является оптимальным для быстрого автоматизированного  тестирования.</li>
<li>Уровень ti3 – отличается  от предыдущего тем, что глубина стека увеличена до 12-ти. Уровень используется  для исследования конкретных проблем многопоточности, включая анализ потока  исполнения программы, приведшего к ошибке.</li>
<li>Уровень ti4 – высший  уровень, позволяющий определять все типы ошибок многопоточности, которые  способен обнаруживать инструмент. Главная отличительная особенность –  анализирует многопоточный доступ к стековой памяти. Уровень вложенности функций  – 32. Как и все остальные уровни, 4-й является инклюзивным, то есть включающим  в себя все виды анализа на предыдущих уровнях. Соответственно, накладные  расходы будут максимальными. Анализ реального большого приложения может занять  значительное время, поэтому рекомендуется проводить его либо в  автоматизированном режиме на тестовых системах, либо оставлять работающий с  приложением инструмент на выходные в конце недели.</li>
</ul>
<p><img border="0" height="514" width="570" alt="Окно выбора глубины анализа" src="http://software.intel.com/file/23576" /></p>
<p><em>Рис.3. Окно выбора глубины анализа</em></p>
<p>Глубина стека на каждом уровне выбрана эмпирически и  является компромиссом между полнотой предоставляемой информации и величиной  накладных расходов на анализ приложения. В данной версии продукта пользователь  не может менять глубину стека.</p>
<p>Выбрав соответствующий тип анализа в инструментальной  панели Parallel Inspector, пользователь нажимает кнопку “Inspect” и выбирает  уровень анализа. Инструментатор внедрится в процесс анализируемого приложения,  заменив оригинальный код, и инициализирует запуск кода программы. Как и в  случае с Memory Checker'ом, вместе с загрузкой дополнительных библиотек процесс  старта приложения занимает определенное время, поэтому пользователь заметит  задержку. Чем больше динамических библиотек в программе, тем больше эта  задержка. Далее, приложение исполняется как обычно, только медленнее - в  зависимости от глубины анализа, ну и, конечно, от относительного количества  специфичных вызовов функций Memory и Threading API во время исполнения.</p>
<p>После окончания исполнения приложения Inspector выведет  список ошибок и диагностических сообщений о событиях, связанных с  существованием потоков в процессе выполнения. Каждому сообщению Inspector  сопоставит строку исходного кода, в которой найдена причина того или иного  события или ошибки, а также стек вызовов функций и адрес памяти.</p>
<p>В соответствии с упомянутым выше графом, результаты  анализа структурированы таким образом, чтобы вначале у нас был обзор списка  проблем (Problem Sets), представляющих собой конечный результат действия  нескольких событий, приведших к проблеме (Observations). Например, «гонки» или  data race проблема  является следствием  доступа из двух потоков к общим данным по «чтению и записи», или по «записи и  записи». Такие детали в виде зарегистрированных событий  можно посмотреть в окне, нажав кнопку  “Details” в главном окне. Там же можно взглянуть на код, в котором это событие  произошло (Рис.4). Это удобно, когда нет необходимости переключаться в редактор  исходного кода, а нужно просто пройтись по ошибкам в списке и просмотреть, на  какие строки кода ссылается диагностика.</p>
<p><img border="0" height="372" width="642" alt="Список событий и ссылки на исходный код приложения" src="http://software.intel.com/file/23577" /></p>
<p><em>Рис.4. Список событий и ссылки на исходный код приложения</em></p>
<p>В списке с найденными ошибками нам доступна вся  информация относительно процесса, модуля, функции и номера строки кода, в  которой эта ошибка произошла. В реальных проектах список ошибок бывает значительным.  В этом случае удобно воспользоваться фильтром по типу ошибки, ее описанию,  имени исходника, имени функции или модуля. В процессе исправления ошибок можно  помечать их как исправленные, или отфильтровывать, чтобы они не загромождали  список.</p>
<p>Из списка проблем или детального списка можно перейти в  режим Sources (рис.5). Так как потоковые ошибки сопряжены с выполнением  программы несколькими потоками одновременно (а иногда доступ к незащищенным  данным может происходить из разных функций), то для удобства представления и  обнаружения причин ошибки Inspector отображает сразу два окна исходного кода с  функциями, выполнявшимися разными потоками. В них будет отображена взаимосвязь  между событиями, например такими, как доступ по чтению и записи к данным, которые  привели к «гонкам». Нарушение корректности реализации многопоточной логики  будет продемонстрировано как с помощью исходного кода, так и в окне  Observations Relationships, где графически выстраивается связь между событиями,  в данном случае равноправными. При этом события помечаются различными цветами,  чтобы легко было определить соответствующие окна исходного кода, описания  событий и стеки вызовов функций. Стеки вызовов нужны для того, чтобы было легче  ориентироваться, каким образом мы попали в ту или иную функцию; так как  событие, приведшее к ошибке, далеко не всегда может произойти в самом  исследуемом приложении, а, например, может быть вызвано в сторонней библиотеке.  Найдя ошибку, совсем не обязательно переключаться Alt-Tab’ом в окно редактора  Visual Studio и искать нужные файл исходника и строку. Достаточно двойного  щелчка мышки на строке кода в окне Sources - и мы попадаем в редактор Visual  Studio в нужное нам место.</p>
<p><img border="0" height="371" width="639" alt="Вид Sources" src="http://software.intel.com/file/23578" /></p>
<p><em>Рис.5. Вид Sources</em></p>
<p>При активном использовании в приложении сторонних многопоточных  библиотек возникает риск ложных диагностик, которые выдает Inspector из-за  нехватки отладочной информации в модулях или нестандартных способах  синхронизации потоков. Довольно показательным примером может служить так  называемая User Level Synchronization, когда вместо использования потокового  API, предоставляемого операционной системой, применяются методы легковесных  пользовательских примитивов для улучшения производительности. В данном случае  Inspector  не подозревает, что  разработчик позаботился о целостности данных, и будет регистрировать ошибки  data race. Для того чтобы подобные диагностики не замусоривали отчет о  проблемах, полезно применять механизм подавления диагностики уже известных  ошибок, Suppressions. Пользователь может создавать собственные фильтры,  добалять ошибку в список Private Suppressions, специфицируя критерий ее  фильтрации, например, по модулю, имени функции, файлу исходного кода и даже  строке (рис.6.).</p>
<p><img border="0" height="346" width="500" alt="Подавление диагностики известных ошибок" src="http://software.intel.com/file/23579" /></p>
<p><em>Рис.6. Создание фильтра для подавления диагностики  известных ошибок</em></p>
<p>При следующем старте анализа необходимо использовать одну  из опций: “Mark problems” или “Delete Problems”. При этом ошибки будут либо  помечены в списке, либо вообще не отображены. Для отмены подавления достаточно  выбрать опцию “Do not use suppressions”. При необходимости файл с созданными  фильтрами можно использовать и для других проектов. Для этого достаточно его  сохранить в каком либо доступном месте и добавить путь к нему в настройках  Public Suppressions: Menu &gt; Tools &gt; Options &gt; Intel Parallel Inspector  &gt; General &gt; Suppressions &gt; Public Suppressions.</p>
<h2>Типы  обнаруживаемых ошибок многопоточности и некоторые хитрости</h2>
<p>Основными ошибками многопоточности являются «гонки» (data  races), или конкурирующий доступ потоков к разделяемым данным, и  взаимоблокировки (deadlocks), когда, захватив неправильно расставленные объекты  синхронизации, потоки самозаблокировались и не могут продолжить свое  выполнение. Примеры кода, приводящего к такого рода ошибкам, рассматривать не  будем; они достаточно просты и обсуждались множество раз. Необходимо только  упомянуть, что Inspector выдает еще два типа ошибок: Lock Hierarchy Violation и  Potential Privacy Infringement.</p>
<p>Lock Hierarchy Violation. Эта ошибка прояляется, когда  порядок захвата нескольких объектов синхронизации (мьютекс, критическая секция,  и т.д.) в одном потоке отличается от порядка их захвата в другом. Либо в  случае, когда объекты захватываются одним потоком, а освобождаются другим.  Ситуация Deadlock является следствием ошибки Lock Hierarchy Violation. Но если  Inspector обнаружил ошибку Deadlock, то только она и будет диагностироваться.</p>
<p>Potential Privacy Infringement. Интересная ситуация,  связанная с тем, что один поток пытается доступиться к объектам стековой памяти  в другом потоке. Простой пример, демонстрирующий такую ситуацию:</p>
<p><em>Листинг 1</em></p>
<pre name="code" class="cpp">int *p;//глобальный указатель
//функция потока Т1
{
int q[10]; //локальный массив переменных
p = q; //публикация долкальных данных
q[0] = 1;
}
//функция потока Т2
{
*p = 2; //доступ к стеку Т1
}

  </pre>
<p>Inspector выдаст предупреждение Potential Privacy  Infringement, указывая на события Stack Across Access в функции, исполняемой потоком T2, и Stack Owned в  функции, исполняемой потоком T1. На самом деле обычно так не поступают,  публикуя локальные данные потока с возможностью доступа к ним из других  потоков. Но такую диагностику все-таки можно встретить в реальных приложениях,  например, базирующихся на OpenMP. При этом Stack Across Access будет диагностирован в точке создания дополнительных  потоков, например в строке с прагмой omp parallel (рис.7). Это происходит  потому, что поток-потомок имеет доступ к разделяемым стековым переменным; в  данном примере это start и end.</p>
<p><img border="0" height="372" width="642" alt="Пример диагностики Potential Privacy Infringement" src="http://software.intel.com/file/23580" /></p>
<p><em>Рис.7. Пример диагностики Potential Privacy  Infringement</em></p>
<p>Рассмотрим еще один интересный случай. В процессе анализа  приложения одного из наших пользователей было обнаружено, что Inspector  диагностирует data race там, где, по идее, его быть не должно. Ниже представлен  урезанный пример, который сам по себе не вызовет такую ошибку, но показателен  по сути.</p>
<p><em>Листинг 2</em></p>
<pre name="code" class="cpp">void function1(int **ppBuffer)
{
	#pragma omp parallel for
	for(int x = 0; x &lt; 10; x++)
	for(int y = 0; y&lt; 10; y++)
		ppBuffer[x][y] = 10;
	return;
}

void function2(int **ppBuffer1, int **ppBuffer2)
{
	#pragma omp parallel for
	for(int x = 0; x &lt; 10; x++){
	for(int y = 0; y&lt; 10; y++)
	{
		j = y + 1;
		ppBuffer2[x][y] = ppBuffer1[i][j];
	}}
	return;
}
int main(void)
{
	...//some code here including allocation ppBuffer1,2
	function1(ppBuffer1);
	function2(ppBuffer1, ppBuffer2);
	return 0;
}
</pre>
<p>Инспектор выдал ошибку Data Race, указывая в качестве событий чтение буфера памяти в функции function2 и запись буфера памяти в функции function1. Результаты диагностики представлены теперь для  реального примера (рис.8).</p>
<p><img border="0" height="372" width="641" alt="Ошибка Data Race" src="http://software.intel.com/file/23581" /></p>
<p><em>Рис.8. Ошибка Data Race для двух функций</em></p>
<p>На самом деле, тем, кто знаком с fork/join моделью  OpenMP, такая ошибка кажется невероятной, даже с учетом того, что выделение  памяти для буфера организовано так, что она берется из одного массива,  выделенного из кучи одним оператором    Дело в том, что функции function1 и function2, хотя и выполняются в параллельном режиме, вызываются  одна за другой в main, и точка возврата из function1 практически являеся  join-точкой для потоков, созданных внутри нее. А значит, даже если функции  оперируют одним и тем же, разделяемым буфером памяти, они не могут создать  такую ситуацию Race Condition по определению модели OpenMP.</p>
<p>Пользователь, внимательно следящий за сообщениями во  время анализа Inspecrot'а, может увидеть, как инструмент предупреждает о том,  что обнаружены проблемы с OpenMP run-time. Такое поведение инструмента  обусловлено тем, что программа была слинкована с Microsoft OpenMP run-time библиотекой  (vcomp.dll), а Inspector не поддерживает каких либо других OpenMP библиотек времени исполнения кроме Intel. Сделано  это не специально, а в силу сложившихся обстоятельств, одним из которых  является то, что Intel поддерживает более новую версию стандарта OpenMP.</p>
<p>Значит ли это, что Inspector непригоден для анализа OpenMP-приложений,  скомпилированных в MSVS? Нет. Это ограничение легко обойти. Для этого  достаточно слинковаться с Intel OpenMP run-time библиотекой, явно передав  линкеру ее имя.</p>
<p>Для Microsoft Visual Studio IDE необходимо изменить  следующие настройки проекта:</p>
<p>Project  Properties -&gt; Linker -&gt; Input -&gt; Ignore Specific Library -&gt; указать vcomp.dll или vcompd.dll <br /> Project  Properties -&gt; Linker -&gt; Input -&gt; Additional Dependencies -&gt; вписать libiomp5md.lib</p>
<p>Для тех, кто использует компиляцию из командной строки,  нужно модифицировать команду следующим образом:<br /> icl  /MD /openmp hello.cpp /link /NODEFAULTLIB:"vcomp.dll" libiomp5md.lib</p>
<p>Более подробную информацию о совместимости OpenMP библиотек можно почерпнуть здесь: Using the OpenMP Compatibility Libraries  (http://software.intel.com/sites/products/documentation/hpc/compilerpro/en-us/cpp/win/compiler_c/optaps/common/optaps_par_compat_libs_using.htm)</p>
<p>Рассмотрим пример прямо противоположный предыдущему. <br /> В некоторых случаях Inspector может не обнаруживать  ошибку Data Race, несмотря на то, что она явно заложена в коде (рис. 9).  Правда, для этого пришлось придумать синтетический пример, который вряд ли встречается  в реальной практике.</p>
<p><img border="0" height="371" width="639" alt="Ошибки при анализе на уровне ti3 не обнаружены" src="http://software.intel.com/file/23582" /></p>
<p><em>Рис.9. Ошибки при анализе на уровне ti3 не обнаружены</em></p>
<p><em>Листинг 3</em></p>
<pre name="code" class="cpp">int g_var;
void TestFunc(int par)
{
	printf("Thread# %d\n", omp_get_thread_num());
	if (par == 0)
		g_var++;
	if (par != 0)
		g_var--;
}

int _tmain(int argc, _TCHAR* argv[])
{
omp_set_num_threads(2);

#pragma omp parallel for
	for (int i=0; i&lt;2; i++)
		TestFunc(i);

printf("%d \n", g_var);
return 0;
}

  </pre>
<p>В данном примере функция TestFunc() вызывается в двух различных потоках с входными  параметрами 0 и 1. То, что это именно так, можно проверить с помощью вывода на  печать исполнения функции omp_get_thread_num(). При этом глобальная  переменная модифицируется в обоих потоках, что, по идее, несомненно должно  детектироваться как ошибка Data Race.</p>
<p>Что бы еще более точно убедиться в корректности начальныз  условий, можно проверить код дизассемблера на предмет оптимизации функции  TestFunc(), из которого видно, что операции инрементации и декрементации  присутствуют.</p>
<p><em>Листинг 4</em></p>
<pre name="code" class="cpp">#   if (par == 0)  
0040105A  mov         eax,dword ptr [par]  
0040105D  test        eax,eax  
0040105F  jne         TestFunc+4Bh (401067h)  
        g_var++;  
00401061  inc         dword ptr [g_var (4072A4h)]  
    if (par != 0)  
00401067  mov         eax,dword ptr [par]  
0040106A  test        eax,eax  
0040106C  je          TestFunc+58h (401074h)  
        g_var--;  
0040106E  dec         dword ptr [g_var (4072A4h)]    

</pre>
<p>Не раскрывая особых секретов внутренней имплементации Inspector’а, можно сказать, что инструмент отслеживает доступ к  памяти потоками, выделяя подобные участки в «теневой» памяти и сохраняя историю  доступа потоков к ним и вызовов функций синхронизации. Однако копировать все  данные в «теневую» память было бы чрезвычайно накладно, поэтому инструмент  поступает следующим образом. Если поток Т1 один раз обращается к памяти 0хNNNN,  то ничего не происходит (за исключением сохранения «истории» факта этого  доступа). Далее, если поток T2 обратился к той же памяти, то это уже сигнал к  тому, чтобы сохранить объект в «теневой» области и начать отслеживать  дальнейшие обращения к нему. Однако, если эта память больше не  читается/пишется, то никаких дальнейших действий в отношении анализа доступа не  производится.</p>
<p>Так, собственно, и происходило в нашем примере. Одна и та  же переменная g_var была «тронута» каждым из двух потоков, при этом не  происходило пересечения обращения к переменной и наступления событий  синхронизации или активности другого потока, что вызвало создание «теневой» ячейки  под g_var, и не более того. Вот если бы мы установили три и более исполняемых  потока, или произвели еще какую-либо операцию над g_var, то тогда бы «гонка»  была диагностирована.</p>
<p>Сделано это исключительно для того, чтобы резко снизить  накладные расходы на анализ любого обращения к памяти, которое так и останется  единичным обращением. В результате мы можем получить такие вот вырожденные  случаи, когда на уровнях ti2-ti3 очевидные «гонки» не обнаруживаются, хотя по  заявленным описаниям должны бы. Но на то они и вырожденные случаи, чтобы  встречаться в практике в реальных программах крайне редко. Разработчики  считают, что данный компромисс никак не отразится на качестве обнаружения  потоковых проблем в реальных приложениях. Более того, если запустить пример на анализ  на уровне ti4, то ошибка будет успешно диагностирована (рис. 10), так как здесь  уже не требуется достижения компромисса между скоростью и тщательностью поиска  ошибок.</p>
<p><img border="0" height="371" width="639" alt="Обнаружена ошибка при анализе на уровне ti4" src="http://software.intel.com/file/23583" /></p>
<p><em>Рис.10. Обнаружена ошибка при анализе на уровне ti4</em></p>
<h2>Заключение</h2>
<p>Использование инструмента, проверяющего корректность  исполнения программ, является обязательным условием при разработке  моногопоточных приложений. Все потоковые «чекеры» используют те или иные  подходы для увеличения производительности анализа, и не существует ни одного инструмента  общего назначения, который бы гарантировал 100-процентную вероятность  обнаружения всех потоковых ошибок и исключал бы ложные диагностики, – это ниша  специализированных анализаторов, привязанных к определенным параллельным языкам  и run-time библиотекам. Поэтому при анализе необходимо тщательно проверять  полученные диагностики, и удостоверяться, что код программы мог ее вызвать. В  сложных случаях разработчики Intel Parallel Inspector будут рады вам помочь, а также услышать мнения  пользователей о продукте и обсудить те недостатки, которые еще есть в нем, на ISN форумах, как англоязычном <a href="http://software.intel.com/en-us/forums/intel-parallel-studio">http://software.intel.com/en-us/forums/intel-parallel-studio</a>, так и <a href="http://software.intel.com/ru-ru/forums/intel-parallel-studio">http://software.intel.com/ru-ru/forums/intel-parallel-studio</a> русскоязычном.</p>
<p>*другие наименования и товарные знаки могут являться  собственностью своих законных владельцев.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/intel-parallel-inspector-secrets</link>
      <pubDate>Mon, 09 Nov 2009 08:34:05 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/intel-parallel-inspector-secrets#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/intel-parallel-inspector-secrets</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Спам и методы борьбы с ним</title>
      <description><![CDATA[ <p>В настоящее время, не существует практически ни одного человека, кто не сталкивался бы со спамом. У вас есть почтовый ящик, аккаунт в Интернет-магазине, страница в социальной сети, блог в ЖЖ или просто ваш компьютер имеет доступ к Интернет – значит вы потенциальная цель для спамеров.</p>
<p>Рассмотрим основные цели, которые преследуют люди, занимающиеся этим:</p>
<h3 class="sectionHeading">Реклама</h3>
<p>Реклама для легальных компаний, которые таким образом пытаются привлечь внимание покупателей к своим товарам. В большинстве случаев вместо увеличения продажи товаров наблюдается обратный эффект. Хотя в настоящее время существует мнение, что есть несколько критериев, выполняя которые фирма может получить желаемый эффект и при этом не вызывать отрицательных эмоций у получателя писем:</p>
<ul type="disc">
<li>Организатором рассылки должен являться поставщик      услуг электронной почты;</li>
<li>Хорошо подобранная целевая аудитория (зачем      рассылать предложения продажи суперкомпьютеров, к примеру, домохозяйкам);</li>
<li>В тексте письма должно содержаться предупреждение о регулярности данной рассылки;</li>
<li>Наконец, предоставление пользователю удобных      механизмов блокирования подобных писем.</li>
</ul>
<p>Не смотря на то, что данные критерии можно оспорить, т.к. они весьма условны, не стоит забывать, что рекламные письма, рассылаемые пользователям с их согласия, не являются спамом.</p>
<h3 class="sectionHeading">Реклама незаконной продукции</h3>
<p>Несомненно, каждый из нас хотя бы один раз встречался с подобным. Здесь предлагается всё, начиная от, скажем, баз данных крупнейших сотовых операторов и заканчивая контрафактным программным обеспечением.</p>
<h3 class="sectionHeading">Антиреклама</h3>
<p>В противовес рекламе ставит своей целью всячески помешать объекту атаки (фирме или простому пользователю). Как правило, содержит заведомо ложную информацию с целью опорочить атакуемый объект.</p>
<h3 class="sectionHeading">Письма с целью выманивания денег</h3>
<p>Принцип довольно прост. Пользователю приходит письмо содержащее информацию о том, что он может получить крупную сумму денег, а отправитель может ему в этом помочь. Далее содержится просьба перевести небольшую часть денежных средств, для, например, оформления документов или под любым другим предлогом. Получение этих денежных средств и есть цель отправителя.</p>
<h3 class="sectionHeading">Фишинг</h3>
<p>Цель – выманить у получателя конфиденциальную информацию, номера кредитных карт, информацию о профиле в электронных платёжных системах и т.д. Здесь активно используется социальная инженерия.</p>
<h3 class="sectionHeading">Распространение вирусов</h3>
<p>Цель заразить как можно больше компьютеров, чтобы создать бот-нет или провести Dos атаку.</p>
<p>С развитием Internet развиваются и способы распространения спама. Как и раньше основная доля приходится на рассылку писем по электронной почте. Причём для этого используются не только почтовые серверы, но и «компьютеры-зомби».</p>
<p>Заходя на сайты, довольно часто мы сталкиваемся с всплывающими окнами, которые занимают большую часть экрана, и при закрытии выполняют переадресацию на некую целевую страницу.</p>
<p>Набирает обороты спам в системах мгновенных сообщений, так называемый <b>спим</b> (<b>SP</b>am + <b>I</b>nstant <b>M</b>essenger).</p>
<p>В настоящее время стали популярны web-сайты, на которых можно оставлять комментарии (форумы, блоги, социальные сети) или свободно редактируемые – wiki. Благодаря тому, что данные страницы возможно редактировать на них может быть размещён спам. Целью может служить продвижение какого-либо ресурса (в том числе и в поисковых системах), посредством размещения ссылок, всплывающих окон и/или сценариев автоматической переадресации. Так например, если основная тематика сайта, скажем, автомобили, а большинство комментариев содержит рекламу фармацевтических препаратов, то релевантность данного ресурса для поисковых систем будет снижена.</p>
<p>Также спам перешел «границу» Интернета и теперь рассылается и в СМС сообщениях. Это стало возможным, благодаря появлению дополнительной функциональности в системах обмена мгновенными сообщениями, системах голосовой связи, а так же сервисам, предоставляемым самими операторами сотовой связи.</p>
<p>К основным методам распространения спама относятся:</p>
<ul type="disc">
<li>Рассылка спама вручную.<br /> Его эффективность невелика      и с ним легко бороться, внося ip-адрес и почтовый адрес в чёрный список</li>
<li>Программы автоматической рассылки спама.<br /> Могут быть      выполнены в виде утилиты под Windows или скрипта для размещения на      web-сайте. В сущности, это автоматизированная разновидность метода 1.</li>
<li>Рассылка спама при помощи сети троянских прокси (Trojan-Proxy)      и спам-ботов. Этот метод наиболее популярен и актуален в настоящее время.</li>
</ul>
<p>Наиболее интересен 3 метод. Для построения сети Trojan-Proxy или спам-ботов, необходимо заразить ими компьютер пользователя. Это можно сделать с помощью: использования уязвимостей в программном обеспечении, установленном на компьютере жертвы; использования специальной программы, которая сама по себе не содержит вредоносного кода, и возможно, содержит какой-либо полезный функционал, но вместе с тем загружает под видом обновлений или в скрытом режиме вирус; использования почтовых и сетевых червей.</p>
<p>В случае заражения компьютера пользователя спам-ботом, последний связывается с сервером владельца, от которого получает информацию, необходимую для дальнейшей работы: список электронных адресов, параметры рассылки, шаблоны писем и так далее. Компьютер с такой программой очень быстро попадает в чёрные списки и у его владельца возникают сложности с отправкой обычной почты. Это особенно актуально для крупных фирм с собственным почтовым сервером. Представьте себе ситуацию, когда крупная компания оказывается «отрезанной» от остального мира, т.е. вся её электронная переписка не отправляется на другие почтовые серверы и не получается от них соответственно. Подобное может произойти, если даже хотя бы у одного корпоративного пользователя окажется заражённый компьютер. Создание сетей таких компьютеров является очень прибыльным бизнесом. Причём деньги можно получить не только предоставляя услуги рассылки спама, но и за продажу как всей сети, так и отдельного бота.</p>
<p>Ещё одна методика рассылки спама, заключается в использовании так называемых троянских прокси (Trojan-Proxy). Они позволяют злоумышленнику работать в сети от имени заражённой машины. По своей сути являются вирусами, т.к. всячески маскируют свое присутствие в системе, под видом системных процессов (наиболее часто под видом winlogon-а). Принцип работы в следующем, открывается на прослушивание некий TCP-порт (номер порта статический или динамический), после чего данный процесс связывается с владельцем и передаёт ему IP-адрес и номер порта, открытого для прослушивания. Затем он работает как обычный Proxy-сервер. Большинство троянских-прокси умеет размножаться по принципу сетевых червей или при помощи уязвимостей в программном обеспечении компьютера жертвы. Стоит упомянуть, что в настоящее время существует множество гибридов, так например спам-бот может обладать возможностью загружать обновления для себя и/или при наступлении некоего события переходить в прокси режим.</p>
<p>Что касается методов борьбы с подобного рода программами, то их присутствие легко детектируется с помощью любого сниффера (программы для пассивного перехвата трафика). В трафике заражённого компьютера будут преобладать SMTP и DNS.</p>
<p><img src="http://software.intel.com/file/23246" /></p>
<p>В данной ситуации можно порекомендовать проверить компьютер с помощью антивируса с последними обновлениями баз данных сигнатур вирусов.</p>
<h3 class="sectionHeading">Общие рекомендации по борьбе со спамом</h3>
<p>На сегодняшний день существует много способов борьбы со спамом, но универсального способа нет.</p>
<p>Создавать так называемые «чёрные списки», для фильтрации сообщений пришедших от определённого автора/адреса электронной почты/IP-адреса и т.п.</p>
<p>Использовать алгоритмы автоматической фильтрации сообщений/комментариев.</p>
<p>Применительно к комментариям можно выделить следующие методы:</p>
<ul>
<li>Позволять оставлять комментарии только зарегистрированным пользователям, с последующей проверкой данных комментариев. Если оставленный комментарий не соответствует каким либо критериям допустимости, характерным для каждого отдельного ресурса, то комментарий удаляется/не публикуется. Таким образом на сайте размещаются только проверенные администрацией комментарии. Данный метод может быть усилен путем разрешения оставления комментариев только «доверенными» пользователями, чьи комментарии были одобрены ранее.</li>
<li>Использовать службы аутентификации. Например такие как Google Account, «Яндекс.Паспорт» и т.д. Самым распространённым на сервис аутентификации TypeKey.<br /> <img src="http://software.intel.com/file/23247" alt="TypeKey" /> При регистрации на сайте данного сервиса, пользователь получает возможность оставлять комментарии на всех сайтах, где этот сервис поддерживается. В России используется OpenID. К плюсам использования систем аутентификации следует отнести то, что для доступа к ресурсу нужно зарегистрироваться и подтвердить свою учётную запись, что создает дополнительные трудности спамерам.</li>
<li>Изменение названий и переменных используемых в скриптах регистрации популярных движков таких как Movable Type и Wordpress. Цель – затруднить спамерам автоматическую публикацию комментариев, так как нужно определить не только новое местоположение скрипта и его название, но и названия внутренних переменных содержащих тело комментария.</li>
<li>Использование CAPTCHA – полностью автоматизированного публичного теста Тьюринга (Completely Automated Public Turing test to tell Computers and Humans Apart). Несомненно, с ним сталкивался каждый пользователь сети. Он представляет из себя сильно искаженный текст и/или циферно-буквенные комбинации. Данный метод используют все крупные интернет-сервисы, включая Google, MSN, «Яндекс», «Рамблер» и другие. Однако данный тест не является единственным. Так например вам может быть предложена картинка с искаженным текстом математической задачи, сложения целых чисел, или предложен звуковой файл содержащий простое слово, которое вы должны будете записать.</li>
</ul>
<p>Итак, потенциальной целью спамеров может стать любой. Но если не публиковать свои личные данные, адрес электронной почты, номера ICQ и т.д. то риск подвергнутся атаке будет заметно уменьшен. Также существуют рекомендации использовать несколько адресов электронной почты. Например, один для рабочей документации и важной информации, другой для личной переписки и неформального общения и т.д.</p>
<h3 class="sectionHeading">Об авторе</h3>
<p>Ирина Сергеевна Раткевич - студентка  3 курса НИЯУ (Национального Исследовательского Ядерного Университета, ранее СарФТИ) Экономико-Математического Факультета, специальность Информационные Системы в Экономике.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/spam</link>
      <pubDate>Tue, 03 Nov 2009 01:05:48 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/spam#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/spam</guid>
      <category>Академическое сообщество</category>
    </item>
    <item>
      <title>Как мы тестируем анализатор кода</title>
      <description><![CDATA[ <h2>Аннотация</h2>
<p>В статье описаны технологии тестирования, используемые при разработке   статического анализатора кода PVS-Studio. Разработчики инструмента для   программистов делятся принциами тестирования собственного программного продукта,   которые могут быть интересны разработчикам аналогичных пакетов обработки   текстовых данных или исходных кодов.</p>
<h2>Введение</h2>
<p>Занимаясь разработкой и продвижением программного продукта <a href="http://www.viva64.com/ru/pvs-studio">PVS-Studio</a>, в своих рассказах мы   огромное внимание уделяем вопросам качества программного обеспечения, процессам   разработки и принципам организации труда программистов. При этом до сих пор   закрытым оставался вопрос о том, как же собственно мы сами разрабатываем свой   программный продукт? Используем ли те технологии, рекомендации и практики, о   которых пишем в статьях? Наконец, относится ли к нам фраза "сапожник без   сапог".</p>
<p>В этой статье мы решили рассказать о том, как мы тестируем программный   продукт PVS-Studio. С одной стороны, это делается для того, чтобы убедить наших   (потенциальных) пользователей в качестве нашего инструмента. С другой стороны -   рассказать об успешном опыте применения некоторых практик.</p>
<p>PVS-Studio - это статический анализатор кода, предназначенный для   разработчиков современных ресурсоемких приложений на языках Си и Си++ (более   подробная информация дана в статье "<a href="http://www.viva64.com/art-4-1-1796251700.html">Учебное пособие по   PVS-Studio</a>" [2]). Под современными мы понимаем 64-битные и/или параллельные   приложения. Разработка таких программ имеет ряд трудностей, отличных от проблем   традиционных программ. Ведь помимо обычных и всем известных ошибок вроде   неинициализированных указателей, которые обнаруживаются любым компилятором, есть   и специфичные виды проблем.</p>
<p>Речь идет об ошибках в программах, которые проявляются при миграции 32-битных   приложений на 64-битные платформы. Или при распараллеливании кода для поддержки   многопроцессорности или многоядерности. Разрабатывать такие приложения довольно   сложно из-за недостатка инструментов, облегчающих создание 64-битных и   параллельных программ. Анализатор PVS-Studio является именно таким   инструментом.</p>
<h2>Практики тестирования, используемые при разработке PVS-Studio</h2>
<p>Разрабатывая PVS-Studio, мы используем пять основных методики   тестирования:</p>
<ol>
<li>Статический анализ кода. Странно было бы разрабатывать статический   анализатор и не использовать при этом статический анализ.</li>
<li>Юнит-тесты уровня классов, методов, функций.</li>
<li>Функциональные тесты уровня самостоятельно написанных файлов</li>
<li>Функциональные тесты уровня отдельных файлов.</li>
<li>Функциональные тесты уровня отдельных сторонних проектов и решений (projects   and solutions).</li>
</ol>
<p>Кратко опишем здесь эти методики.</p>
<p>Поскольку PVS-Studio - это статический анализатор кода, то при его разработке   мы также используем методику статического анализа кода для поиска 64-битных   проблем в анализаторе. Во-первых, это нужно для того, чтобы нас не упрекнули в   том, что мы не пользуемся своим продуктом (шутка). А во-вторых, это реально   позволяет находить ошибки до того, как их найдут пользователи.</p>
<p>Юнит-тесты уровня классов, методов функций позволяют нам быть уверенными в   том, что добавление новой функциональности не портит имеющийся код. На этом   уровне проверяются отдельные программные сущности в виде простеньких маленьких   тестов. В общем, самые обычные юнит-тесты.</p>
<p>Функциональные тесты уровня самостоятельно написанных файлов позволяют быстро   проверять, что всё, что должно диагностироваться – диагностируется как и раньше.   Все обнаруживаемые потенциальные проблемы в коде должны обнаруживаться.</p>
<p>Функциональные тесты уровня отдельных файлов позволяют убедиться, что   различные отдельные файлы с кодом полностью и без проблем проверяются   анализатором без проблем. Под проблемами здесь понимаются срабатывающие ASSERT,   неожиданные сообщения об ошибках, а также падения. Ведь анализатор кода - это   такая же программа, как и другие, и падения в ее работе, увы, не редки.</p>
<p>Функциональные тесты уровня отдельных сторонних проектов и решений позволяют   убедиться в том, что анализатор все еще умеет проверять проекты, а количество   диагностических сообщений от версии к версии изменяется контролируемо, а не   хаотично.</p>
<p>Конечно же, помимо этих методик, есть и другие стандартные подходы вроде   "зоркий глаз программиста" или "у нас завтра релиз, проверьте ЭТО", но они   показали свои недостатки и мы стараемся их не применять.</p>
<p>Теперь же расскажем об используемых приемах более подробно.</p>
<h2>Статический анализ кода, выполненный статическим анализатором кода</h2>
<p>Заголовок раздела вызывает недоумение? Кто Что же еще может выполнять   статический анализ кроме статического анализатора? Однако при разработке   инструментов для программистов всегда есть нюансы.</p>
<p>Как вы наверняка знаете, первые версии компиляторов языков программирования   редко пишутся сразу же на этих языках. Как правило, для разработки компилятора   нового языка используется совсем другой язык, являющийся стандартом на тот   момент. Это сегодня все компиляторы Си++, пишутся на Си++, а первая версия была   написана на Си.</p>
<p>Точно так же, разрабатывая первую версию статического анализатора кода, мы не   могли ее проверить. Именно поэтому первая версия нашего продукта (тогда он   назывался еще Viva64) вовсе не была 64-битной! Зато уже версия 1.10, которая <a href="http://www.viva64.com/content/PVS-Studio-help-ru/Release_history.html">появилась</a> 16 января 2007 года, то есть через 17 дней после выпуска первой версии, среди   нововведений содержала строку:</p>
<ul>
<li>С помощью анализатора Viva64 мы подготовили 64-битную версию Viva64! Но   пользователю не надо беспокоиться о выборе подходящей версии. Правильная версия   выбирается автоматически во время установки.</li>
</ul>
<p>Таким образом, как только у нас появился статический анализатор, выявляющий   проблемы 64-битного кода, так мы сразу стали проверять им наш же код.</p>
<p>Помогает ли нам статический анализ нашего же продукта? Конечно же, да. Но   опять-таки из-за нашей специфики есть нюансы. Поскольку мы сами пишем статьи про   то, как надо делать 64-битный код, то 64-битных ошибок в новом коде мы все-таки   не допускаем. Однако от применения статического анализа мы извлекаем пользу в   плане улучшения диагностики. Например, мы можем посмотреть, какие типы   синтаксических конструкций дают явно ложное срабатывание и исключить их   диагностирование.</p>
<p>Таким образом, применение статического анализа для разработки статического   анализатора оказывается полезным для нас.</p>
<h2>Юнит-тесты уровня классов, методов, функций</h2>
<p>Юнит-тесты уровня классов, методов, функций представляют собой набор тестов   для проверки отдельных логических элементов программы. Вот некоторые примеры   функциональности, на которую у нас есть юнит-тесты:</p>
<ul>
<li>применение тех или иных правил диагностики потенциально опасных   конструкций;</li>
<li>вычисление типов в выражениях, в операциях приведения типов и т.п.;</li>
<li>работа операторов (сложение, вычитание и т.п.);</li>
<li>работа механизмов загрузки и предварительной обработки файлов;</li>
<li>проверка регистрационной информации;</li>
</ul>
<p>Естественно, этими областями наши юнит-тесты не ограничиваются, но являются   показательными с точки зрения примеров.</p>
<p>Как мы пользуемся этими юнит-тестами? При исправлении ошибки в анализаторе   или при добавлении новой функциональности юнит-тесты запускаются в режиме   release-сборки. Если тесты проходят без проблем, то тесты запускаются в режиме   debug-сборки под отладчиком. Это делается для того, чтобы проверить, не   срабатывают ли ASSERT, которых в коде предостаточно. Позднее будет понятно,   почему сразу нельзя запускать debug-версию.</p>
<p>Хотя такие тесты позволяют выявить ошибки, они не являются полноценным   решением. Дело в том, что у нас в PVS-Studio очень мало юнит-тестов по сравнению   с тем, сколько юнит-тестов нужно для проверки "почти компилятора". Поэтому   достичь юнит-тестами большого покрытия в нашем случае очень сложно. Это огромная   по трудозатратам задача, которую в настоящее время реализовать нет   возможности.</p>
<p>Тем не менее, юнит-тесты уровня классов, методов, функций - это первый   уровень "обороны" в нашей системе тестирования.</p>
<h2>Функциональные тесты уровня самостоятельно написанных файлов</h2>
<p>При разработке анализатора кода важно не "потерять" диагностику тех   потенциальных ошибок, которые уже давно обнаруживаются. Это достигается за счет   функциональных тестов уровня самостоятельно написанных файлов. Абсолютно все   обнаруживаемые потенциальные проблемы в виде кода собраны в отдельные файлы. Эти   файлы размечены определенным образом. В тех строках, где анализатор кода должен   обнаружить ошибки, стоят специальные маркерные символы. Причем в этих маркерах   указано, сколько ошибок в данной строке должно быть: одна, две и т.д. Как только   из-за ошибки разработчиков что-то перестает диагностироваться, мы сразу же это   видим. Раньше в строке номер 17 выдавалось сообщение о двух ошибках, а теперь   только об одной. Или наоборот, появились лишние сообщения.</p>
<p>Такой подход очень похож по принципу работы на функциональные тесты уровня   отдельных проектов (о которых будет сказано ниже), но отличается очень высокой   скоростью работы и тем, что проверяемые файлы написаны (и размечены)   самостоятельно.</p>
<p>Кроме того, эту систему можно использовать, разумеется, и при разработке   новых диагностических сообщений. Сначала надо в файлах вручную написать код, в   котором будет диагностироваться новая ошибка, затем разметить его и можно   приступать к реализации собственно диагностики ошибки. Пока реализации нет,   система тестирования будет сообщать, что в этом месте должна быть диагностируема   ошибка, но ее нет. Как только диагностика появится, тест будет проходить   нормально.</p>
<h2>Функциональные тесты уровня отдельных файлов</h2>
<p>Проверка не отдельных классов/методов или вручную сделанных файлов, а файлов   из реальных проектов позволяет нам добиться большего покрытия кода. Четвертым   уровнем в нашей системе тестирования является именно такая проверка. В наши   тесты включены отдельные полностью препроцессированные файлы из различных   проектов: wxWidgets, fox-toolit, CImg, Lame, Boost, CxImage, FreeType и многих   других. Также сюда входят препроцессированные файлы, построенные на основе   стандартных системных заголовочных файлов (CRT, MFC и так далее).</p>
<p>После добавления новой или исправления старой функциональности программист   запускает сначала release-версию тестов, а потом debug-версию. Почему сразу не   запускать debug-версию? Очень просто, Debug-версию не запускают сразу потому,   что release-версия тестов работает одну минуту, а debug-версия - пять минут.</p>
<p>Тестирование на уровне отдельных файлов – очень мощная вещь. Она позволяет   мгновенно выявить ошибку, если в результате развития анализатора какая-то   функциональность "отвалилась". Огромное количество ошибок было не допущено   благодаря этим тестам.</p>
<h2>Функциональные тесты уровня отдельных сторонних проектов и решений</h2>
<p>Самая мощная часть нашей системы тестирования - это ее пятый уровень, который   представляет собой проверку отдельных проектов и решений (projects and   solutions). Именно благодаря этой системе каждая новая версия PVS-Studio как   минимум не хуже предыдущей.</p>
<p>Выглядит эта система следующим образом. Имеется несколько десятков проектов и   решений различных доступных в интернете программ. Например: Lame, Emule, Pixie,   Loki и другие. Каждый из этих проектов проверен с помощью PVS-Studio, результаты   проверки (в виде log-файла) сохранены. После установки новой (разрабатываемой   версии) запускается специальная разработанная нами система, которая по очереди   открывает каждый проект, проверяет его с помощью PVS-Studio, сохраняет   результаты, а затем сравнивает их с эталонными. Если есть отличия, то она их   записывает в отдельный файл (аналог стандартного diff), который легко можно   посмотреть с помощью PVS-Studio и разобраться в причине появления этих отличий.</p>
<p>Например, в новой версии PVS-Studio появилось новое диагностическое сообщение   с кодом V118. Мы запускаем систему тестирования, и она должна сообщить, что в   некоторых проектах появились сообщения V118. Затем мы вручную просматриваем все   изменения в результатах и решаем, правильно ли выдано сообщение V118.</p>
<p>Если же при этом помимо появления сообщения V118 мы видим в результатах, что   пропали некоторые сообщения V115, то это означает, что тесты показали недостаток   текущей версии программы, и она отправляется обратно на доработку. В случае   признания всех изменений справедливыми новые файлы отчетов признаются   эталонными, и сравнение выполняется уже с ними.</p>
<p>Эта система имеет и другое назначение. Поскольку продукт PVS-Studio   предназначен для работы как в Visual Studio 2005, так и в Visual Studio 2008, то   мы всегда проверяем, чтобы диагностические сообщения всегда совпадали в разных   версиях Visual Studio. То есть если, к примеру, мы получили во всех проектах 10   000 диагностических сообщений в Visual Studio 2005, то и в Visual Studio 2008 мы   должны получить ровно столько же сообщений. Когда выйдет Visual Studio 2010, мы   добавим запуск тестов и в этой среде разработки.</p>
<p>Сколько времени занимает такая проверка? На этот вопрос нет однозначного   ответа, покольку база этих тестов постоянно растет за счет новых проектов. И,   естественно, время работы постоянно увеличивается. Год назад, когда анализатор   работал только с использованием одного ядра, тесты выполнялись около часа. Затем   мы сделали возможным работу в несколько потоков, и для двухъядерной машины время   работы сократилось почти вдвое. Со временем мы добавляли все новые проекты и в   тесты. Теперь на той же двухъядерной машине эти тесты отрабатывают чуть больше,   чем за час. Все это происходит, естественно, в release-версии. В ближайшее время   мы планируем перейти на машину с четырьмя ядрами и добавить еще некоторое число   проектов. В таком случае система снова будет работать не меньше часа.</p>
<h2>Что дальше?</h2>
<p>Известно, что нет предела совершенству. Мы продолжаем развивать нашу систему   тестов по всем направлениям. Естественно, мы постоянно увеличиваем базу всех   тестов. Кроме того, нам недостает системы тестирования GUI. Ошибки в этой   системе не так критичны для анализатора кода, в том смысле, что заметить их   можно и глазами. Но в любом случае, для качественного программного продукта   необходимо тестировать и GUI. .</p>
<h2>Заключение</h2>
<p>Из данной статьи вы узнали, как мы тестируем наш статический анализатор кода   PVS-Studio. Возможно, наш опыт поможет вам внедрить подобные практики   тестирования в своих рабочих проектах. А мы надеемся, что прочитав о том, как мы   тестируем PVS-Studio, у вас возникнет желание подробнее познакомиться с нашим   инструментом.</p>
<h2>Библиографический список</h2>
<ol>
<li>Анализатор кода PVS-Studio. <a href="http://www.viva64.com/ru/pvs-studio/">http://www.viva64.com/ru/pvs-studio/</a></li>
<li>Учебное пособие по PVS-Studio. <a href="http://www.viva64.com/art-4-1-1796251700.html">http://www.viva64.com/art-4-1-1796251700.html</a></li>
</ol> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/how-we-test-code-analyzer</link>
      <pubDate>Wed, 21 Oct 2009 03:40:59 -0700</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/how-we-test-code-analyzer#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/how-we-test-code-analyzer</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Конкурс «Лето с Intel для профессионального роста»</title>
      <description><![CDATA[ <p><img height="120" width="745" src="http://software.intel.com/file/21987" alt="Конкурс проектов «Лето с Intel для профессионального роста»" /></p>
<p>Завершились летние каникулы, а вместе с ними и очередная молодежная школа-стажировка Intel. Начиная с 2000 года, и вот уже на протяжении 9 лет каждое лето лучшие молодые специалисты получали уникальную возможность проверить свои силы и знания в реальных проектах в одном из центров Intel, расположенных в Москве, Санкт-Петербурге, Нижнем Новгороде, Новосибирске и Сарове. Но этот год особенный – <a rel="nofollow" href="http://uni-schools.ru/">Летняя школа Intel</a> отметила свое десятилетие, обрела новый формат в партнерстве с ведущими вузами страны и получила поддержку местных администраций!</p>
<p>И впервые сообщество Intel® Software Network совместно с академической программой проводит конкурс проектов «Лето с Intel для профессионального роста», к участию в котором приглашаются студенты <a rel="nofollow" href="http://uni-schools.ru/">Летней школы</a> 2009 года.</p>
<p>На протяжении летних солнечных месяцев самые талантливые ребята, собранные со всей нашей необъятной страны, работали над увлекательными проектами. Надеемся, что это время не прошло бесследно и достойным дополнением к летнему отдыху стали новые знания и неоценимый опыт работы в сотрудничестве с международной компанией. Первый шаг в сторону будущей карьеры сделан, и теперь мы предлагаем сделать следующий - вспомнить самые яркие моменты лета и поделиться своими впечатлениями, полученными знаниями и достижениями, приняв участие в конкурсе. Всех представивших свои работы ждут памятные сувениры, а победителю достанется главный приз – нетбук Lenovo* IdeaPad*. Кроме того не забывайте, участие в конкурсе – это достаточно весомый пункт для вашего резюме ;)</p>
<h2 class="sectionHeading">Конкурсное задание</h2>
<p>Участникам конкурса необходимо опубликовать на сайте сообщества разработчиков программного обеспечения Intel® Software Network техническую статью с описанием хода и результатов разработанного проекта или выполненного исследования. Не забудьте включить в статью рассказ о впечатлениях от стажировки в Летней школе и ее влиянии на дальнейшие профессиональные взгляды. Это увеличит ваши шансы на победу!</p>
<p>Подробнее о задании, оформлении и порядке публикации работ читайте на страницах <a href="http://software.intel.com/ru-ru/articles/contest-summer-school-2009-rules/">официальных правил конкурса</a> «Лето с Intel для профессионального роста» и <a href="http://software.intel.com/ru-ru/articles/contest-summer-school-2009-article-requirements/">требований к статьям</a>.</p>
<h2 class="sectionHeading">Сроки и условия участия</h2>
<p>Конкурс «Лето с Intel для профессионального роста» проходит с 7 сентября по 26 октября 2009 года.<br />Cроки представления конкурсных работ истекают 6 октября 2009 года в 21:00 по московскому времени.<br />В первой половине октября 2009 года судейская комиссия определит победителей конкурса, занявших первое, второе и третье места.</p>
<p>Для участия в конкурсе необходимо:</p>
<ul>
<li><a href="https://fm1cedar.cps.intel.com/isn/registration/isnRegpage.aspx?Lang=RUS&amp;TARGET=http%3A%2F%2Fsoftware.intel.com%2Fru-ru%2F">Зарегистрироваться</a> в сообществе Intel® Software Network.</li>
<li>Заполнить учетную запись участника сообщества Intel® Software Network, включая контактную информацию: ФИО, географический адрес и телефон.</li>
<li>Опубликовать на сайте Intel® Software Network (<a href="http://software.intel.com/ru-ru/">http://software.intel.com/ru-ru/</a>) техническую статью с описанием разработанного проекта или выполненного исследования.</li>
</ul>
<p>Содержание и оформление конкурсных работ должны удовлетворять <a href="http://software.intel.com/ru-ru/articles/contest-summer-school-2009-article-requirements/">требованиям</a>, предъявляемым организаторами данного конкурса.<br />Подробнее о сроках и условиях участия читайте на странице <a href="http://software.intel.com/ru-ru/articles/contest-summer-school-2009-rules/">официальных правил</a> конкурса «Лето с Intel для профессионального роста».</p>
<h2 class="sectionHeading">Критерии и принципы оценки конкурсных работ</h2>
<p>Для определения победителей конкурса будут использованы следующие критерии оценки:</p>
<ul>
<li>Оригинальный, инновационный подход к исследованию/разработке;</li>
<li>Практические результаты, полученные в ходе работы над проектом;</li>
<li>Оформление статьи, стиль изложения, полнота предоставленных материалов;</li>
<li>Мнение сообщества, на основании количества просмотров статей и комментариев.</li>
</ul>
<p>Победителем конкурса является участник, статья которого получила наивысший суммарный рейтинг судейской комиссии с учетом мнения сообщества. <br />Подробнее о критериях оценки и принципах судейства читайте на странице <a href="http://software.intel.com/ru-ru/articles/contest-summer-school-2009-rules/">официальных правил</a> конкурса «Лето с Intel для профессионального роста».</p>
<a name="prizes" id="prizes"></a>
<h2 class="sectionHeading">Информация о призах</h2>
<p>Никто без приза не останется – всех ждут памятные сувениры. Участники, занявшие первое, второе и третье места получат:</p>
<p>Первое место:</p>
<p><img height="150" width="500" src="http://software.intel.com/file/22000" align="right" alt="Призы конкурса" /></p>
<ul type="disc">
<li>Нетбук Lenovo IdeaPad S10-2</li>
</ul>
<p>Второе место:</p>
<ul>
<li>Устройство для чтения книг PocketBook 301</li>
</ul>
<p>Третье место:</p>
<ul type="disc">
<li>Цифровая фоторамка Sony DPF–D82 Black</li>
</ul>
<a name="judges" id="judges"></a>
<h2 class="sectionHeading">Состав жюри</h2>
<p><strong>Александр Лазарев</strong>, инженер группы поддержки разработки программного обеспечения, Software Solutions Group<br />В Интел работает около 6 лет, занимается оптимизацией программного обеспечения сторонних производителей. Также работал инженером по тестированию Intel® Threading Tools на приложениях. До Интел занимался тестированием встроенного программного обеспечения средств связи Моторола и Алкатель. Закончил кафедру прикладной матемтики Физико-Механического факультета Санкт-Петебрургского Государственного Технического Университета.</p>
<p><strong>Алексей Александров</strong>, старший инженер по ПО, архитектор программных продуктов Intel для анализа производительности приложений.</p>
<p><strong>Николай Куртов</strong>, инженер-практикант по ПО. Разработчик библиотеки параллельного программирования Intel® Concurrent Collections for C++.</p>
<p><strong>Валерий Курякин</strong>, кандидат физико-математических наук. Работал в Intel менеджером по инициативам в области новых технологий и продуктов. В настоящее время занимается поиском и оценкой новых технологических и бизнес–проектов в области IT-приложений. Был руководителем и инициатором десятков проектов, принимал участие в оценке многих конкурсов.</p>
<p><strong>Максим Перминов</strong>, инженер-программист. Работает с приложениями из области HPC (High Performance Computing), занимается их адаптацией к архитектуре процессоров Интел, а также последовательной и параллельной оптимизацией.</p>
<p><strong>Дмитрий Мишура</strong>, старший инженер-программист. Область интересов: HPC, вычислительные кластеры, MPI. Занимается предпродажной поддержкой вендоров, оптимизацией ПО, бенчмаркингом.</p>
<p><strong>Андрей Марочко</strong>, в разработке ПО с 1992 года, профессионально - с 1999. Работал в различных областях - от численного анализа до сложных GUI решений и от распределенных клиент-серверных систем до написания библиотек для разработчиков. Начал активно использовать многопоточность в конце 90-х. C 2004 года – старший инженер в Интеле, сначала в отделе Threading Tools, а с 2007 - в Threading Runtimes. Основные направления деятельности - развитие функциональности планировщика задач TBB и параллельных алгоритмов, а также поддержка взаимодействия TBB c другими параллельными технологиями (OpenMP, Cilk) и с инстументами анализа корректности и производительности (Thread Checker, Thread Profiler, Parallel Amplifier).</p>
<p><strong>Андрей Чурбанов</strong>, старший инженер по программному обеспечению, работает в проекте по реализации стандарта OpenMP в компиляторе Интел.</p>
<p><strong>Роман Шарыгин</strong>, инженер программного обеспечения, Software Solutions Group. Работает в команде Performance, Analysis and Threading, которая отвечает за качество выпускаемых продуктов, как уже выпущеных (VTune, Parallel Studio), так и еще готовящихся к выпуску.</p>
<p><strong>Василий Липсюк</strong>, старший инженер ПО, работает в SSG-VCSD/CIP/IPP группе.</p>
<p><strong><a href="http://software.intel.com/ru-ru/blogs/author/dmitry-oganezov/">Дмитрий Оганезов</a></strong>, менеджер сообщества разработчиков ПО Intel Software Network.</p>
<h2 class="sectionHeading">Поддержка</h2>
<p>Если у вас появятся вопросы к организаторам конкурса, пожелания и замечания, пожалуйста, оставляйте их в <a href="http://software.intel.com/ru-ru/forums/contest-summer-school-2009/topic/68134/">специальной ветке форума</a> или в виде комментария к данной статье.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/contest-summer-school-2009</link>
      <pubDate>Tue, 20 Oct 2009 06:39:36 -0700</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/contest-summer-school-2009#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/contest-summer-school-2009</guid>
      <category>ISN General</category>
      <category>Сообщество разработчиков программного обеспечения</category>
      <category>События</category>
    </item>
    <item>
      <title>Инструмент для поиска потенциальных возможностей проведения кэш-оптимизаций</title>
      <description><![CDATA[ <h2 class="sectionHeading">Аннотация</h2>
<p>В статье описывается работа над проектом, целью которого было создание инструмента для поиска потенциальных возможностей проведения кэш-оптимизаций, состоящего из трех компонент: инструментатора бинарного кода для сбора статистики доступа к RAM, статистического анализатора данных и графического интерфейса для представления полученных результатов.</p>
<h2 class="sectionHeading">Введение</h2>
<p>Г. Маркес был прав, говоря, что самое лучшее в нашей жизни происходит неожиданно. И случайно замеченное мной на просторах Интернета приглашение в Летнюю Школу Intel - яркий тому пример. Сегодня, находясь в Иркутске и выбрав тему диплома непосредственно связанную с тематикой моего проекта в Летней Школе, я понимаю, что один месяц, проведенный на стажировке в Новосибирском Академгородке, оказал достаточно сильное влияние на мою дальнейшую деятельность. В этой статье мне бы хотелось познакомить вас с идеей проекта, рассказать о уже проделанной работе, а также о перспективах развития данной темы.</p>
<h2 class="sectionHeading">Об идее проекта</h2>
<p>«Создание инструмента для поиска потенциальных возможностей проведения кэш-оптимизаций» - таково название проекта, над которым кроме меня работал Антон Астафьев, а курировал проект ментор от Intel Дмитрий Буданов.</p>
<p>В данном случае нам нужно было рассмотреть возможность повышения производительности программы посредством увеличения эффективности доступа к памяти, зависящей от структуры кода. Реализация проекта состояла из реализации трех его компонент, а именно: инструментатора бинарного кода для сбора статистики доступа к RAM, статистического анализатора данных, графического интерфейса для представления полученных данных. В общем виде суть проекта можно представить следующей схемой:</p>
<p><img src="http://software.intel.com/file/22433" alt="Суть проекта" /></p>
<p><i>Рис. 1. Суть проекта</i></p>
<p>Говоря о структуре кода, необходимо сказать о видах существующих цикловых оптимизаций, т.к. именно циклы зачастую оказываются «узкими местами» программы и, изменяя их структуру, в большинстве случаев, можно достичь более эффективной работы программы с памятью. Ниже приведены наиболее известные цикловые оптимизации:</p>
<ul>
<li>Loop invariant code motion (вынесение инвариантов цикла) - оптимизация, которая находит и выносит за пределы цикла выражения независящие от индексных переменных цикла;</li>
<li>Loop fusion (объединение циклов) - объединение циклов, увеличивающее инструкционный параллелизм и позволяющее интенсивнее загружать вычислительные устройства процессора;</li>
<li>Loop distribution (разбиение циклов) - разбиение циклов способное улучшить производительность за счет улучшения работы с памятью;</li>
<li>Loop unrolling (развертка цикла) - техника призванная уменьшить количество условных переходов в цикле. Несколько итераций цикла объединяются в одну;</li>
<li>Loop unswitching (размыкание цикла) - оптимизация, состоящая в вынесении условия за пределы цикла и дублирования тела цикла с помещением соответствующих вариантов в соответствующие ветви условия;</li>
<li>Loop interchange (Перестановка циклов) - оптимизация, при которой меняется порядок циклов;</li>
<li>Loop tiling (разбиение цикла на блоки) - метод оптимизации состоящий в разбиении пространства итерирования исходного цикла на небольшие блоки меньшего размера, что позволяет хранить используемые в этих небольших блоках данные в кэше полностью для их неоднократного использования в процессе выполнения блока.</li>
</ul>
<p>Таким образом, выявив с помощью статистического анализатора и представив визуально наиболее «проблемные» участки программного кода, можно предположить, что эти участки следует рассмотреть на предмет применения одной или ряда цикловых оптимизаций. Не следует забывать, что результаты, полученные при компиляции программы без оптимизаций компилятора и с ними, будут отличаться друг от друга, что в свою очередь позволяет использовать инструмент при тестировании компиляторов.</p>
<h2 class="sectionHeading">Средства, способы и алгоритмы реализации</h2>
<p>Для реализации первой компоненты нам понадобилась утилита Pin. <a target="_blank" href="http://www.pintool.org/" title="Pin">Pin</a> - Dynamic Binary Instrumentation Tool, утилита, которая внедряется в анализируемый процесс непосредственно перед стартом и позволяет отслеживать выполнение практически любых инструкций, предоставляет API доступа к содержимому регистров, контексту выполнения программы, символьной и отладочной информации. В нашем случае, мы использовали API, позволяющие инструментировать инструкции доступа к памяти.</p>
<p>Таким образом, в БД записывался адрес инструкции программы, происходящей в каждый n-ый момент времени, адрес ячейки памяти, к которой она обращалась и тип доступа. Т.к. при выполнении даже самой простой программы количество происходящих событий измеряется тысячами, то в качестве параметра n задавался шаг, с которым считывалась необходимая нам информация.</p>
<p>Что касается БД, то мы использовали <a target="_blank" href="http://www.sqlite.org/ ">SQLite</a> - свободно распространяемую реляционную базу данных, не использующую парадигму «клиент-сервер», а основанную на встраиваемом движке. SQLite хранит всю базу данных (включая определения, таблицы, индексы и данные) в единственном стандартном файле на том компьютере, на котором исполняется программа.</p>
<p>Наиболее сложным и самым важным оказалось создание статистического анализатора полученных данных, функция которого заключалась в выявлении областей (кластеров) с наибольшей плотностью обращения инструкций к сегменту памяти. В итоге нами были предложены следующие реализации:</p>
<p><b>Реализация №1:</b> Определение границ кластеров по каждому измерению с помощью фильтра LoG, фильтрация по плотности наполнения областей.</p>
<p>Фильтр LoG (Laplacian of Gaussian) - сглаживание ряда данных по методу Гаусса, взятие второй производной и определение границ кластера по смене её знака. Чтобы применить фильтр формируются временные таблицы следующей структуры: адрес инструкции, число её повторений (для выявления диапазонов адресов инструкций) и адрес ячейки памяти, число обращений к этой ячейки (для выявления диапазонов адресов ячеек памяти).</p>
<p><b>Реализация №2:</b> Сортировка собранной статистики пространственным деревом поиска (окто-деревом), фильтрация по плотности и восходящее объединение кластеров.</p>
<p>Кластеризацию на окто-дереве можно описать следующими характеристиками:</p>
<ul type="disc">
<li>3D пространство данных;</li>
<li>Равномерное распределение по каждому измерению - O(n);</li>
<li>Заполнение дерева - O( n x Log<sub>8</sub>n ) ~ 7n;</li>
<li>Автоматическая балансировка загрузки памяти;</li>
<li>Связная структура для быстрого объединения;</li>
<li>Гибкое распараллеливание;</li>
<li>Использование гипотезы λ-компактности: критерии одинаковой плотности и близости объемов.</li>
</ul>
<p>Результат обеих реализаций сведен к единому формату - списку кластеров. Кластер представляет собой пересечене диапазонов времени, адресов инструкций и адресов данных.</p>
<p>Надо сказать, что вопрос наиболее эффективного метода обработки данной статистики еще не закрыт и является основным направлением нашего дальнейшего исследования в этом проекте. В рамках Летней Школы мы представили результаты, полученные методами, описанными выше. Графический интерфейс был разработан на языке Java с помощью библиотек Swing и <a target="_blank" href="http://www.jfree.org/jfreechart/" title="JfreeChart">JfreeChart</a>, в среде NetBeans 6.5.</p>
<h2 class="sectionHeading">О проблемах</h2>
<p>Думаю, было бы странно, если при работе над проектом не возникло бы ни одного затруднения. В этом проекте мне пришлось впервые столкнуться с достаточно большими объемами данных. Индексация и обработка баз данных занимала довольно много времени на ПК средней производительности. Благодаря тому, что у нас был доступ к использованию кластера, нам удалось в разы сократить время работы инструмента. Кроме того, из-за большого объема данных (миллионы записей) терялась точность выявления проблемных областей. Объем данных в свою очередь был связан с выбором шага инструментации. В итоге было принято решение, менять шаг инструментации в соответствии с размером исследуемого кода.</p>
<h2 class="sectionHeading">Результаты</h2>
<p>Исследовав предметную область, выбрав средства реализации и устранив возникшие проблемы, мы перешли к реализации задуманного. Благодаря организованной работе команды (Антон занимался реализацией первого компонента и метода, основанного на окто-деревьях, а я реализацией третьего компонента и метода, использующего фильтр LoG) и грамотному управлению проектом со стороны нашего куратора, был создан инструмент, архитектура которого представлена на рис.2.</p>
<p><img src="http://software.intel.com/file/22435" alt="Архитектура инструмента" /></p>
<p><i>Рис. 2. Архитектура инструмента</i></p>
<p>На сегодня, так скажем, в первой версии программы, инструментатор бинарного кода отделен от статистического анализатора и графического интерфейса. То есть инструмент состоит из двух частей. На вход первой, консольной части, подается исполняемый бинарный код, на выходе формируется файл базы данных, содержащий необходимые для статистического анализатора данные и используемый во второй части. Во второй части сначала выбирается файл базы данных, затем метод кластеризации. Результаты формируются в виде таблицы кластеров со значимой плотностью, диаграмм диапазонов адресов памяти и адресов инструкций и графика пересечений этих диапазонов.</p>
<p>Ниже вы видите график пересечений диапазонов и общий вид графического интерфейса.</p>
<p><img src="http://software.intel.com/file/23142" alt="plot.jpg" title="plot.jpg" /></p>
<p><i>Рис. 3. График пересечений диапазонов</i></p>
<p>На графике по оси Х указаны адреса ячеек памяти, а по оси Y адреса инструкций. Синим цветом показано, что два диапазона адресов инструкций обращаются к одному и тому же диапазону адресов памяти. Данное пересечение указывает на потенциальную «проблемную» область.</p>
<p>К сожалению, нам не хватило времени протестировать инструмент на реальных приложениях, на скриншоте представлены результаты, полученные при работе с приложениями тестового набора Polyhedron.</p>
<p><img src="http://software.intel.com/file/23141" alt="gui.jpg" title="gui.jpg" /></p>
<p><i>Рис. 4. Общий вид графического интерфейса</i></p>
<h2 class="sectionHeading">Перспективы</h2>
<p>Я не случайно написала выше именно о первой версии программы. В рамках Летней Школы был заложен хороший фундамент для дальнейшего исследования данной предметной области и работы над проектом, поэтому останавливаться на достигнутом мы не собираемся. Развивать проект предполагается в следующих направлениях:</p>
<ul type="disc">
<li>Аналитическая обработка данных. Дальнейшее исследование предметной области и методов анализа данных необходимо для создания более эффективного статистического анализатора.</li>
<li>Визуализация в 3D. Для представления общей картины нужно будет сформировать куб, измерениями которого будут: адрес инструкции, адрес ячейки памяти, момент времени. Таким образом, интересующие нас фрагменты кода будут представлены в виде наиболее плотных областей в этом кубе. Тип доступа предполагается выделять цветом. </li>
<li>Объединение компонентов. Следует объединить инструментатор бинарного кода со статистическим анализатором и графическим интерфейсом пользователя, что возможно повлечет за собой незначительные изменения в структуре базы данных.</li>
<li>Экспертная система для помощи в выявлении потенциальных возможностей кэш-оптимизаций. На данном этапе инструмент предлагает в табличном и графическом виде только факты нахождения проблемных областей, и пользователь сам должен сделать выводы о том, какие корректировки можно провести в структуре кода. Было бы неплохо создать модуль, действующий на основе базы знаний о возможных оптимизациях.</li>
<li>Тестирование на реальных приложениях. Необходимо уделить больше времени тестированию инструмента, провести сравнительные анализы выполнения на разных аппаратных и программных платформах.</li>
</ul>
<h2 class="sectionHeading">Заключение</h2>
<p>Как видите, впереди много интересной работы. Возможно, пройдет время, и на сайте сообщества появится статья о второй версии инструмента, а пока я прощаюсь с вами. Благодарю за внимание.</p>
<h2 class="sectionHeading">Об авторе и летней школе Intel</h2>
<p>Жданова Наталия Юрьевна, студентка Байкальского Государственного Университета Экономики и Права ( факультет Экономической Кибернетики), проходила стажировку в рамках Летней Школы Intel в Новосибирске. Для меня стажировка в Летней Школе стала самым ярким событием этого лета. Интенсивная работа, интереснейшие лекции и тренинги, общение с единомышленниками, экскурсии - четыре сумасшедших недели за которые я расширила свой кругозор, нашла новых друзей, получила опыт работы в команде, изменила взгляд на науку и многое другое. Если бы стажировка длилась всё лето, я бы не задумываясь провела его с Intel, потому что как оказалось, лето и Intel очень даже совместимы!</p>
<h2 class="sectionHeading">Используемая литература и ссылки на ресурсы</h2>
<ol>
<li>Презентация «Проект №5: Создание инструмента для поиска потенциальных возможностей проведения кэш-оптимизаций» Астафьев А., Жданова Н.</li>
<li>Презентация «Современные оптимизирующие компиляторы» Ануфриенко А.</li>
<li><a target="_blank" href="http://www.ixbt.com/soft/intel-parallel-inspector.shtml">http://www.ixbt.com/soft/intel-parallel-inspector.shtml</a> - «Intel Parallel Inspector - поиск ошибок доступа к памяти» В. Цымбал.</li>
<li><a target="_blank" href="http://en.wikipedia.org/wiki/Loop_optimization">http://en.wikipedia.org/wiki/Loop_optimization</a> - Loop optimization.</li>
<li><a target="_blank" href="http://www.pintool.org/">http://www.pintool.org/</a> - сайт, посвященный утилите Pin(Dynamic Binary Instrumentation Tool.</li>
<li><a target="_blank" href="http://www.sqlite.org/">http://www.sqlite.org/</a> - официальный сайт БД SQLite.</li>
<li><a target="_blank" href="http://www.jfree.org/jfreechart/">http://www.jfree.org/jfreechart/</a> - официальный сайт графической библиотеки Jfreechart.</li>
</ol> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/potential_opportunities_of_cache_optimizations</link>
      <pubDate>Mon, 19 Oct 2009 06:40:46 -0700</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/potential_opportunities_of_cache_optimizations#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/potential_opportunities_of_cache_optimizations</guid>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
  </channel></rss>