<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated on Thu, 09 Feb 2012 21:24:18 -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/</link>
    <description></description>
    <language>ru-ru</language>
    <item>
      <title>Не зная брода, не лезь в воду. Часть вторая.</title>
      <description><![CDATA[ <p>В этот раз я хочу поговорить о функции printf. Все наслышаны об уязвимостях в программах, и что функции наподобие printf объявлены вне закона. Но одно дело знать, что лучше не использовать эти функции. А совсем другое - понять почему. В этой статье я опишу две классических уязвимости программ, связанных с printf. Хакером после этого вы не станете, но, возможно, по-новому взгляните на свой код. Вдруг, вы реализуете аналогичные уязвимые функции, даже не подозревая об этом.</p>
<p><b>СТОП.</b> Подожди читатель, не проходи мимо. Я знаю, что ты увидел слово printf. И уверен, что автор статьи сейчас расскажет банальную историю о том, что функция не контролирует типы передаваемых аргументов. Нет! Статья будет не про это, а именно про уязвимости. Заходи почитать.</p>
<p>Предыдущая заметка находится здесь: <a href="http://www.viva64.com/ru/b/0127/">Часть первая</a>.</p>
<h2>Введение</h2>
<p>Взглянем на вот эту строчку:</p>
<pre name="code" class="cpp">printf(name);</pre>
<p>Она кажется простой и безобидной. А между тем, в ней скрывается как минимум два способа, чтобы атаковать программу.</p>
<p>Начнем статью с демонстрационного примера, где есть эта строчка. Код может показаться вам странноватым. Так оно и есть. Оказалось не так просто написать программу, чтобы потом её атаковать. Дело в оптимизации, которую производит компилятор. Получается, что если написать слишком простую программу, то компилятор создает такой код, что ломать там нечего. Он использует регистры, а не стек для хранения данных, встраивает функции и тому подобное. Можно написать код с лишними действиями и циклами, чтобы компилятору не хватило свободных регистров, и он начал помещать данные в стек. К сожалению, пример получается слишком большой и запутанный. Про всё это можно написать отдельную детективную историю, но не будем.</p>
<p>Представленный пример является компромиссом между сложностью и необходимостью не дать компилятору "схлопнуть в ничто" слишком простой код. Признаюсь, немного я себе всё равно помог. Я отключил некоторые виды оптимизации, используемые в Visual Studio 2010. Во-первых, был отключен ключ /GL (Whole Program Optimization). Во–вторых, я использовал атрибут __declspec(noinline).</p>
<p>Прошу прощение за такое длинное вступление. Хотелось пояснить неуклюжесть программного кода. И сразу пресечь дискуссии на тему, что этот код можно написать лучше. Я знаю, что можно. Но не получается сделать код одновременно и коротким, и чтобы можно было показать уязвимость.</p>
<h2>Демонстрационный пример</h2>
<p>Полный код и проект для Visual Studio 2010 доступен <a href="http://www.viva64.com/external-pictures/printf_demo.zip">здесь</a>.</p>
<pre name="code" class="cpp">const size_t MAX_NAME_LEN = 60;
enum ErrorStatus {
  E_ToShortName, E_ToShortPass, E_BigName, E_OK
};

void PrintNormalizedName(const char *raw_name)
{
  char name[MAX_NAME_LEN + 1];
  strcpy(name, raw_name);

  for (size_t i = 0; name[i] != '\0'; ++i)
    name[i] = tolower(name[i]);
  name[0] = toupper(name[0]);

  printf(name);
}

ErrorStatus IsCorrectPassword(
  const char *universalPassword,
  BOOL &amp;retIsOkPass)
{
  string name, password;
  printf("Name: "); cin &gt;&gt; name;
  printf("Password: "); cin &gt;&gt; password;
  if (name.length() &lt; 1) return E_ToShortName;
  if (name.length() &gt; MAX_NAME_LEN) return E_BigName;
  if (password.length() &lt; 1) return E_ToShortPass;

  retIsOkPass = 
    universalPassword != NULL &amp;&amp;
    strcmp(password.c_str(), universalPassword) == 0;
  if (!retIsOkPass)
    retIsOkPass = name[0] == password[0];

  printf("Hello, ");
  PrintNormalizedName(name.c_str());

  return E_OK;
}

int _tmain(int, char *[])
{
  _set_printf_count_output(1);
  char universal[] = "_Universal_Pass_!";
  BOOL isOkPassword = FALSE;
  ErrorStatus status =
    IsCorrectPassword(universal, isOkPassword);
  if (status == E_OK &amp;&amp; isOkPassword)
    printf("\nPassword: OK\n");
  else
    printf("\nPassword: ERROR\n");
  return 0;
}</pre>
<p>Функция _tmain() вызывает функцию IsCorrectPassword(). Если пароль верен или если он совпадает с магическим словом "_Universal_Pass_!", то программа выводит строку "Password: OK". Целью атак будет добиться, чтобы программа выводила именно эту строку.</p>
<p>Функция IsCorrectPassword() запрашивает у пользователя имя и пароль. Пароль считается корректным, если он совпадает с переданным в функцию магическим словом. Также он корректен, если первая буква пароля совпадает с первой буквой имени.</p>
<p>Вне зависимости от того, введен правильный пароль или нет, программа приветствует пользователя. Для этого вызывается функция PrintNormalizedName().</p>
<p>В функции PrintNormalizedName() всё самое интересное. Именно в ней, находится обсуждаемый "printf(name);". Подумайте, как с помощью этой строчки можно обмануть программу. Если знаете как, то дальше можно не читать.</p>
<p>Что делает функция PrintNormalizedName()? Она печатает имя, сделав первую букву заглавной, а остальные маленькими. Например, если ввести имя "andREy2008", то она распечатает "Andrey2008".</p>
<h2>Первая атака</h2>
<p>Предположим мы не знаем правильный пароль. Но знаем, что где-то есть некий магический пароль. Попробуем его поискать, используя printf(). Если адрес этого пароля есть где-то в стеке, то у нас есть шанс на успех. Есть идеи, как увидеть этот пароль на экране?</p>
<p>Даю подсказку. Функция printf() относится к семейству функций с переменным количеством аргументов. Работают такие функции так. В стек записывается произвольное количество данных. Функция printf() не знает, сколько данных записано в стек и какой у них тип. Она руководствуется исключительно строкой форматирования. Если написано "%d%s", то значит, из стека следует извлечь одно значение типа int и один указатель. Так как функция printf() не знает, сколько аргументов ей передали, то она может заглянуть глубже в стек и распечатать данные, которые никакого к ней отношения не имеет. Как правило, это приводит к <a href="http://www.viva64.com/ru/t/0063/">access violation</a> или к распечатке мусора. Однако, этим мусором можно воспользоваться.</p>
<p>Рассмотрим, как может выглядеть стек в момент, когда мы вызываем функцию printf():</p>
<p><img alt="Рисунок 1. Схематическое расположение данных в стеке." src="http://www.viva64.com/external-pictures/Wade_not_in_unknown_waters_2/image1_r.png" /></p>
<p>Рисунок 1. Схематическое расположение данных в стеке.</p>
<p>Вызов функции "printf(name);" имеет только один аргумент, который является строкой форматирования. Это значит, что если мы вместо имени мы введём "%d", то распечатаем данные, которые лежат в стеке до адреса возврата в функцию PrintNormalizedName(). Попробуем:</p>
<p>Name: %d</p>
<p>Password: 1</p>
<p>Hello, 37</p>
<p>Password: ERROR</p>
<p>Пока данное действие малоосмысленно. Как минимум, вначале мы должны распечатать адреса возврата и всё содержимое буфера char name[MAX_NAME_LEN + 1];, который тоже расположен в стеке. И только потом, возможно, мы доберемся до чего-то интересного.</p>
<p>Если злоумышленник не имеет возможности дизассемблировать или отладить программу, то ему сложно понять, найдет он что-то в стеке или нет. Тем не менее, он может действовать следующим образом.</p>
<p>В начале, ввести: "%s". Потом ввести "%x%s". Потом ввести "%x%x%s" и так далее. Этим хакер будет перебирать по-очереди данные в стеке, и пытаться распечатать их как строку. Здесь ему помогает то, что все данные в стеке выровнены, как минимум по границе 4 байта.</p>
<p>Если честно, действуя так, у нас ничего не получится. Мы превысим лимит в 60 символов, так и не распечатав ничего полезного. На помощь нам придет "%f", который предназначен для печати значений типа double. Следовательно, с его помощью мы сможем двигаться по стеку сразу по 8 байт.</p>
<p>И вот она - долгожданная строчка:</p>
<p>%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%x(%s)</p>
<p>Результат:</p>
<p><img alt="Рисунок 2. Распечатка пароля. Нажмите на рисунок для увеличения." src="http://www.viva64.com/external-pictures/Wade_not_in_unknown_waters_2/image3.png" /></p>
<p>Рисунок 2. Распечатка пароля. Нажмите на рисунок для увеличения.</p>
<p>Попробуем эту строчку в качестве волшебного пароля:</p>
<p>Name: Aaa</p>
<p>Password: _Universal_Pass_!</p>
<p>Hello, Aaa</p>
<p>Password: OK</p>
<p>Ура! Мы смогли найти и вывести на экран приватные данные, к которым программа не планировала дать нам доступ. Причем, обратите внимание, для этого нет необходимости иметь доступ к самому двоичному коду программы. Достаточно усердия и настойчивости.</p>
<h2>Выводы по первой атаке</h2>
<p>Подобный способ получения приватных данных следует обдумать более широко. При разработке программ, содержащих функции с переменным количеством аргументов подумайте, существуют ли ситуации, когда через них могут утечь данные во внешний мир. Это может быть лог-файл, пакет, передаваемый по сети, и так далее.</p>
<p>В рассмотренном случае атака стала возможна из-за того, что на вход функции printf() поступает строка, которая может содержать управляющие команды. Чтобы этого избежать, было достаточно написать так:</p>
<pre name="code" class="cpp">printf("%s", name);</pre>
<h2>Вторая атака</h2>
<p>Вы знаете, что функция printf() может модифицировать память? Скорее всего, вы про это читали, но забыли. Речь идет о спецификаторе "%n". Он позволяет записать по указанному адресу количество символов, которые уже распечатала функция printf().</p>
<p>Если честно, атака, основанная на спецификаторе "%n" носит исключительно исторический характер. Начиная с Visual Studio 2005 возможность использования "%n" по умолчанию отключена. Чтобы провести эту атаку мне пришлось явно разрешить этот спецификатор. Вот это магическое действие:</p>
<pre name="code" class="cpp">_set_printf_count_output(1);</pre>
<p>Чтобы стало понятнее, приведу пример использования "%n":</p>
<pre name="code" class="cpp">int i;
printf("12345%n6789\n", &amp;i);
printf( "i = %d\n", i );</pre>
<p>Вывод программы:</p>
<p>123456789</p>
<p>i = 5</p>
<p>Как добраться до нужного указателя, находящегося в стеке, мы уже узнали. А теперь у нас в руках есть инструмент, который позволяет модифицировать память по этому указателю.</p>
<p>Конечно, пользоваться этим неудобно. Во-первых, мы можем записать только сразу 4 байта (размер типа int). Если нам нужно большое число, то вначале функция printf() будет должна вывести очень много символов. Чтобы этого не делать, может помочь спецификатор "%00u". Спецификатор влияет на значение текущего количества выведенных байт. Подробнее вникать в тонкости не будем.</p>
<p>В нашем случае всё проще. Нам достаточно записать в переменную isOkPassword любое значение, неравное 0. Адрес этой переменной передаются в функцию IsCorrectPassword(), а значит, находится где-то в стеке. Пусть вас не смущает, что переменная передается как ссылка. На низком уровне, ссылка является обыкновенным указателем.</p>
<p>Вот строка, которая позволит нам модифицировать переменную IsCorrectPassword:</p>
<p>%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f%f %n</p>
<p>Спецификатор "%n" не учитывает количество символов, выведенных с помощью таких спецификаторов, как "%f". Поэтому, перед "%n" поставим один пробел, чтобы записать в isOkPassword значение 1.</p>
<p>Пробуем:</p>
<p><img alt="Рисунок 3. Запись в память. Нажмите на рисунок для увеличения." src="http://www.viva64.com/external-pictures/Wade_not_in_unknown_waters_2/image5.png" /></p>
<p>Рисунок 3. Запись в память. Нажмите на рисунок для увеличения.</p>
<p>Впечатляет? Но это ещё далеко не всё. Можно произвести запись почти по произвольному адресу. Если выводимая строка находится в стеке, то мы можем дойти до нужных символов и использовать их как адрес.</p>
<p>Например, мы можем написать строку, содержащую подряд символы с кодами 'xF8', 'x32', 'x01', 'x7F'. Получается, что в строке есть жестко закодированное число, которое эквивалентно значению 0x7F0132F8. В конце мы поставим спецификатор "%n". Используя "%x" или другие спецификаторы, мы можем добраться до закодированного числа 0x7F0132F8 и записать количество выведенных символов по этому адресу. У такого способа есть ограничения, но он всё равно очень любопытен.</p>
<h2>Выводы по второй атаке</h2>
<p>Можно сказать, что атака второго рода сейчас вряд ли возможна. Как видите, поддержка спецификатора "%n" в современных библиотеках по умолчанию выключена. Однако можно создать свой самодельный механизм, который будет предрасположен данному виду уязвимости. Будьте аккуратны, когда введенные извне данные, управляют тем, что и куда записать в память.</p>
<p>Конкретно в этом случае, проблемы опять не возникнет, если написать так:</p>
<pre name="code" class="cpp">printf("%s", name);</pre>
<h2>Общие выводы</h2>
<p>Здесь рассмотрено только два простых примера уязвимости. Конечно, их существует намного больше. Здесь не делается попытка описать или хотя бы перечислить их. В статье планировалось показать, что опасность может представлять даже такая простая конструкция как printf(name).</p>
<p>Отсюда следует важный вывод. Если вы не специалист по безопасности, то лучше следовать всем рекомендациям, о которых пишут. Суть рекомендаций бывает слишком тонка, чтобы оценить весь спектр угроз самостоятельно. Ведь вы наверняка читали, что printf() опасная функция. Но я уверен, что многие, из читающих эту статью, впервые узнали о глубине кроличьей норы.</p>
<p>Если вы пишите приложение, которое потенциально может служить объектом атаки, соблюдайте максимальную аккуратность. То, что на ваш взгляд является совершенно безобидным кодом, может содержать уязвимость. Если вы не видите в коде подвоха, это не означает, что его нет.</p>
<p>Соблюдайте все рекомендации компилятора об использовании обновленных версий строковых функций. Имеется в виду, использование sprintf_s вместо sprintf и так далее.</p>
<p>Ещё лучше - вообще откажитесь от низкоуровневой работы со строками. Эти функции - наследие языка Си. Сейчас есть std::string. Есть безопасные способы формирования строк, такие как boost::format или std::stringstream.</p>
<p><b>P.S.</b> Кто-то, прочитав вывод, сказал - "это и так было понятно". Но будьте честны. До прочтения этой статьи вы знали и помнили о том, что printf() может писать в память? А ведь это является большой уязвимостью. По крайней мере, являлось таковой раньше. Сейчас есть другие, не менее коварные.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-two-ru/</link>
      <pubDate>Fri, 03 Feb 2012 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-two-ru/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-two-ru/</guid>
      <category>ISN General</category>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
    <item>
      <title>Не зная брода, не лезь в воду. Часть первая.</title>
      <description><![CDATA[ <p>Захотелось написать несколько небольших заметок о том, как программисты на Си/Си++ играют с огнём, не подозревая об этом. Первая заметка будет про попытки явно вызвать конструктор.</p>
<p>Программисты - ленивые существа. Поэтому норовят решить задачу минимальным количеством кода. Это похвальное и хорошее стремление. Главное не увлечься процессом и вовремя остановиться.</p>
<p>Например, программистам бывает лень создавать единую функцию инициализации в классе, чтобы затем вызывать её из разных конструкторов. Программист думает: "Зачем мне лишняя функция? Я лучше вызову один конструктор из другого". К сожалению, даже эту простую задачу программисту удается решить не всегда. Для выявлений таких неудачных попыток я как раз сейчас реализовываю в <a href="http://www.viva64.com/ru/pvs-studio/">PVS-Studio</a> новое правило. Вот, пример кода, который  я обнаружил в проекте eMule:</p>
<pre name="code" class="cpp">class CSlideBarGroup
{
public:
  CSlideBarGroup(CString strName,
    INT iIconIndex, CListBoxST* pListBox);
  CSlideBarGroup(CSlideBarGroup&amp; Group);
  ...
}

CSlideBarGroup::CSlideBarGroup(CSlideBarGroup&amp; Group)
{
  CSlideBarGroup(
    Group.GetName(), Group.GetIconIndex(), Group.GetListBox());
}</pre>
<p>Рассмотрим внимательнее реализацию последнего конструктора. Программист решил, что код</p>
<pre name="code" class="cpp">CSlideBarGroup(
  Group.GetName(), Group.GetIconIndex(), Group.GetListBox());</pre>
<p>просто вызывает другой констурктор. Ничего подобного. Здесь создается и тут же уничтожается новый неименованный объект типа CSlideBarGroup.</p>
<p>Получается, что программист действительно вызвал другой конструктор. Вот только сделал он совсем не то, что задумал. Поля класса останутся неинициализированными.</p>
<p>Такие ошибки, это только половина беды. Некоторые знают, как все-таки действительно вызвать другой конструктор. И вызывают. Лучше бы они не знали, как это делается. :)</p>
<p>Например, приведенный код, можно было бы переписать так:</p>
<pre name="code" class="cpp">CSlideBarGroup::CSlideBarGroup(CSlideBarGroup&amp; Group)
{
  this-&gt;CSlideBarGroup::CSlideBarGroup(
    Group.GetName(), Group.GetIconIndex(), Group.GetListBox());
}</pre>
<p>или так:</p>
<pre name="code" class="cpp">CSlideBarGroup::CSlideBarGroup(CSlideBarGroup&amp; Group)
{
  new (this) CSlideBarGroup(
    Group.GetName(), Group.GetIconIndex(),
    Group.GetListBox());
}</pre>
<p>Теперь действительно один конструктор для инициализации данных вызывает другой конструктор.</p>
<p>Если увидите, программиста, который так делает, отвесьте ему один шелбан в лоб от себя и один от меня лично.</p>
<p><b>Приведенные примеры являются очень опасным кодом, и нужно хорошо понимать, как они работают!</b></p>
<p>Из-за мелочной оптимизации (лень писать отдельную функцию), этот код может нанести больше вреда, чем пользы. Рассмотрим подробнее, почему иногда подобные конструкции работают, но чаще нет.</p>
<pre name="code" class="cpp">class SomeClass
{
  int x,y;
public:
  SomeClass() { new (this) SomeClass(0,0); }
  SomeClass(int xx, int yy) : x(xx), y(yy) {}
};</pre>
<p>Этот код будет корректно работать. Код безопасен и работает, так как класс содержит простые типы данных и не наследуется от других классов. В этом случае двойной вызов конструктора ничем не грозит.</p>
<p>Рассмотрим другой код, где явный вызов конструктора приводит к ошибке (пример взят из <a href="http://www.viva64.com/go.php?url=790">дискуссии</a> на сайте StackOverflow):</p>
<pre name="code" class="cpp">class Base 
{ 
public: 
 char *ptr; 
 std::vector vect; 
 Base() { ptr = new char[1000]; } 
 ~Base() { delete [] ptr; } 
}; 
 
class Derived : Base 
{ 
  Derived(Foo foo) { } 
  Derived(Bar bar) { 
     new (this) Derived(bar.foo); 
  } 
}</pre>
<p>Когда мы вызываем конструктор "new (this) Derived(bar.foo);", объект Base уже создан и поля инициализированы. Повторный вызов конструктора приведет к двойной инициализации. В 'ptr' запишем указатель на вновь выделенный участок памяти. В результате получаем утечку памяти. К чему приведет двойная инициализация объекта типа std::vector, вообще предсказать сложно. Ясно одно. Такой код недопустим.</p>
<h2>Вывод</h2>
<p>Явный вызов конструктора требуется только в крайне редких случаях. В обычном программировании, явный вызов конструктора, как правило, появляется из-за желания сокращения размера кода. Не надо этого делать! Создайте обыкновенную функцию инициализации.</p>
<p>Вот как должен выглядеть правильный код:</p>
<pre name="code" class="cpp">class CSlideBarGroup
{
  void Init(CString strName, INT iIconIndex,
            CListBoxST* pListBox);
public:
  CSlideBarGroup(CString strName, INT iIconIndex,
                 CListBoxST* pListBox)
  {
    Init(strName, iIconIndex, pListBox);
  }
  CSlideBarGroup(CSlideBarGroup&amp; Group)
  {
    Init(Group.GetName(), Group.GetIconIndex(),
         Group.GetListBox());
  }
  ...
};</pre>
<h2>P.S. Явный вызов одного конструктора из другого в C++11 (делегация)</h2>
<p>Новый стандарт С++11 позволяет вызывать одни конструкторы класса из других (так называемая делегация). Это позволяет писать конструкторы, использующие поведение других конструкторов без внесения дублирующего кода. Пример корректного кода:</p>
<pre name="code" class="cpp">class MyClass {
  std::string m_s;
public:
    MyClass(std::string s) : m_s(s) {}
    MyClass() : MyClass("default") {}
};</pre> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-one-ru/</link>
      <pubDate>Fri, 27 Jan 2012 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-one-ru/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/wade-not-in-unknown-waters-part-one-ru/</guid>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
    <item>
      <title>Как уменьшить вероятность ошибки на этапе написания кода. Заметка N4</title>
      <description><![CDATA[ <img alt="PVS-Studio vs Firefox" src="http://software.intel.com/file/40385" align="left" />
<h2>Аннотация</h2>
<p>Это уже четвертая заметка, где я хочу поделиться полезными наблюдениями о паттернах ошибок и том, как можно с ними бороться. В этот раз я затрону такую тему, как обработка редких и аварийных ситуаций в программах. Рассматривая множество программ, я пришел к выводу, что код обработки ошибок в Си/Си++ программах - одно из самых ненадежных мест.</p>
<p>К чему приводят такие дефекты? Программа, вместо того, чтобы выдать сообщение "файл X не найден", падает и заставляет пользователя гадать, что он не так делает. Программа для работы с базой данных выводит невразумительное сообщение, вместо того, чтобы сообщить, что неверно заполнено одно из полей. Попробуем сразиться с этой разновидностью ошибок, которые досаждают нашим пользователям.</p>
<h2>Введение</h2>
<p>Вначале информация для читателей, которые не знакомы с моими предыдущими заметками. Их можно найти здесь:</p>
<ul>
<li><a href="http://www.viva64.com/ru/a/0070/">Заметка N1</a> [Miranda IM];</li>
<li><a href="http://www.viva64.com/ru/a/0072/">Заметка N2</a> [Chromium, Return to Castle Wolfenstein и т.д.];</li>
<li><a href="http://www.viva64.com/ru/a/0075/">Заметка N3</a> [Qt SDK].</li>
</ul>
<p>Как всегда я буду не абстрактен, а начну с примеров. В этот раз примеры будут взяты из исходного кода <a href="http://www.viva64.com/go.php?url=762">Firefox</a>. Я постараюсь продемонстрировать, что даже в качественном и известном приложении с кодом для обработки ошибок, всё обстоит не самым лучшим образом. Дефекты были найдены мной с помощью анализатора <a href="http://www.viva64.com/ru/pvs-studio/">PVS-Studio</a> 4.50.</p>
<h2>Примеры ошибок</h2>
<p><b>Пример N1. Неполноценная проверка целостности таблицы</b></p>
<pre name="code" class="cpp">int  AffixMgr::parse_convtable(..., const char * keyword)
{
  ...
  if (strncmp(piece, keyword, sizeof(keyword)) != 0) {
      HUNSPELL_WARNING(stderr,
                       "error: line %d: table is corrupt\n",
                       af-&gt;getlinenum());
      delete *rl;
      *rl = NULL;
      return 1;
  }
  ...
}</pre>
<p>Диагностика PVS-Studio: V579 The strncmp function receives the pointer and its size as arguments. It is possibly a mistake. Inspect the third argument.  affixmgr.cpp 3708</p>
<p>Здесь сделана попытка проверить целостность таблицы. К сожалению, эта проверка может сработать, а может и не сработать. Для вычисления длины ключевого слова используют оператор sizeof(), что естественно некорректно. В результате, работоспособность кода зависит от счастливого стечения обстоятельств (длины ключевого слова, размера указателя 'keyword' в текущей <a href="http://www.viva64.com/ru/t/0012/">модели данных</a>).</p>
<p><b>Пример 2. Неработающая проверка при чтении файла</b></p>
<pre name="code" class="cpp">int PatchFile::LoadSourceFile(FILE* ofile)
{
  ...
  size_t c = fread(rb, 1, r, ofile);
  if (c &lt; 0) {
    LOG(("LoadSourceFile: "
         "error reading destination file: " LOG_S "\n",
         mFile));
    return READ_ERROR;
  }
  ...
}</pre>
<p>Диагностика PVS-Studio: V547 Expression 'c &lt; 0' is always false. Unsigned type value is never &lt; 0.  updater.cpp 1179</p>
<p>Это пример, когда код обработки ошибки пишется по принципу "чтобы было". Программист даже не задумался, что собственно он написал, и как это будет работать. Проверка некорректна. Функция fread() возвращает количество прочитанных байт с помощью беззнакового типа. Прототип функции:</p>
<pre name="code" class="cpp">size_t fread( 
   void *buffer,
   size_t size,
   size_t count,
   FILE *stream 
);</pre>
<p>Естественно, для хранения результата используется переменная 'c', имеющая тип size_t. Как следствие, результат проверки (c &lt; 0) всегда ложен.</p>
<p>Это хороший пример. На первый взгляд кажется, что есть какая-то проверка, но на практике выясняется, что она совершенно бесполезна.</p>
<p>Аналогичную ошибку можно увидеть и в других местах:</p>
<p>V547 Expression 'c &lt; 0' is always false. Unsigned type value is never &lt; 0.  updater.cpp 2373</p>
<p>V547 Expression 'c &lt; 0' is always false. Unsigned type value is never &lt; 0.  bspatch.cpp 107</p>
<p><b>Пример 3. Проверка указателя на NULL уже после его использования</b></p>
<pre name="code" class="cpp">nsresult
nsFrameSelection::MoveCaret(...)
{
  ...
  mShell-&gt;FlushPendingNotifications(Flush_Layout);
  if (!mShell) {
    return NS_OK;
  }
  ...
}</pre>
<p>Диагностика PVS-Studio: V595 The 'mShell' pointer was utilized before it was verified against nullptr. Check lines: 1107, 1109.  nsselection.cpp 1107</p>
<p>Если указатель равен нулю, то мы должны обработать этот специальный случай и вернуть из функции NS_OK. Смущает то, что указатель mShell до этого момента уже используется.</p>
<p>Скорее всего, этот код работает, так как указатель mShell всегда неравен NULL. Пример я привожу, чтобы показать, что можно допустить ошибку даже в очень простых проверках. Проверка есть, а смысла от неё нет.</p>
<p><b>Пример 4. Проверка указателя на NULL уже после его использования</b></p>
<pre name="code" class="cpp">CompileStatus
mjit::Compiler::performCompilation(JITScript **jitp)
{
  ...
  JaegerSpew(JSpew_Scripts,
    "successfully compiled (code \"%p\") (size \"%u\")\n",
    (*jitp)-&gt;code.m_code.executableAddress(),
    unsigned((*jitp)-&gt;code.m_size));

  if (!*jitp)
      return Compile_Abort;
  ...
}</pre>
<p>Диагностика PVS-Studio:V595 The '* jitp' pointer was utilized before it was verified against nullptr. Check lines: 547, 549.  compiler.cpp 547</p>
<p>Кстати, использование указателя до проверки - распространенная ошибка. Это ещё один пример на эту тему.</p>
<p><b>Пример 5. Неполная проверка входных значений</b></p>
<pre name="code" class="cpp">PRBool
nsStyleAnimation::AddWeighted(...)
{
  ...
  if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null ||
      unit[0] == eCSSUnit_Null || unit[0] == eCSSUnit_URL) {
    return PR_FALSE;
  }
  ...
}</pre>
<p>Диагностика PVS-Studio: V501 There are identical sub-expressions 'unit [0] == eCSSUnit_Null' to the left and to the right of the '||' operator.  nsstyleanimation.cpp 1767</p>
<p>Кажется, здесь сразу 2 опечатки. Затрудняюсь сказать, как именно должен выглядеть здесь код, но, наверное, хотели написать так:</p>
<pre name="code" class="cpp">if (unit[0] == eCSSUnit_Null || unit[1] == eCSSUnit_Null ||
    unit[0] == eCSSUnit_URL  || unit[1] == eCSSUnit_URL) {</pre>
<p>Из-за опечаток функция может начать обрабатывать  некорректные входные значения.</p>
<p><b>Пример 6. Неполная проверка входных значений</b></p>
<pre name="code" class="cpp">nsresult PresShell::SetResolution(float aXResolution, float aYResolution)
{
  if (!(aXResolution &gt; 0.0 &amp;&amp; aXResolution &gt; 0.0)) {
    return NS_ERROR_ILLEGAL_VALUE;
  }
  ...
}</pre>
<p>Диагностика PVS-Studio: V501 There are identical sub-expressions to the left and to the right of the '&amp;&amp;' operator: aXResolution &gt; 0.0 &amp;&amp; aXResolution &gt; 0.0  nspresshell.cpp 5114</p>
<p>А вот ещё один пример неудачной проверки входных параметров. В этот раз из-за опечатки не проверяется значение аргумента aYResolution.</p>
<p><b>Пример 7. Неразыменованный указатель</b></p>
<pre name="code" class="cpp">nsresult
SVGNumberList::SetValueFromString(const nsAString&amp; aValue)
{
  ...
  const char *token = str.get();
  if (token == '\0') {
    return NS_ERROR_DOM_SYNTAX_ERR; // nothing between commas
  }
  ...
}</pre>
<p>Диагностика PVS-Studio: V528 It is odd that pointer to 'char' type is compared with the '\0' value. Probably meant: *token == '\0'.  svgnumberlist.cpp 96</p>
<p>Проверка, что между запятыми ничего нет, не работает. Чтобы узнать, пустая строка или нет, можно сравнить первый символ с '\0'. Но здесь с нулем сравнивается не первый символ, а указатель. Этот указатель всегда неравен нулю. Корректная проверка должна была выглядеть так:  (*token == '\0').</p>
<p><b>Пример 8. Неподходящий тип для хранения индекса</b></p>
<pre name="code" class="cpp">PRBool 
nsIEProfileMigrator::TestForIE7()
{
  ...
  PRUint32 index = ieVersion.FindChar('.', 0);
  if (index &lt; 0)
    return PR_FALSE;
  ...
}</pre>
<p>Диагностика PVS-Studio: V547 Expression 'index &lt; 0' is always false. Unsigned type value is never &lt; 0.  nsieprofilemigrator.cpp 622</p>
<p>Функция не вернёт PR_FALSE, если в строке нет точки и продолжит работать с некорректными данными. Ошибка в том, что для переменной 'index' выбран беззнаковый тип данных. Проверка (index &lt; 0) не имеет смысла.</p>
<p><b>Пример 9. Формирование неправильного сообщения об ошибке</b></p>
<pre name="code" class="cpp">cairo_status_t
_cairo_win32_print_gdi_error (const char *context)
{
  ...
  fwprintf(stderr, L"%s: %S", context, (wchar_t *)lpMsgBuf);
  ...
}</pre>
<p>Диагностика PVS-Studio: V576 Incorrect format. Consider checking the third actual argument of the 'fwprintf' function. The pointer to string of wchar_t type symbols is expected.  cairo-win32-surface.c 129</p>
<p>Даже если ошибка успешно обнаружена, её еще надо суметь правильно обработать. А поскольку обработчики ошибок тоже никто не тестирует, то там можно увидеть много интересного.</p>
<p>Функция _cairo_win32_print_gdi_error() распечатает абракадабру. В качестве третьего аргумента функция fwprintf() ожидает указатель на unicode-строку, а вместо этого получает строку в формате 'const char *'.</p>
<p><b>Пример 10. Ошибка записи дампа</b></p>
<pre name="code" class="cpp">bool ExceptionHandler::WriteMinidumpForChild(...)
{
  ...
  DWORD last_suspend_cnt = -1;
  ...
  // this thread may have died already, so not opening
  // the handle is a non-fatal error
  if (NULL != child_thread_handle) {
    if (0 &lt;= (last_suspend_cnt =
                SuspendThread(child_thread_handle))) {
  ...
}</pre>
<p>Диагностика PVS-Studio: V547 Expression is always true. Unsigned type value is always &gt;= 0.  exception_handler.cc 846</p>
<p>Это другой пример в обработчике ошибок. Здесь некорректно обрабатывается результат, возвращаемый функцией SuspendThread. Переменная last_suspend_cnt имеет тип DWORD, а значит она всегда будет больше или равна 0.</p>
<h2>О других ошибках в Firefox</h2>
<p>Сделаю небольшое отступление и расскажу о результатах проверки Firefox в целом. Проект качественен, и PVS-Studio выявил мало ошибок. Однако, так как он большой, то в количественном отношении ошибок достаточно много. К сожалению, мне не удалось полноценно изучить отчет, выданный инструментом PVS-Studio. Дело в том, что для Firefox отсутствует файл проекта для Visual Studio. Проект проверялся консольной версией PVS-Studio, вызываемой из make-файла. Открыв отчет в Visual Studio, можно просмотреть все диагностические сообщения. Но раз нет проекта, то  Visual Studio не подсказывает, где какие переменные объявлены, не позволяет перейти в место определения макросов и так далее. В результате, анализ неизвестного проекта крайне трудоемок, и я смог изучить только часть сообщений.</p>
<p>Ошибки встречаются разноплановые. Например, есть выход за границы массива:</p>
<pre name="code" class="cpp">class nsBaseStatis : public nsStatis {
public:
  ...
  PRUint32 mLWordLen[10]; 
  ...
  nsBaseStatis::nsBaseStatis(...)
  {
    ...
    for(PRUint32 i = 0; i &lt; 20; i++)
       mLWordLen[i] = 0;
    ...
  }
  ...
};</pre>
<p>Диагностика PVS-Studio: V557 Array overrun is possible. The value of 'i' index could reach 19.  detectcharset.cpp 89</p>
<p>Хотя эта и подобные ошибки интересны, они не связаны с темой данной статьи. Поэтому, если интересно, можно посмотреть на некоторые другие ошибки в этом файле: <a href="http://www.viva64.com/external-pictures/txt/mozilla-test.txt">mozilla-test.txt</a>.</p>
<h2>Вернемся к ошибкам в обработчиках ошибок</h2>
<p>Я решил привести не парочку, а 10 примеров, чтобы убедить вас в актуальности проблем наличия дефектов в обработчиках ошибок. Конечно, обработчики ошибок не самые критичные и важные участки программы. Но ведь программисты их пишут, а значит, надеются с их помощью улучшить поведение программы. К сожалению, как показывают мои наблюдения, очень часто проверки и обработчики ошибок не работают. Смотрите, мне было достаточно одного, пусть и крупного проекта, чтобы показать множество ошибок данного типа.</p>
<p>Что же с этим делать и какие можно дать рекомендации?</p>
<h2>Первая рекомендация</h2>
<p>Нужно признать, что можно сделать ошибку даже в простой проверке. Это самое сложное и важное. Именно из-за того, что обработчики ошибок считаются простыми фрагментами кода, в них так много опечаток и иных дефектов. Обработчики ошибок не проверяют и не тестируют. На них не пишут тесты.</p>
<p>Конечно, писать тесты на обработчики ошибок очень сложно и часто экономически нецелесообразно. Но если программист хотя бы будет знать об опасности, это уже многое. Осведомлен, значит вооружен. С обработчиками ошибок можно привести и вот такую аналогию.</p>
<p>По статистике альпинисты чаще всего падают в конце подъема. Это случается не из-за усталости, а из-за того, что человек думает, что ему осталось совсем немного. Он расслабляется, теряет внимательность и, как результат, чаще допускает ошибки. С программистом при написании кода происходит что-то похожее. Он много сил и внимания тратит на алгоритм, а различные проверки пишет не сосредотачиваясь, так как уверен, что в них он не может допустить ошибку.</p>
<p>Итак, теперь вы предупреждены. И я уверен, это уже очень хорошо и полезно.</p>
<p>Если вы скажите, что подобные глупые ошибки допускают только студенты и неопытные программисты, то вы не правы. Опечатки легко делают все. Предлагаю на эту тему вот эту небольшую заметку "<a href="http://www.viva64.com/ru/b/0116/">Миф второй - профессиональные разработчики не допускают глупых ошибок</a>". Я могу подтвердить это множеством примеров из различных проектов. Но думаю, приведенных здесь вполне достаточно, чтобы задуматься.</p>
<h2>Вторая рекомендация</h2>
<p>Механизмы сохранения дампов, функции записи логов и другие подобные вспомогательные механизмы вполне заслуживают того, чтобы для них были сделаны юнит-тесты.</p>
<p>Неработающий механизм сохранения дампа не только бесполезен, он только создает видимость, что в случае беды им можно будет воспользоваться. Если пользователь пришлёт испорченный dump-файл, то он не только не поможет, а может только ввести в заблуждение и на поиски ошибок будет потрачено больше времени, чем если бы dump-файла вообще отсутствовал.</p>
<p>Рекомендация выглядит простой и очевидной. Но у многих ли, из читающих эту заметку, есть юнит-тесы для проверки класса WriteMyDump ?</p>
<h2>Третья рекомендация</h2>
<p>Используйте статические анализаторы кода. Возможность нахождения дефектов в обработчиках ошибок является одной из сильных сторон методологии <a href="http://www.viva64.com/go.php?url=636">статического анализа</a>. Статический анализ покрывает все ветви кода, в не зависимости от частоты их использования в работающем приложении. Он может выявить ошибки, проявляющие себя крайне редко.</p>
<p>Другими словами, при статическом анализе <a href="http://www.viva64.com/go.php?url=760">покрытие кода</a> составляет 100%. Достичь такого покрытия кода с помощью других видов тестирования практически нереально. Покрытие кода при юнит-тестах и регрессионном тестировании обычно составляет менее 80%. Оставшиеся 20% протестировать очень сложно. В эти 20% входят большинство обработчиков ошибок и редких ситуаций.</p>
<h2>Четвертая рекомендация</h2>
<p>Можно попробовать использовать методологию <a href="http://www.viva64.com/go.php?url=759">внесения неисправностей</a>. Смысл в том, что ряд функций время от времени начинают возвращать различные коды ошибок, и программа должна корректно их обрабатывать. Например, можно написать свою функцию malloc(), которая время от времени будет возвращать NULL, даже если память ещё есть. Это позволит узнать, как будет вести себя программа, когда память действительно кончится. Аналогично можно поступать с такими функциями, как fopen(), CoCreateInstance(), CreateDC() и так далее.</p>
<p>Существуют специальные программы, которые позволяют автоматизировать этот процесс и не писать самостоятельно свои функции, приводящие временами к отказу. К сожалению, я не работал с подобными системами, поэтому затрудняюсь рассказать про них подробнее.</p>
<h2>Заключение</h2>
<p>Дефекты в обработчиках ошибок встречаются часто. К сожалению, я не уверен, что приведенные в статье рекомендации достаточны, чтобы их избежать. Но я надеюсь, что вас заинтересовала эта проблема, и вы сможете придумать способы сократить количество недочетов в своих программах. Также я и другие читатели будут признательны, если вы поделитесь своими идеями и методиками, которые позволят избежать ошибки рассмотренного вида.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/how-to-make-fewer-errors-at-the-stage-of-code-writing-part-n4-ru/</link>
      <pubDate>Tue, 13 Dec 2011 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/how-to-make-fewer-errors-at-the-stage-of-code-writing-part-n4-ru/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/how-to-make-fewer-errors-at-the-stage-of-code-writing-part-n4-ru/</guid>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
    <item>
      <title>Подключение удаленной директории через ssh</title>
      <description><![CDATA[ <p>Во время работы над конкурсной задачей <b><a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Intel Acceler8</a></b> перед нашей командой встал вопрос удобной разработки на локальной машине с простой синхронизацией файлов с удаленным сервером. Из средств доступа был только ssh, и, исследовав возможные решения, мы остановили свой выбор на sshfs. sshfs - изначально файловая система, основанная на fuse, впоследствии портированная с linux на macos и freebsd, клонированная в windows, позволяет подмонтировать директорию на удаленном сервере через стандартную подсистему ssh - sftp. Это избавляет пользователя от постоянной синхронизации файлов вручную через scp/sftp/rsync/etc, позволяя даже редактировать удаленные файлы прямо из IDE. В этой статье я расскажу, как настроить и пользоваться sshfs под популярными операционными системами.</p>
<p>Перед тем, как настраивать sshfs, удостоверьтесь, что можете соединиться с сервером по ssh. Далее, ваш логин на сервере будет обозначаться $login, пароль - $passwd, адрес сервера - $host (для MTL - 36.81.203.1), удаленная директория - $remote_path, локальная директория, в которую будет примонтирована удаленная - $local_path (например, <i>~/mtl-home</i>).</p>
<h2>Windows</h2>
<p><b>Установка и настройка</b></p>
<p>Для работы нам потребуется библиотека Dokan (аналог fuse для windows) и основанное на ней приложение DokanSSHFS. Скачать их можно тут - &lt;<i>http://dokan-dev.net/en/download/</i>&gt;. После установки запускаем DokanSSHFS.exe и настраиваем по аналогии с putty:</p>
<ul>
<li>Host - $host</li>
<li>User - $login</li>
<li>Password - $passwd</li>
<li>Server root - $remote_path - удаленная директория, которая станет корнем нового диска. В нашем случае подойдет домашняя директория - /home/$login.</li>
</ul>
<p>После нажатия connect программа попытается соединиться с сервером, и, если соединение установлено успешно, выведет сообщение о начале сессии и спрячется в трей. После этого в системе появится виртуальный локальный диск, который представляет собой удаленную директорию.</p>
<p><b>Авторизация по ключам</b></p>
<p>Чтобы не вводить пароль в программу каждый раз (а запоминать его она специально не хочет), можно настроить авторизацию по ключам. DokanSSHFS использует ключи формата OpenSSH (ключи от putty не подойдут), и если у вас нет такого, можно сгенерировать один на удаленном сервере:</p>
<p><i>$ ssh-keygen <br />&lt;enter&gt;<br />&lt;enter&gt;<br />&lt;enter&gt;</i></p>
<p><i><b>Внимание</b>: я специально оставил passhprase пустым, так как DokanSSHFS почему-то не работает с запароленными ключами)</i></p>
<p>После этих действий в <i>/home/username/.ssh</i> появятся файлы id_rsa и id_rsa.pub</p>
<p>Первый необходимо скопировать на рабочую машину, а второй добавить в список авторизованных ключей на удаленном сервере: <i>$ cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys; chmod 0600 ~/.ssh/authorized_keys</i></p>
<p>Теперь, в DokanSSHFS переключаем авторизацию с Password на Identity и указываем путь к id_rsa. Готово!</p>
<h2>Нюансы</h2>
<p>Из-за нестабильности сети, время от времени DokanSSHFS может работать некорректно. При этом фактически соединение с сервером может оборваться, а виртуальный локальный диск останется в системе. В таких случаях необходимо вручную отключиться от сервера. В особо тяжких случаях придется воспользоваться диспетчером задач.</p>
<p>Второй аспект DokanSSHFS, который может вызвать недоумение и сбои в работе - агрессивное кеширование списков директорий. По-умолчанию, DokanSSHFS кеширует списки файлов в директориях и обновляет только следуя своей внутренней логике. Это не доставляет проблем, пока вы работаете один и только через подмонтированную директорию. Но в случае если в одной и той же директории работают два пользователя, возможны отставания в обновлении списка файлов в подмонтированной директории - на удаленном сервере уже может быть содзан/удален файл/директория, а на локальной машине изменений нет. Кэш может держаться несколько минут, что может обеспечить вам головную боль при активной работе. Отключить его можно, выставив флаг “Disable cache” на вкладке “Option”. С другой стороны, отключение кеша сильно влияет на отзывчивость работы с удаленной директорией. В случае больших пингов (как до MTL из Сибири) это может действовать на нервы.</p>
<h2>Linux</h2>
<p>В linux-системах существует утилита sshfs, основанная на fuse. Работать с ней проще, чем в windows, и все действия сводятся к трем командам:</p>
<ol>
<li>Устанавливаем (для ubuntu/debian):<br /><i>sudo apt-get install sshfs</i></li>
<li>Создаем директорию, в которую будет подмонтированная удаленная директория ($local_path) и запускаем sshfs:<br /><i>sshfs $login@$host:$remote_path $local_path</i><br />Теперь в $local_path располагается ваша домашняя директория сервера mtl.</li>
<li>Чтобы отмонтировать директорию, используется другая команда:<br /><i>fusermount -u $local_path</i></li>
</ol>
<h2>Авторизация по ключам</h2>
<p>Чтобы не вводить логин и пароль при каждом подключении sshfs, можно настроить авторизацию по ключам. Этой теме посвящено немало статей в интернете, здесь я опишу простой способ сделать это.</p>
<ol>
<li>Создание ключа<br /><i>ssh-keygen<br />&lt;Enter&gt;<br />&lt;Enter&gt;<br />&lt;Enter&gt;</i></li>
<li>Добавление публичного ключа на сервер<br /><i>ssh-copy-id $login@$host</i><br />Проверяем работоспособность - ssh и sshfs больше не должны спрашивать пароль.</li>
</ol>
<h2>MacOS</h2>
<p>Для macos существует проект MacFUSE, с помощью которого можно пользоваться sshfs аналогично linux-системам. Скачать его можно тут &lt;http://code.google.com/p/macfuse/downloads/list&gt;. Графическая оболочка для него доступна тут - &lt;http://code.google.com/p/sshfs-gui/downloads/detail?name=sshfs-gui.1.2.dmg&amp;can=2&amp;q=&gt;</p>
<h2>FreeBSD</h2>
<p>Для freebsd также существует порт fuse, он располагается в порте sysutils/fusefs-kmod. Для работы sshfs потребуются также порты sysfsutils/fusefs-libs и sysfsutils/fusefs-sshfs. Используется sshfs аналогично системам linux.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/mounting-remote-directory-over-ssh-and-sftp/</link>
      <pubDate>Fri, 18 Nov 2011 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/mounting-remote-directory-over-ssh-and-sftp/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/mounting-remote-directory-over-ssh-and-sftp/</guid>
      <category>Сообщество разработчиков технологий управления</category>
      <category>Tools</category>
      <category>События</category>
    </item>
    <item>
      <title>Ищем подматрицу с максимальной суммой элементов? - Найдем и распараллелим!</title>
      <description><![CDATA[ <p>В статье приводится алгоритм решения задачи, решавшейся по условиям конкурса <b><a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Acceler8</a></b>, проводившегося с 15 октября по 20 ноября 2011, организатор – компания <b>Intel</b>.</p>
<p>Основная идея конкурса – получить минимальное время работы программы, хорошую масштабируемость (максимальное ускорение) на всех архитектурах от двухядерной машины среднего пользователя до 40-ядерной машины с общей памятью из Manycore Testing Lab (MTL) от Intel.</p>
<h2 class="sectionHeading">Итак, в чем же задача? - Поиск максимальной подматрицы</h2>
<p>Дана двумерная прямоугольная матрица целых (– условие конкурса, а вообще вещественных) значений, необходимо найти в ней прямоугольную подматрицу с максимальной суммой элементовсреди всех подматриц.</p>
<p>Приведем пример – дана прямоугольная матрица:</p>
<blockquote>-8 -48<br /> -7 -10<br /> 3 -55<br /> 1 64<br /></blockquote>
<p>Очевидно, прямоугольная подматрица с максимальной суммой элементов в ней есть последний столбец, т.е. матрица с координатами [0][2]…[3][2] (первое число – номер начальной строки, нумерация начинается с нуля, второе – номер начального столбца, третье – конечной строки, четвертое – конечного столбца). Собственно этот ответ и требуется от алгоритма в качестве ответа на исходную матрицу.</p>
<p><strong>Что имеем? Описание входных данных</strong></p>
<p>Запускать будем с 2 параметрами:</p>
<p>Первый параметр командной строки — имя входного файла, второй параметр — имя выходного файла.</p>
<p>Во входном файле содержится несколько тестов. На первой строке входного файла дано число C — количество тестов, которые нужно решить. Далее описываются C тестов. Для каждого из них в файле дана одна строка, содержащая 6 целых чисел через пробел.</p>
<p>Первые два числа (M и N) — количество строк и столбцов в матрице, соответственно.</p>
<p>Четыре следующих числа — параметры некоторого псевдослучайного алгоритма, с помощью которого генерируется матрица: seed0, a, b и m соответственно,  – тем не менее алгоритм поиска максимума не зависит от способа генерации входной матрицы. <i>Т.е. Мы</i> <i>не будем привязывать алгоритм к каким-либо предположениям о структуре матрицы, связанным с методом генерации, кроме условия, что в ней есть как отрицательные, так и положительные значения элементов (и нули, конечно :))</i>.</p>
<p><strong>Ограничения для входных параметров (по условиям конкурса):</strong></p>
<blockquote>
<p>1 ≤ C ≤ 1000</p>
<p>1 ≤ M, N, a, b, m, seed0 ≤ 20000</p>
</blockquote>
<p><strong>Что хотим получить? Описание выходного файла</strong></p>
<p>Для каждого теста в выходной файл выводится строка "Case #x: ", за которой следуют 6 целых чисел (через пробел) и символ переноса строки.</p>
<p>Первые 4 числа — две пары координат, задающих подматрицу с максимальной суммой элементов (в первой паре оба числа должны быть меньше или равны соответственным числам во второй паре, т.е. сначала описывается координаты левого верхнего, а потом правого нижнего угла). В каждой паре первое число — номер строки, а второе число — номер столбца (нумерация начинается с нуля). Если в процессе поиска находится несколько подматриц с одинаковой максимальной суммой элементов, выводятся координаты любой из них.</p>
<p>Последние 2 числа — сумма элементов в найденной подматрице и площадь этой подматрицы соответственно.</p>
<p><b>Пример вызова программы</b></p>
<blockquote>
<p>./msp input.txt output.txt</p>
</blockquote>
<p><b>Пример входного файла (input.txt):</b></p>
<blockquote>
<p>2</p>
<p>4 3 10 3 4 17</p>
<p>14 31 11 4 5 18</p>
</blockquote>
<p><b>Пример выходного файла (output.txt):</b></p>
<blockquote>
<p>Case #1: 0 2 3 2 17 4</p>
<p>Case #2: 0 4 5 9 18 36</p>
</blockquote>
<p> </p>
<h2 class="sectionHeading">Алгоритмы решения задачи</h2>
<p><b>Последовательный алгоритм решения одномерной задачи</b></p>
<p><b>1D:</b></p>
<p>Очевидно, что «грубая сила» не лучший вариант решения задачи, дающий верхнюю оценку принципиального решения задачи.</p>
<p>За основу был взят алгоритм Кадане 1D, решающий задачу для одномерной задачи за O(N), где N – длина одномерного массива:</p>
<p>Алгоритм Кадане 1D</p>
<pre name="code" class="cpp">M=0, t=0
i=1
for j=1 to n do
 	t=t+a[j]
 	if t &gt; M then M=t, (x1,x2)=(i,j)
 	if t ≤ 0 then t=0, i=j+1 //обновить накопление
end for
output M, (x1,x2)
</pre>
<p>Его работа довольно очевидна: в t накапливается частичная сумма и если t становится больше M, то надо обновить M и положение подмассива. Если t становится отрицательными мы обнуляем накопленное значение, т.к. подмассив с максимальной суммой элементов не может начинаться с меньшего подмассива с отрицательной суммой элементов (в случае рандомного заполнения как положительными, так и отрицательными, очевидно, всегда можно выбрать любое положительное число, а не подмассив с отрицательной суммой элементов;в случае только отрицательных чисел это не так). В приведенном алгоритме M=0 в начале, т.е. если сумма подмассива с максимальной суммой отрицательна, то возвращается пустой подмассив и M=0.</p>
<p><b>2D:</b></p>
<p>Обобщение алгоритма на 2D не позволяет в прямом смысле его обобщить, сохранив замечательную скорость поиска максимума – мы можем лишь обобщить его использовав быстрый поиск по 2 измерению и «грубую силу» по 1 измерению:</p>
<p>2D версия Алгоритма Кадане</p>
<pre name="code" class="cpp">M = 0, (r1,c1) = (0,0), (r2,c2) = (0,0)
for g = 1 to m do
 	for j = 1 to n do p[j] = 0
 	for i = g to m do
 		t = 0, h = 1
 		for j = 1 to n do
 			p[j] = p[j] + a[i] [j]
 			t = t+p[j]
 			if t &gt; M then M = t, (r1,c1) = (g,h) , (r2,c2) = (i, j)
 			if t ≤ 0 then t=0, i = j+1 //обновить накопление
 		end for
 	end for

end for
output M, (r1,c1), (r2,c2)
</pre>
<p>Здесь по Y измерению (по номерам строк) мы применяем «грубую силу», а по X измерению (по номерам столбцов) применяется одномерный алгоритм Кадане. Т.к. по Y нам необходимо не меньше, чем <i>O(m<sup>2</sup>)</i> времени, по X алгоритм Кадане имеет скорость <i>O(n)</i>, следовательно общая скорость алгоритма <b><i>O(m<sup>2</sup>n)</i></b>.</p>
<p>В программной реализации мы применили алгоритм Кадане по строкам, а не по столбцам, как описано в алгоритме, т.е. далее мы под Х имеем в виду количество столбцов, а под Y – количество строк</p>
<h2 class="sectionHeading">Параллелизация и улучшения алгоритма:</h2>
<p>Распараллеливание представленного обобщения на 2D алгоритма Кадане:</p>
<ul>
<li>Во-первых, т.к. скорость алгоритма квадратична по m нам необходимо ее уменьшить, если это возможно, т.е. если m&gt;n, то надо искать решение для транспонированной матрицы, что для алгоритма означает, что мы меняем местами m и n, а к 2D матрице, хранящейся по строкам в виде одномерного массива, начинаем обращаться «по столбцам»</li>
</ul>
<ul>
<li>очевидно, что по X измерению все вычисления независимы везде, кроме сравнивания с текущим максимумом и записи нового значения максимума и его положения. Используя технологию OpenMP, мы можем распараллелить вычисления по внешнему циклу </li>
</ul>
<ul>
<li>Для начальных Х количество итераций внутреннего цикла от применения «грубой силы», максимально, для конечных Х – минимально. Поэтому при распараллеливании в начале на каждый процессор будет приходиться слишком много работы, а в конце они будут простаивать из-за накладных расходов. Поэтому необходимо, чтобы в начале chunk был равен 1, а в конце – больше 1, чтобы сбалансировать нагрузку.   Для OpenMP это решается параметром schedule(guided), при котором размер порции уменьшается с некоторого начального значения до величины chunk (по умолчанию chunk=1) пропорционально количеству ещё не распределённых итераций, делённому на количество нитей, выполняющих цикл.  (У нас ситуация с загрузкой прямо противоположная – в начале сильная загрузка, в конце слабая, поэтому мы используем этот параметр вместе с обращением цикла (т.е. отрицательным шагом), т.е. начинаем искать с конца по X).</li>
</ul>
<ul>
<li>При параллельном выполнении возможны ошибки «гонки данных». Введение критической секции (т.е. области кода, куда может входить одновременно не более одной нити – самым простым решением этих ошибок) – при сравнении с текущим максимумом, обновлением его значения и значений координат начала и конца подмассива – значительно замедлит параллельное выполнение, т.к. большую часть времени нити будут ждать друг друга.   Чтобы не вводить их мы создаем массив максимумов (и их координат) – «свой» элемент для каждой нити. Это решает проблему гонки данных и снимает проблему синхронизации. Полная синхронизация нитей (и найденных ими «локальных» максимумов) проводится при выходе из основного вычисления и тривиального поиска максимума в массиве максимум каждой нити.</li>
</ul>
<pre name="code" class="cpp">	int xidx, yidx, endxidx, endyidx;
	int Y = M[p]; //Число строк
	int X = N[p]; //Число столбцов
	
	///* Kadans 2D parallel alghoritm
	int N,M;
    int t = 0;
	int s = 0, k, l,j;
    int z,i,x;
	int numthread = omp_get_num_procs();
	int * S, * x1, * x2, * y1, * y2;
	S = (int*) calloc(numthread, sizeof(int)); //(int*) malloc(sizeof(int)*numthread);
	x1 = (int*) calloc(numthread, sizeof(int));
	x2 = (int*) calloc(numthread, sizeof(int));
	y1 = (int*) calloc(numthread, sizeof(int));
	y2 = (int*) calloc(numthread, sizeof(int));
	omp_set_num_threads(numthread);
//т.к. скорость алгоритма О(X^2*Y), надо в качестве Х надо взять min(X,Y), во втором случае надо обращаться к "транспонированной" матрице
	if (X&lt;=Y){
		N = X;
		M = Y;
		#pragma omp parallel shared(N,M,arr,S,x1,x2,y1,y2) private(z,x,i,t,s,j,k,l)  default(none) 
		{
			#pragma omp for schedule(guided) nowait //shared(N,M,arr,S,x1,x2,y1,y2) private(z,x,i,t,s,j,k,l)  default(none) //
			for(z = N-1; z &gt;-1; z--) //обратное уменьшение шага балансирует нагрузку с chunk &gt; 1, где операций меньше, До chunk=1 в начале, где вычислений больше
			{
				int threadIdx = omp_get_thread_num();
				int * sa;
				sa = (int*) calloc(M,sizeof(int));
				for(x = z; x &lt; N; x++)
				{
					t = 0;
					s = 1&lt;&lt;31;
					j = 0;
					k = 0; l = 0;
					for(i = 0; i &lt; M; i++)
					{
						sa[i] = sa[i] + arr[i*N+x]; 
						t = t + sa[i];
						if( t &gt; s)
						{
							s = t;
							k = i;
							l = j;
						}
						if( t &lt; 0 )
						{
							t = 0;
							j = i + 1;
						}
					}
					if( s &gt; S[threadIdx])
					{
						S[threadIdx] = s;
						x1[threadIdx] = z;
						y1[threadIdx] = k;
						x2[threadIdx] = x;
						y2[threadIdx] = l;
					}
					
				}
				free(sa);
			}
		}
		int max_idx = 0;
		int temp = 1&lt;&lt;31;
		for (i=0;i &lt; numthread;i++)
		{if (temp &lt; S[i]) {temp = S[i]; max_idx=i;}}
		if (x2[max_idx] &lt; x1[max_idx])
		{temp = x1[max_idx]; x1[max_idx] = x2[max_idx]; x2[max_idx] = temp;}
		if(y2[max_idx] &lt; y1[max_idx])
		{temp = y1[max_idx]; y1[max_idx] = y2[max_idx]; y2[max_idx] = temp;}

		FILE* file;
		file = fopen(name, "a");
		if (!file) printf("ERROR: file %s doesn't exist", name);
		fprintf(file, "Case #%i, %i, %i, %i, %i, %i, %i \n", p+1, y1[max_idx], x1[max_idx], y2[max_idx], x2[max_idx], S[max_idx], (x2[max_idx]-x1[max_idx]+1)*(y2[max_idx]-y1[max_idx]+1));
		fclose(file);
}</pre>
<p>Если <b><i>m&gt;n</i></b> то кроме указанных выше изменений в переборе элементов одномерного массива, полученного из исходной матрицы задачи, и замены x&lt;-&gt;y при выводе результата код ничем идейно не отличается.</p>
<p><b>Проблемы распараллеливания 2D алгоритма  Кадане:</b></p>
<p>Очевидно, что по Y направлению алгоритм принципиально последовательный (т.к. называемая истинная зависимость) и распараллелить мы его сможем, только сменив алгоритм :). В этом смысле это не наилучший алгоритм. Но учитывая ограничения конкурса, и то, что ядер все равно не больше 40, это не большая проблема.     <b><br /></b></p>
<p><b>Тайминги:</b></p>
<p>Использовались предложенные на форуме:</p>
<pre name="code" class="cpp">#include &lt;sys/time.h&gt;
int t = curTime();
fprintf(stderr, "Time of execution: %.3lf sec.\n",
(double)(curTime() - t) / 1000.0);

static int curTime() {
    struct timeval now_tv;
    gettimeofday( &amp;now_tv, NULL );
    return (int)now_tv.tv_sec * 1000 + (int)now_tv.tv_usec / 1000;
}</pre>
<h2 class="sectionHeading">Полученные результаты и недостгнутые цели</h2>
<ul>
<li>Транспонирование матрицы (если M&lt;N) дает ускорение в 2 раза для наборов содержащих сильно прямоугольные матрицы (M=3*N). <strong><br /></strong></li>
<li>Использование директивы guided дает ускорение в 1.5 раза.</li>
<li>Использование массива максимумов для каждой нити дает ускорение в 5 раз. (Среднее число одновременно работающих потоков остается постоянной, равной удвоенному (с включенным HyperThreading) количеству ядер). Для варианта с критической секции и с общей переменной максимума среднее число одновременно работающих потоков равно 0, т.е. нити всегда друг друга ждут.</li>
</ul>
<p>На тестах для следующего input.txt:</p>
<blockquote>3<br />4 3 10 3 4 17<br />14 31 11 4 5 18<br />1024 1024 10 3 4 17<br /></blockquote>
<p>Intel Core i3-350M, RAM 3Gb: 9.35 секунд, при этом удалось добиться равномерной загрузки всех 4 потоков (с включенным HyperThreading).</p>
<p>Корректность параллельного алгоритма, т.е. правильность выходных данных для средних датасетов (medium.txt) была проверена на Intel MTL и гарантируется (время выполнения ~23 секунд).</p>
<h2 class="sectionHeading">Выводы</h2>
<p><strong>Преимущества алгоритма Кадане 2D:</strong></p>
<ul>
<li>Элегантность</li>
<li>Простота реализации</li>
<li>Относительная простота распараллеливания</li>
<li>Хорошая масштабируемость по количеству ядер </li>
<li>Большое количество литературы</li>
</ul>
<p><strong>Недостатки алгоритма Кадане 2D:</strong></p>
<ul>
<li>Далеко не лучшая скорость работы <em>O(m<sup>2</sup>n)</em> по сравнению с минимальной оценкой идеального алгоритма <em>O(mn)</em>. (Лучшую чем <em>O(m<sup>2</sup>n)</em> оценку имеет, например, алгоритм Такаоки, о котором мы только скажем, что для довольно «маленьких» тестов данного конкурса он слишком громоздок)<strong><br /></strong></li>
<li>Принципиальная последовательность алгоритма по второму направлению (столбцы в нашей статье).</li>
</ul>
<p><strong>Литература:</strong></p>
<p>В процессе поиска было найдено много материалов (ссылки ниже) из открытого доступа.</p>
<p>[1] <a href="http://software.intel.com/file/39980/">Tadao Takaoka, Efficient Algorithms for the Maximum Subarray Problem by Distance Matrix Multiplication</a></p>
<p>[2] <a href="http://software.intel.com/file/39982/">KALYAN PERUMALLA, PARALLEL ALGORITHMS FOR MAXIMUM SUBSEQUENCE AND MAXIMUM SUBARRAY</a></p>
<p>[3] <a href="http://software.intel.com/file/39985/">Sung Eun Bae, Sequential and Parallel Algorithms for the Generalized Maximum Subarray Problem</a></p>
<p>[4] <a href="http://software.intel.com/file/39991/">Fredrik Bengtsson C. Jingsen Chen, Efficient Algotithms for k Maximum Sums</a></p>
<p>[5] <a href="http://software.intel.com/file/39996/">Mohammad Bashar, AVERAGE CASE ANALYSIS OF ALGORITHMS FOR THE MAXIMUM SUBARRAY PROBLEM</a></p>
<p>P.S. Успехов в этом увлекательном деле, Let's go parallel!</p>			 ]]></description>
      <link>http://software.intel.com/ru-ru/articles/maximum_subarray_problem_algorithm_kadane_by_udjin123/</link>
      <pubDate>Thu, 17 Nov 2011 00:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/maximum_subarray_problem_algorithm_kadane_by_udjin123/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/maximum_subarray_problem_algorithm_kadane_by_udjin123/</guid>
      <category>Параллельное программирование</category>
      <category>Академическое сообщество</category>
    </item>
    <item>
      <title>Способы оценки энергопотребления программы</title>
      <description><![CDATA[ <div ><i> </i><span class="sectionHeadingText"><b>Введение</b></span><i><br /><br />Что важно пользователю в программе? Корректность работы, скорость, удобный интерфейс... Да. Так же одним из значимых критериев является энергопотребление программы.</i><br /><br /><i> Сама программа, конечно же, никакой электроэнергии не потребляет, потребляет компьютер. Но именно при её выполнении тратится энергия. Таким образом Вы, выбирая тот или иной способ написания программы, можете управлять энергопотреблением. Приятно, когда Ваш гаджет работает наделю без подзарядки, а не рабочий день.</i><br /><br />Чтобы оценить текущее энергопотребление устройства, работающего от аккумулятора, есть несколько способов. Один из них это использование специальных приборов, снимающих показания об энергопотребление с контактов на устройстве[1]. Так можно получить довольно точную информацию об энергопотреблении на каждой части устройства: экран, видеокарта, процессор... Этот способ предполагает наличие с одной стороны специального прибора, с другой стороны знаний из области электротехники. Одним из других способов является использование программ, позволяющих оценить текущее энергопотребление аккумулятора. Обзор некоторых из них мы и совершим.<br /></div>
<br /><b class="sectionHeadingText">Perfmon</b><br /><br />
<div >Perfomance Monitor - стандартная программа Microsoft Windows (в ранних версиях до Windows XP - System Monitor). Позволяет получать и собирать информацию о многих параметрах системы: о процессоре, физическом диске, файле подкачки, кэше... В т.ч. и о использовании батареи. Чтобы получить требуемую информацию, небходимо создать группу сборщиков данных об интересующих параметрах. После запуска программа предоставит отчёт и отобразит содержимое на графике.Нас интересует discharge rate - мощность разрядки батареи. Чем выше мощность, тем больше энергии потребляет программа. На рисунке отображен составленный программой отчёт  о разрядке батареи:<br /></div>
<img  src="http://i40.tinypic.com/2u8ymur.png" alt="Perfmon discharge battery" /><br /><br />
<div >Уровень разрядки батареи отображен красной линией. При запуске приложения, потребляемая мощность увелилчивается.<br /></div>
<br /><b class="sectionHeadingText">Intel Power Checker</b><br /><br />
<div>
<div >Программа направлена конкретно на оценку энергопотребления приложения. Позволяет получить текущую потребляемую мощность. Процесс измерения состоит из 3-х этапов: измеряется энергопотребления системы без приложения в состоянии простоя, затем потребляемой мощность с работающим приложением, в завершение потребляемая мощность системы с загруженным, но не работающим приложением. Потребляемая мощность равна разнице между результатами второго и первого пункта соответственно:<br /></div>
<img height="399" width="700"  src="http://i42.tinypic.com/245bxcj.png" alt="Intel Power Checker battery discharge" /><br /><br />
<div >Работает как Windows системах, так и в Linux.<br /><br /><b>Заключение</b><br /><br /><i>Мы рассомтрели некоторые возможные пути оценки энергопотребления приложения - использование специальных приборов или программ. <b>Пусть Ваши программы будут энергоэффективными!</b></i><br /><br />Ссылки:<br /><a href="http://software.intel.com/en-us/articles/creating-energy-efficient-software-part-4">Creating Energy-Efficient Software (Part 4)</a><br /></div>
</div> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/Energy_Consumption_Tools/</link>
      <pubDate>Sat, 12 Nov 2011 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/Energy_Consumption_Tools/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/Energy_Consumption_Tools/</guid>
      <category>Сообщество разработчиков мобильного ПО</category>
      <category>Tools</category>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
    <item>
      <title>Шаблон проектирования Producer-Consumer</title>
      <description><![CDATA[ <h2 class="sectionHeading">Шаблон проектирования producer-consumer, предыстория</h2>
<p>Год тому назад, когда я учился на 3-ем курсе университета, у нас появился предмет, который вел “big-boss” одной из местных фирм, занимающихся разработкой программного обеспечения. Этот предмет назывался “Операционные системы”, а речь на нем шла о современном строении, функционировании компьютерных систем в общем. На практических занятиях требовалось реализовать простую задачу: подобрать пароль по хэшу, зашифрованному стандартной функцией crypt() из POSIX. Написав простую программу, вы получите новое задание - реализовать это многопоточно, а потом опять новое - реализовать распределенно и так далее.</p>
<p>Но мы остановимся на многопоточном решении этой задачи, и на примере ее разберем тему нашего разговора - шаблон проектирования “producer-consumer”. Я замечу, что для чтения требуется понимание основ и базовых приемов параллельного программирования, например, нужно знать, что такое синхронизация потоков, мьютексы, семафоры, а также понимать типичные ошибки в проектировании решения параллельных задач.</p>
<h2 class="sectionHeading">Анатомия нашего шаблона</h2>
<p>Шаблон “producer-consumer” устроен по следующей схеме:</p>
<p><img height="338" width="498" src="http://cs10699.vkontakte.ru/u4024736/29341819/x_389d023d.jpg" /></p>
<p>Producer (изготовитель) - это некоторый поток, который генерирует “задания” и складывает их в очередь Queue.</p>
<p>Consumer (исполнитель) - это поток, который берет задания из очереди, выполняет и отправляет результаты туда, куда нужно.</p>
<p>Очередь Queue - это ограниченный буфер заданий с заранее заданной вместимостью.</p>
<p>В общем случае, как “продюсеров”, так и “консьюмеров” может быть много, но важно соблюдать баланс, чтобы рационально расходовать ресурсы. Идеальный режим функционирования очереди достигается, когда задания по ней движутся непрерывно, то есть скорость добавления заданий равно скорости, с которой “консьюмеры” их выполняют, а иначе часть потоков ожидает пока остальные сделают свою часть работы.</p>
<h2 class="sectionHeading">Применение шаблона в нашей задаче</h2>
<p>Самое простое решение будет перебирать все строки длиной не более <b><i>L</i></b>, с буквами из определенного алфавита (например, большие и маленькие латинские буквы и цифры) вызывать функцию crypt(), и полученный хэш сравнивать с исходным.</p>
<p>Можно легко протестировать и понять, что самая ресурсоемкая часть программы - это вызов функции crypt() для всех перебранных строк, в то время как сам перебор работает 1-2 процента от общего времени исполнения. Понятно, что нужно параллелить именно эту часть программы.</p>
<p>Основные части шаблона в нашей задаче устроены следующим образом:</p>
<p><i><b>task = (s, len)</b></i>, где <b><i>s</i></b> - это строка длиной <b><i>len</i></b>.</p>
<p><i><b>queue = (data[], head, tail, mutex)</b></i>. Задания хранятся в массиве <b><i>data[]</i></b>, используются указатели <b><i>head</i></b> и <b><i>tail</i></b> для работы с очередью, а также обязательно нужен <b><i>mutex</i></b>, чтобы обеспечить синхронизацию потоков, работающих с очередью.</p>
<i>result = (found, s, mutex)</i>, где <b><i>found</i></b> - булева переменная, указывающая найден уже ответ или нет.
<p> </p>
<p>Схема:</p>
<p><img height="338" width="840" src="http://cs10699.vkontakte.ru/u4024736/29341819/z_693f66d7.jpg" /></p>
<p>Это вариант первый, очевидный. Продюсер генерирует все строки, а консьюмеры будут считать хэш от строк s в заданиях и сравнивать с данным. Однако, мы помним, что генерация строк занимает всего несколько процентов от общего времени выполнения, а значит, большинство времени очередь будет заполнена, а продюсер - простаивать в ожидании свободного места. Это неэффективно.</p>
<p>Таким образом, мы встретили одну из крайностей реализации нашего шаблона - заданий много, но они все мелкие, доступные процессоры заняты не полностью, много времени тратится на синхронизацию потоков, ожидание, блокирование и так далее.</p>
<p>Вариант второй, улучшенный. теперь задание - это строка, которую перебрали “наполовину”, то есть <b><i>task = (s, len, l, r)</i></b>, <b><i>l</i></b> и <b><i>r</i></b> означают, что консьюмер должен еще перебрать кусок строки с <b><i>l</i></b> по <b><i>r</i></b>, а только потом проверять хэш. В данной задаче, подобная идея - это основа оптимального решения, нужно лишь подобрать пропорцию, в которой продюсер и консьюмер делят процесс перебора строк.</p>
<h2 class="sectionHeading">Зачем это все нужно</h2>
<p>Этот шаблон часто применяется в высоконагруженных системах, распределенных вычислениях, что, на самом деле, неудивительно. Почти всегда можно абстрагировать задачу и выделить в ней части, которые делают некоторый препроцессинг, и части, которые занимаются окончательной обработкой данных на основе этого препроцессинга. Этот шаблон - это обобщение принципа подобного разделения.</p>
<p>Части, который осуществляют препроцессинг также можно параллелить (это означает, что нам нужно несколько параллельно работающих продюсеров), как и части окончательной обработки.</p>
<p>Шаблон легко обобщается на распределенные вычисления по сети, в этом случае, роли разделяются не по потокам, а по отдельным компьютерам, а очередь может храниться даже на отдельном сервере.</p>
<p>Зачем я написал эту статью в рамках конкурса <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Aceller8</a>? Как знать... :)</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/producer-consumer/</link>
      <pubDate>Fri, 11 Nov 2011 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/producer-consumer/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/producer-consumer/</guid>
      <category>Параллельное программирование</category>
    </item>
    <item>
      <title>Поиск подматрицы с наибольшей суммой: прикладные задачи</title>
      <description><![CDATA[ <p>Приветствую всех участников конкурса <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Acceler8</a>!</p>
<p>Многие задачи в спортивном программировании не абстрактны, как могло бы показаться на первый взгляд, а могут применяться при решении некоторой прикладной задачи. Для тех, кто не знает про задачу конкурса Acceler8: требуется в заданной матрице найти подматрицу с наибольшей суммой элементов. Подматрицей считается последовательность строк и столбцов, следующих непосредственно друг за другом. Подматрица может содержать как всю матрицу,так и один-единственный элемент. В данной статье я хотел бы рассказать о прикладных задачах, в которых может понадобиться искать подматрицу с наибольшей суммой элементов. Итак, начнем.</p>
<h2 class="sectionHeading">1. Анализ генетических последовательностей</h2>
<p>В связи с быстрым развитием изучения генетических данных, анализ последовательностей стал важной частью исследований в области биоинформатики. Главная направление исследований последовательностей – нахождение биологически значимых участков.</p>
<p>Одна из задач – предсказание расположения мембран протеина. Протеины – большие молекулы, состоящие из большого числа аминокислот, насчитывающих 20 видов. Фактически, все протеины могут рассматриваться как длинная цепочка аминокислот, или последовательность букв 20-буквенного алгоритма. В большинстве случаев, действительное расположение каждого атома молекулы протеина может быть точно определено линейной последовательностью, которая определяет биологическое значение в живом организме. Информация о биологическом значении протеина содержится в линейной последовательности.</p>
<p>Компьютерный анализ последовательностей становится все более популярным. В особенности, вычислительные алгоритмы, разработанные для предсказания структуры протеиновых последовательностей, могут стать бесценным инструментом биологов для быстрого понимания значения протеина, которое станет первым шагом к созданию имунноглобулина.</p>
<p>Определение трансмембранных доменов в протеиновой последовательности – одна из важных задач понимания структуры протеина. Для каждой аминокислоты найден индекс гидрофобности. Гидрофобные радикалы протеина находится только в ядре мембраны, которая окружена двойным липидным слоем. Наименее гидрофобные располагаются в водной среде. Это явление известно как гидрофобный эффект, одна из связывающих сил внутри молекулы. Трансмембранные домены обычно содержат гидрофобные радикалы. Если известен индекс гидрофобности для каждого радикала, трансмембранные домены будут являться сегментами с наибольшей суммой индексов. Задача нахождения цепочки с наибольшей общей суммой будет эквивалентна нахождению подвектора с наибольшей суммой.</p>
<h2 class="sectionHeading">2. Компьютерное зрение</h2>
<p>Изображение представляется двумерным массивом, где в каждая ячейка представляет значение яркости каждого пискела, основанное на стандарте RGB. Если необходимо найти наиболее яркую часть изображения, строится двумерный массив, где каждая ячейка представляет яркость соответствующего пикселя. Яркость формально определяется как (R+G+B)/3, однако это не соответствует человеческому восприятию цветов. Для яркости, соответствующей человеческому восприятию, в большинстве графических приложений используется освещенность Y, вычисляемая как взвешенная сумма R, G, B: Y=0,3R+0,59G+0,11B. Веса выбраны так, чтобы максимально точно соответствовать чувствительности глаза к красному, зеленому и синему. Очевидно, все значения неотрицательные, и подмассив с наибольшей суммой – весь массив. Перед вычислением подмассива с наибольшей суммой, важно нормализовать каждую ячейку вычитанием положительного опорного значения. Обычно, среднее арифметическое значений яркостей всех пикселей является подходящим опорным значением. Затем находится подмассив с наибольшей суммой, соответствующей наиболее яркой области изображения.</p>
<p>Этот подход может применяться к различным схемам оценки пикселя. Например, можно находить горячие точки на термографическом изображении. Наиболее горячие части имеют белый цвет, средние температуры – красный и желтый, и самые холодные части – синие. Можно назначить значение каждому пикселю и определить самую нагретую область.</p>
<h2 class="sectionHeading">3.	Анализ данных (data mining)</h2>
<p>Поиск подматрицы с наибольшей суммой применяется в области анализа данных, когда заданы числовые характеристики. Вероятность, обозначаемая x-&gt;y  и называемая правилом ассоциации, задается формулой условной вероятности:
<blockquote>conf(x-&gt;y)=support(x,y)/support(x)</blockquote>
Support(x,y) – число транзакций, включающих как X, так и Y, support(X)  - число транзакций, включающих только X. Предположим, мы хотим определить правило, которое сообщает, что если покупатель приобретает товар Х, то он также приобретет товар Y. Тот же принцип может применяться, если мы хотим открыть зависимости между численными характеристиками, такими как возраст или доход. </p>

<p>Предположим, мы хотим видеть, имеет ли тенденцию определенная возрастная группа тенденцию покупать определенный товар. У нас имеются 2 числовые характеристики покупателей: их возраст и количество купленного товара. Эта задача может быть сведена к нашей задаче. Пусть есть массив из 6 элементов, в котором a[i] – число покупателей возраста от 10i до 10(i+1), и купивших товар А. У нас есть массив {0,1,2,0,0,0}, который нормализуем вычитанием среднего арифметического:
<blockquote>a={-1/2,1/2,3/2,-1/2, -1/2, -1/2}.</blockquote>
<p>Подмассив a[2..3] имеет наибольшую сумму. Приходим к выводу, что основная группа покупателей – возрастом от 20 до 30 лет. Если имеются 2 числовые характеристики, то задача сведется к поиску подматрицы с наибольшей суммой.</p>
<p>P.S. Успехов всем участникам!</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/Applying_Max_Submatrix/</link>
      <pubDate>Fri, 11 Nov 2011 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/Applying_Max_Submatrix/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/Applying_Max_Submatrix/</guid>
      <category>Параллельное программирование</category>
      <category>University</category>
      <category>Академическое сообщество</category>
    </item>
    <item>
      <title>Основные возможности менеджера ресурсов Torque (PBS)</title>
      <description><![CDATA[ <p>В настоящей статье попробую рассказать основные возможности системы управления, точнее менеджера ресурсов Linux-кластерами Torque.</p>
<p>Почему именно Torque? Ответ на это вопрос достаточно прост. Torque является продолжением семейства систем управления PBS, одна из который установлена на Intel(R) Manycore Testing Lab.</p>
<p>Весь функционал, предоставляемый разработчиками систем управления кластерами, условно можно разделить на две группы:</p>
<ol>
<li><span >Команды управления ресурсами:</span><ol>
<li><span >Постановка задачи в очередь системы управления для последующего исполнения.</span></li>
<li><span >О</span><span >становка задачи, если она была запущена, или удаление из очереди системы управления, если она ожидает запуска.</span></li>
</ol></li>
<li><span >Команды мониторинга:</span><ol>
<li><span >Получение актуальной информации о состоянии узлов системы управления.</span></li>
<li><span >Получение информации о текущем состоянии задач.</span></li>
</ol></li>
</ol>
<p>Центральным компонентом системы управления кластером является планировщик. Именно планировщик обеспечивает эффективное использование узлов кластера. По мере поступления задач в очередь системы управления, каждая получает некоторый приоритет. Планировщик выбирает наиболее приоритетные задачи и в соответствии с некоторое стратегией планирования назначает узлы, на которых они будут исполняться. Фактически планировщик строит расписание запуска задач. В настоящее время наиболее распространенными стратегиями планирования являются <i>FCFS</i> (First Come First Served) и <i>Backfilling</i>.</p>
<p>В системе управления Torque существует встроенный планировщик, в котором реализован алгоритм <i>FCFS</i>. Наряду с этим, есть возможность подключения внешнего планировщика <i>Maui</i> . Разработчики <i>Maui</i> предоставляют несколько модификаций стратегии <i>Backfilling</i>, а именно <i>FIRSTFIT, BESTFIT, GREEDY</i>. Подробнее о том, как рабоают эти алгоримты можно познакомиться на <a href="http://www.adaptivecomputing.com/resources/docs/maui/mauiadmin.php">официальной странице</a>.</p>
<p><b>Команды управления ресурсами системы управления</b></p>
<p>1. Постановка задачи в очередь системы управления. Команда <a href="http://www.clusterresources.com/torquedocs/commands/qsub.shtml"><b> qsub </b></a>.</p>
<p>Формат команды:</p>
<pre name="code" class="cpp">qsub [ключи] [script]</pre>
<p><b>Наиболее важные ключи</b>:</p>
<ul>
<li>[-a date_time] - время, после которого задача готова к исполнению.</li>
<li>[-b secs] - максимальное время исполнения задачи (в секундах).</li>
<li>[-c checkpoint_options] - применение механизма контрольных точек к задаче, запущенной на узле, при условии, что ОС поддерживает механизм контрольных точек.</li>
<li>[-d path] - рабочая директория приложения.</li>
<li>[-e path] - путь для перенаправления стандартного потока ошибок.</li>
<li>[-I ] - интерактивный запуск задачи.</li>
<li>[-l resource_list ] - определяет список ресурсов для запуска задачи на конкретном наборе ресурсов. Формат задания списка ресурсов следующий: <i>resource_name[=[value]][,resource_name[=[value]],...]</i></li>
<li>[-N  name] - название задачи.</li>
<li>[-o path] - директория для перенаправления стандартного потока вывода.<br /> [-p  priority] - приоритет задачи (значение от -1024 до 1023).</li>
<li>[-v  variable_list] - установка списка переменных окружения.</li>
</ul>
<p>Скрипт для запуска задачи <i>[script]</i> может состоять из директив, комментариев и исполняемых команд системы управления <i>PBS</i>. <i>PBS</i>-директивы обеспечивают добавление дополнительных аттрибутов к команде qsub. По существу qsub сканирует строки скрипта на наличие директив. Например, название задачи можно указать внутри скрипта с помощью слудующей директивы: <i>#PBS -N my_job</i></p>
<p>Ниже приведен пример скрипта. Отметим, что в заголовке скрипта явно указывается интерпретатор. Последующие строки скрипта содержат установку переменной окружения, установку списка узлов (i-node и i-master) для запуска задачи, явную установку (cd) рабочей директории и прав пользователя (chmod) на эту директорию, а также непосредственный вызов для исполнения задачи</p>
<pre name="code" class="cpp">#!/bin/sh
PATH=/opt/openmpi_ifort_1.3.3/bin/:$PATH
#PBS -l nodes=i-node+i-master
cd /mnt/share/2322/
chmod 777 *
/mnt/share/2322/runMPI.sh &gt; /mnt/share/2322/std.out</pre>
<p>2. Остановка задачи. Команда <a href="http://www.clusterresources.com/torquedocs/commands/qdel.shtml"><b>qdel</b></a>.</p>
<p>Формат команды:</p>
<div>
<pre name="code" class="cpp">qdel [ключи] &lt;JobId&gt; | 'all' | 'ALL'</pre>
</div>
<p><b>Наиболее важные ключи</b>:</p>
<ul>
<li>'all' - удаление всех задач из очереди.</li>
</ul>
<p><b>Команды мониторинга</b></p>
<p>1. Получение актуальной информации о состоянии узлов системы управления. Команда <a href="http://www.clusterresources.com/torquedocs/commands/pbsnodes.shtm"><b>pbsnodes</b></a></p>
<p>Формат команды:</p>
<div>
<pre name="code" class="cpp">pbsnodes [ключи] [JobId]</pre>
</div>
<p><b>Наиболее важные ключи</b>:</p>
<ul>
<li>[-a] - получение всей информации обо всех узлах системы управления.</li>
<li>[-x] - то же, что и предыдущий ключ, только результат выводится в формате xml.</li>
<li>[-c] - убрать узлы, которые находятся в состоянии OFFLINE, из списка просматриваемых узлов.</li>
<li>[-l] - получение названий узлов с их текущими состояниями.</li>
</ul>
<p>2. Получение информации о текущем состоянии задачи. Команда <a href="http://www.clusterresources.com/torquedocs/commands/qstat.shtml"><b>qstat</b></a></p>
<p><b>Формат команды</b>:</p>
<pre name="code" class="cpp">qstat [ключи]</pre>
<p><b>Наиболее важные ключи</b>:</p>
<ul>
<li>[-a] - выводится информация обо всех задачах, которые находятся в очереди системы управления.</li>
<li>[-n] - дополнительно к состоянию задачи выводятся названия узлов, на которых была запущена задача.</li>
</ul>
<p>Стандартный вывод команды gstat содержит следующую информацию о задаче:</p>
<div>
<ul>
<li><span >идентификатор задачи на системе управления;</span></li>
<li><span >название задачи, которое было установлено при добавлении задачи в очередь системы управления;</span></li>
<li><span >владелец задачи;</span></li>
<li><span >время работы задачи;</span></li>
<li><span >состояние задачи (C - задача завершена, E - задача закрыта после выполнения набора операций, H - запуск задачи отложен, Q - задача ожидает ресурсов, R - задача исполняется, T - задача перемещена на другой кластер, W - задача ожидает запуска (ресурсы уже выделены), S - задача приостановлена).</span></li>
</ul>
</div>
<p><a href="http://www.clusterresources.com/torquedocs/">Официальная страница</a>, на которой размещена подробная документации.</p> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/PBSCapabilities/</link>
      <pubDate>Thu, 10 Nov 2011 12:00:00 -0800</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/PBSCapabilities/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/PBSCapabilities/</guid>
      <category>Tools</category>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
    <item>
      <title>Морфологическое сглаживание (MLAA)</title>
      <description><![CDATA[ <link media="screen" href="http://software.intel.com/media/gamedev/css/3302_Intel_VC_01.css?v=11" type="text/css" rel="stylesheet" />
<link media="screen" href="http://software.intel.com/file/23729" type="text/css" rel="stylesheet" />
<table width="100" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td valign="top">
<div id="left_container">
<div id="header_content"><a href="http://software.intel.com/en-us/visual-computing/" title="Visual Computing Developer Community"><img height="96" width="727" src="http://software.intel.com/file/20493/" border="0" /></a></div>
<div id="left_content_container2"><!-- START left content -->
<div id="showcase_01">
<div >
<h2 >Морфологическое сглаживание</h2>
<p >MLAA – это алгоритм сглаживания, основанный на постобработке изображений. Его действие основано на поиске разрывов в изображении и смешении цвета в районе этих разрывов, что обеспечивает эффективный антиалиасинг. Базовая реализация метода была представлена в статье А. Решетова, <a href="http://visual-computing.intel-research.net/publications/papers/2009/mlaa/mlaa.pdf">"CPU-based MLAA implementation"</a> (2009г.), которая была доработана в данной работе.</p>
<br />
<div class="coolButton"><a href="http://software.intel.com/ru-ru/articles/mlaa-efficiently-moving-antialiasing-from-the-gpu-to-the-cpu/" title="MLAA article">Читать статью</a></div>
<div class="coolButton"><a href="http://software.intel.com/file/37247" title="MLAA Source">Скачать исходный код</a></div>
<br /></div>
<div >
<p>
<object height="203" width="360" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" id="v_8600_1986" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000">
<param name="id" value="v_8600_1986" />
<param name="name" value="v_8600_1986" />
<param name="flashvars" value="file=http://software.intel.com/media/videos/8/c/2/4/9/6/7/MLAA.mp4&amp;image=http://software.intel.com/media/videos/8/c/2/4/9/6/7/8c249675aea6c3cbd91661bbae767ff1_player.jpg&amp;autostart=false&amp;bufferlength=5&amp;allowfullscreen=true&amp;plugins=http://software.intel.com/common/swf/listen&amp;title=CPU-Based+MLAA+Implementation+" />
<param name="allowfullscreen" value="true" />
<param name="src" value="http://software.intel.com/common/swf/mediaplayer.swf" /><embed height="203" width="360" src="http://software.intel.com/common/swf/mediaplayer.swf" allowfullscreen="true" flashvars="file=http://software.intel.com/media/videos/8/c/2/4/9/6/7/MLAA.mp4&amp;image=http://software.intel.com/media/videos/8/c/2/4/9/6/7/8c249675aea6c3cbd91661bbae767ff1_player.jpg&amp;autostart=false&amp;bufferlength=5&amp;allowfullscreen=true&amp;plugins=http://software.intel.com/common/swf/listen&amp;title=CPU-Based+MLAA+Implementation+" name="v_8600_1986" id="v_8600_1986" type="application/x-shockwave-flash"></embed>
</object>
</p>
<p align="center"><a href="http://software.intel.com/en-us/videos/avx-cloth-developer-video/">Демонстрация возможностей алгоритма сглаживания MLAA </a></p>
<p>Подробное описание алгоритма: "<a href="http://software.intel.com/ru-ru/articles/mlaa-efficiently-moving-antialiasing-from-the-gpu-to-the-cpu/" title="MLAA article">MLAA: эффективно переносим антиалиасинг с графического на центральный процессор </a>"</p>
<p> </p>
</div>
<br clear="all" />
<div>
<table bgcolor="#ffffff" width="100%" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td><img height="37" width="531" src="http://software.intel.com/file/25372" /></td>
<td></td>
</tr>
</tbody>
</table>
<table bgcolor="#ffffff" cellpadding="0" bordercolor="#ffffff" cellspacing="6" border="0">
<tbody>
<tr>
<td width="350" valign="top">
<div ><a alt="MLAA_Found_Edges.PNG" href="http://software.intel.com/file/37117" title="MLAA_Found_Edges.PNG"><img src="http://software.intel.com/file/37114" alt="MLAA_Found_Edges_thumb.jpg" title="MLAA_Found_Edges_thumb.jpg" /></a></div>
</td>
<td width="350" valign="top">
<div ><a href="http://software.intel.com/file/37119"><img src="http://software.intel.com/file/37116" alt="MLAA_MLAA_ZB_thumb.jpg" title="MLAA_MLAA_ZB_thumb.jpg" /></a></div>
</td>
</tr>
<tr>
<td >
<p><i> Функция "<i>Show found edges</i>" может быть <br />полезна при отладке метода</i></p>
<i> </i></td>
<td >
<p><i> Экран программы в действии</i></p>
</td>
</tr>
</tbody>
</table>
</div>
<br /><br /><!-- start of 3 column table --> 
<table width="695" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td width="695" rowspan="2" valign="top">
<table width="695" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td valign="top"><img height="8" width="697" src="http://software.intel.com/file/22889" /></td>
</tr>
<tr>
<td valign="top" class="panel_bg_02">
<table width="695" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td width="12" rowspan="2"><img height="8" width="12" src="http://software.intel.com/media/gamedev/_images/blank.gif" /></td>
<td valign="top" height="4"><img height="8" width="8" src="http://software.intel.com/media/gamedev/_images/blank.gif" /></td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td></td>
<td valign="top">
<table width="100%" cellpadding="2" cellspacing="0" border="0">
<tbody>
<tr>
<td width="65%" valign="top" height="19" class="arrow" ><span ><b>Минимальные системные требования</b></span></td>
<td width="35%" valign="top" height="19" class="arrow" ><span ><b><a href="http://software.intel.com/en-us/articles/code/">Другие примеры кода</a></b></span></td>
</tr>
<tr>
<td width="33%" valign="top" height="19" class="arrow" ></td>
<td width="33%" valign="top" height="19" class="arrow" ></td>
</tr>
<tr>
<td width="33%" valign="top" height="19" ><ol type="1">
<li>CPU: Intel® Core™ i5 2го поколения или лучше </li>
<li>GFX: аппаратная поддержка DX10</li>
<li>OS: Microsoft Windows Vista* or Microsoft Windows 7*</li>
<li>MEM: 2 GB RAM или больше</li>
<li>Программное обеспечение: <ol type="1">
<li>DirectX SDK (версия от Июня 2010 или более поздняя)</li>
<li>Microsoft Visual Studio 2008* w/SP1 или Visual Studio 2010*</li>
</ol></li>
</ol>
<p>* Другие наименования и торговые марки могут являться собственностью третьих лиц</p>
</td>
<td width="33%" valign="top" height="19" >
<ul>
<li><a href="http://software.intel.com/en-us/articles/sandy-bridge-samples/" title="Sandy Bridge Samples">Sandy Bridge Samples</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/avx-cloth/" title="AVX Cloth">AVX Cloth</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/onloaded-shadows/" title="Onloaded Shadows">Onloaded Shadows</a></li>
<li><a href="http://software.intel.com/en-us/articles/tickertape/" title="TickerTape">TickerTape Demo</a></li>
<li><a href="http://software.intel.com/ru-ru/articles/smoke-game-technology-demo/" title="Smoke">Smoke Game Technology </a></li>
<li><a href="http://software.intel.com/en-us/articles/ocean-fog-using-direct3d-10/">OceanFog Using Directed3D 10 </a></li>
</ul>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<!--bottom border for large box--> 
<tr>
<td valign="top"><img height="8" width="697" src="http://software.intel.com/media/gamedev/_images/footer-bg-01.gif" /></td>
</tr>
<!--end border-->
</tbody>
</table>
</td>
<td width="10" rowspan="2"><img height="10" width="10" src="http://software.intel.com/media/gamedev/_images/blank.gif" /></td>
</tr>
<tr>
<td></td>
<!--raghava-->
</tr>
<tr>
<td width="344" valign="top"></td>
</tr>
</tbody>
</table>
<!-- end of 3 column table --><br /><br /></div>
</div>
</div>
</td>
<td valign="top" ><!-- RHC --> 
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td width="215" >
<table width="223" cellpadding="0" cellspacing="0" border="0" >
<tbody>
<tr>
<td height="4"><img height="4" width="232" src="http://software.intel.com/file/20516" /></td>
</tr>
<tr>
<td>
<table width="223" cellpadding="0" cellspacing="0" border="0" >
<tbody>
<tr>
<td valign="top" ><a href="http://www.intelsoftwaregraphics.com/?lid=5ceakfXf8Ho=&amp;siteid=cqMoF5H/37o="><img height="71" width="223" src="http://software.intel.com/file/20512" alt="Intel Visual Adrenaline" border="0" title="Intel Visual Adrenaline" /></a></td>
</tr>
<tr>
<td valign="top" >
<table width="223" cellpadding="0" cellspacing="0" border="0" >
<tbody>
<tr>
<td width="11" height="8"></td>
<td width="10" ><img height="5" width="5" src="http://software.intel.com/file/20514" /></td>
<td ><a href="http://software.intel.com/en-us/visual-computing/" title="Intel Adrenaline Developer Community" >Сообщество разработчиков</a></td>
<td width="10"></td>
</tr>
<tr>
<td height="8"></td>
<td ><img height="5" width="5" src="http://software.intel.com/file/20514" /></td>
<td ><a href="http://software.intel.com/partner/home?locale=ru-RU" title="Intel Adrenaline Software Partner Program" >Intel® Software Partner Program</a></td>
<td></td>
</tr>
<tr>
<td height="8"></td>
<td ><img height="5" width="5" src="http://software.intel.com/file/20514" /></td>
<td ><a href="http://www.intel.com/Consumer/Game/index.htm" title="Intel Adrenaline Game On" >Game On</a></td>
<td></td>
</tr>
<tr>
<td height="8"></td>
<td ><img height="5" width="5" src="http://software.intel.com/file/20514" /></td>
<td ><a href="http://www.intelsoftwaregraphics.com/?lid=5ceakfXf8Ho=&amp;siteid=cqMoF5H/37o=" title="Intel Adrenaline Showcase" >Демонстрация</a></td>
<td></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td valign="top" height="7"><img height="7" width="223" src="http://software.intel.com/file/20515" /></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td></td>
</tr>
<tr>
<td valign="top" height="4"><img height="6" width="6" src="http://software.intel.com/file/20494" /></td>
</tr>
<tr>
<td></td>
</tr>
</tbody>
</table>
<br /><center> 
<table cellpadding="0" cellspacing="0" border="0" id="nav_table">
<tbody>
<tr>
<td>
<table width="190" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td width="9"></td>
<td>
<div ><b>Visual Adrenaline</b><br /><a href="http://software.intel.com/sites/billboard/"><img src="http://software.intel.com/file/25369" alt="Download PDF" border="0" /></a><br /><br /><b>Преимущества SIMD</b><br /><a href="http://software.intel.com/en-us/articles/tickertape-part-2/"><img src="http://software.intel.com/file/25665/" alt="Download PDF" border="0" /></a><br /></div>
</td>
<td></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</center><br /><center> 
<table cellpadding="0" cellspacing="0" border="1" id="nav_table">
<tbody>
<tr>
<td>
<table width="190" cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td width="9" class="right_container_hdr2"></td>
<td class="right_container_hdr2"><b>Intel Tools for Unreal Developers <br /><a href="http://software.intel.com/en-us/articles/epic-licenses-tbb-for-ue-licensees/">TBB for Unreal Engine</a></b></td>
<td class="right_container_hdr2"></td>
</tr>
<tr>
<td colspan="3" valign="top" height="4"><img height="4" width="4" src="http://software.intel.com/file/20494" /></td>
</tr>
<tr>
<td width="9" class="right_container_hdr"></td>
<td class="right_container_hdr">
<h4>Related Links</h4>
</td>
<td class="right_container_hdr"></td>
</tr>
<tr>
<td colspan="3" valign="top" height="4"><img height="4" width="4" src="http://software.intel.com/file/20494" /></td>
</tr>
<tr>
<td height="15"></td>
<td valign="middle"><a href="http://www.intel.com/software/graphics" title="Intel Visual Computing Home">Visual Computing Home</a></td>
<td></td>
</tr>
<tr>
<td></td>
<td>
<h3>Intel<sup>®</sup> Technologies</h3>
</td>
<td></td>
</tr>
<tr>
<td></td>
<td valign="top"><a href="http://www.intel.com/software/sandybridge">Sandy Bridge</a><br /><a href="http://software.intel.com/en-us/articles/integrated-graphics/" title="Intel Visual Computing Technologies Integrated Graphics">Graphics</a><br /><a href="http://software.intel.com/en-us/articles/parallel-programming-vc/" title="Intel Visual Computing Technologies Parallel Programming">Parallel Programming</a></td>
<td></td>
</tr>
<tr>
<td colspan="3" valign="top" height="4"><img height="4" width="4" src="http://software.intel.com/file/20494" /></td>
</tr>
<tr>
<td></td>
<td>
<h3>Focus Areas</h3>
</td>
<td></td>
</tr>
<tr>
<td></td>
<td valign="top"><a href="http://software.intel.com/en-us/articles/game-dev/" title="Intel Game Development Focus Area">Game Development</a><br /><a href="http://software.intel.com/en-us/articles/artist-animator/" title="Intel Visual Computing Artist/Animator Focus Area">Artist/Animator</a><br /><a href="http://software.intel.com/en-us/articles/media/" title="Intel Visual Computing Media Focus Area">Media</a></td>
<td></td>
</tr>
<tr>
<td colspan="3" valign="top" height="4"></td>
</tr>
<tr>
<td></td>
<td>
<h3>Develop</h3>
</td>
<td></td>
</tr>
<tr>
<td></td>
<td valign="top"><a href="http://software.intel.com/en-us/articles/tools-vc/" title="Intel Visual Computing Devlopment Tools">Tools</a><br /><a href="http://software.intel.com/en-us/articles/code/" title="Intel Visual Computing Devlopment Code">Code</a></td>
<td></td>
</tr>
<tr>
<td colspan="3" valign="top" height="4"></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</center><!--END right column Content --></td>
</tr>
</tbody>
</table> ]]></description>
      <link>http://software.intel.com/ru-ru/articles/mlaa/</link>
      <pubDate>Thu, 03 Nov 2011 12:00:00 -0700</pubDate>
      <comments>http://software.intel.com/ru-ru/articles/mlaa/#comments</comments>
      <guid isPermaLink="true">http://software.intel.com/ru-ru/articles/mlaa/</guid>
      <category>Сообщество разработчиков графических приложений</category>
      <category>Сообщество разработчиков программного обеспечения</category>
    </item>
  </channel></rss>
