<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Блоги &#187; Разработка софта</title>
	<atom:link href="http://software.intel.com/ru-ru/blogs/category/engineering/feed/" rel="self" type="application/rss+xml" />
	<link>http://software.intel.com/ru-ru/blogs</link>
	<description></description>
	<lastBuildDate>Thu, 24 May 2012 12:16:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.3</generator>
		<item>
		<title>Как и зачем мерить FLOPSы</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/24/flops/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/24/flops/#comments</comments>
		<pubDate>Thu, 24 May 2012 12:16:29 +0000</pubDate>
		<dc:creator>Vladimir Tsymbal (Intel)</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Разработка софта]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/24/flops/</guid>
		<description><![CDATA[Следующий обзор на Хабре - рассматриваются способы измерения производительнcоти вычислительных систем в FLOPS, а также метод измерения FLOPS программной реализации алгоритмов с помощью Intel VTune Amplifier XE. Хабра-ссылка. Как всегда, вопросы можно задавать и здесь, на ISN.]]></description>
			<content:encoded><![CDATA[<p>Следующий обзор на Хабре - рассматриваются способы измерения производительнcоти вычислительных систем в FLOPS, а также метод измерения FLOPS программной реализации алгоритмов с помощью Intel VTune Amplifier XE. <a href="http://habrahabr.ru/company/intel/blog/144388/">Хабра-ссылка</a>. Как всегда, вопросы можно задавать и здесь, на ISN.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/24/flops/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Практическое введение в строковые операции SSE4.2 (STTNI)</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/23/sse42-sttni/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/23/sse42-sttni/#comments</comments>
		<pubDate>Wed, 23 May 2012 12:19:17 +0000</pubDate>
		<dc:creator>chasovshikova</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>
		<category><![CDATA[Intel SSE4.2]]></category>
		<category><![CDATA[STTNI]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/23/sse42-sttni/</guid>
		<description><![CDATA[В рамках конкурса Accelerate 2012 нам всем пришлось хорошенько поработать со строками. Сначала от участников, а затем и от организаторов прозвучала идея, что использование набора инструкций SSE4.2 может придать значительное ускорение. Скажу честно, мы с некоторым недоверием посмотрели на эту идею: — Как мы применим SSE в нашем, еще не существующем, решении? Но внесли её [...]]]></description>
			<content:encoded><![CDATA[<p>В рамках <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-main/">конкурса Accelerate 2012</a> нам всем пришлось хорошенько поработать со строками.<br />
Сначала от участников, а затем и от организаторов прозвучала идея, что использование набора инструкций SSE4.2 может придать значительное ускорение.</p>
<p>Скажу честно, мы с некоторым недоверием посмотрели на эту идею:<br />
<em>— Как мы применим SSE в нашем, еще не существующем, решении?</em><br />
Но внесли её в список todo и, когда код уже начал обретать законченный вид, мы нашли места "приложения рычага".<br />
Потратив всего несколько часов (на чтение документации по STTNI, эксперименты и переписывание) нам удалось ускорить "тяжелые" фрагменты <strong>в два-три раза</strong>!<br />
<span id="more-2007776"></span></p>
<p align="justify">Небольшое предупреждение: не ожидайте что SSE инструкции превратят неэффективный алгоритм в конфетку. Уделяйте внимание прежде всего асимптотике и простоте. Затем беритесь за специальные инструменты для участков кода на которые указал профайлер.</p>
<p>Я постараюсь описать картину крупными мазками. За подробностями загляните в <a href="http://softwarecommunity.intel.com/isn/Downloads/Intel%20SSE4%20Programming%20Reference.pdf">руководство по SSE4 для разработчиков</a>. Хорошая документация по командам и параметрам лежит в <a href="http://msdn.microsoft.com/en-us/library/bb514048.aspx">MSDN</a>. Оба ресурса на английском языке.</p>
<p><strong>Что включает в себя SSE4.2</strong></p>
<ul>
<li>операции с парами строк — именно о них пойдет речь дальше</li>
<li>вычисление CRC32</li>
<li>подсчет количества установленных бит</li>
<li>операция "больше чем" упакованных 64-битных знаковых чисел (Compare Packed Signed 64-bit data For Greater Than)</li>
</ul>
<p><strong>Выбираем форму</strong></p>
<p>Существует 4 инструкции процессора и целых 14 команд поддерживаемых компилятором.<br />
Следующий рисунок поможет разобраться с ними:</p>
<p><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/5-22-2012-21-02-13_cr1.png" alt="" width="224" height="237" class="aligncenter size-full wp-image-2007792" /></p>
<ul>
<li><strong>e</strong> или <strong>i</strong> в середине команды говорит о том, как определена длина входных строк: <strong>e</strong>xplicit&nbsp;—&nbsp;длина указывается явно или <strong>i</strong>mplicit&nbsp;—&nbsp;процессор сам определит длину нуль-терминированной строки.</li>
<li align="justify">суффикс команды определяет тип результата: <strong>i</strong>ndex — значение индекса или <strong>m</strong>ask — битовая маска. Кроме этого, можно получить значение арифметических регистров (выделены синим), которые изменяются в зависимости от содержимого операндов и полученного результата. Это пригодится, если вам, к примеру, интересно не точное местоположение символа в строке, а сам факт его наличия.</li>
</ul>
<p><strong>... и содержание</strong></p>
<p align="justify">Последний аргумент каждой из команд — управляющий байт (imm8 control byte) объясняет как нужно интерпретировать данные и что с ними нужно сделать.</p>
<p>Байт формируется комбинацией флагов которые определяют:</p>
<ul>
<li>формат данных: знаковые или беззнаковые числа? Байты или пары байт (unicode)?</li>
<li>тип сравнения:
<ul>
<li>равенство любых символов — для поиска символов из набора (<code>_SIDD_CMP_EQUAL_ANY</code>)</li>
<li>равенство символов из диапазона — поиск символов из диапазона (<code>_SIDD_CMP_RANGES</code>)</li>
<li>посимвольное сравнение — сравнение строк (<code>_SIDD_CMP_EQUAL_EACH</code>)</li>
<li>упорядоченное сравнение — поиск подстрок (<code>_SIDD_CMP_EQUAL_ORDERED</code>)</li>
</ul>
</li>
<li align="justify">промежуточное преобразование результата (polarity). Подробности в документации. Ниже будет пример с использованием этого флага</li>
<li align="justify">для результата-индекса — нужно ли вернуть наименьший (<code>_SIDD_LEAST_SIGNIFICANT</code>) или наибольший найденный индекс (<code>_SIDD_MOST_SIGNIFICANT</code>)</li>
<li align="justify">для результата-маски — вернуть битовое (<code>_SIDD_BIT_MASK</code>) или целочисленное (<code>_SIDD_UNIT_MASK</code>) её представление.</li>
</ul>
<p><strong>Примеры</strong></p>
<p>Пара примеров прямо из нашего кода. Мы оперируем беззнаковыми байтами в силу специфики решения.</p>
<p>Поиск первого вхождения любого символа из строки:</p>
<pre name="code" class="cpp">
int get_first_index_of_symbol(unsigned char * needle, int needle_length,
	unsigned char * haystack, int haystack_length)
{
	return _mm_cmpestri(
		_mm_loadu_si128((const __m128i*)needle), needle_length,
		_mm_loadu_si128((const __m128i*)haystack), haystack_length,
		_SIDD_UBYTE_OPS | _SIDD_CMP_EQUAL_ANY | _SIDD_LEAST_SIGNIFICANT);
}
</pre>
<p>Длина строк известна и нам нужна позиция символа. Выбор падает на _mm_cmpestri.</p>
<p>Определяемся с флагами:</p>
<ul>
<li>мы оперируем с беззнаковыми байтами — <code>_SIDD_UBYTE_OPS</code></li>
<li>нужно найти вхождения символа в любом месте — <code>_SIDD_CMP_EQUAL_ANY</code></li>
<li>нас интересует первое вхождение — <code>_SIDD_LEAST_SIGNIFICANT</code></li>
</ul>
<p>Все эти флаги можно опустить, поскольку они установлены по умолчанию.</p>
<p>Поиск первого несовпадающего символа для строк равной длины (используется для нахождения длины наибольшего общего префикса):</p>
<pre name="code" class="cpp">
int get_first_unmatch(unsigned char * a, unsigned char * b, int length)
{
	return _mm_cmpestri(
		_mm_loadu_si128((const __m128i*)a), length,
		_mm_loadu_si128((const __m128i*)b), length,
		_SIDD_CMP_EQUAL_EACH | _SIDD_NEGATIVE_POLARITY);
}
</pre>
<p>Обратите внимание, что поменялись только управляющие флаги:</p>
<ul>
<li>нужно посимвольное сравнение — <code>_SIDD_CMP_EQUAL_EACH</code></li>
<li>нас интересует несовпадение — <code>_SIDD_NEGATIVE_POLARITY</code></li>
</ul>
<p>Что получится, если символ не был найден или строки совпадают? Мы получим в ответ 16 (или 8, если бы операции были над строками из двухбайтовых символов).</p>
<p>Чтобы не быть голословной, утверждая что STTNI дает преимущество, я замерила время выполнения участка кода для файла размером 200 Мб:</p>
<ul>
<li>без SSE - 360 мс
<pre name="code" class="cpp">
//i - текущее положение в строке buffer размера size
while (i &lt; size &amp;&amp; buffer[i] != '\n' &amp;&amp; buffer[i] != '&gt;') i++;
//i выходит за пределы строки или указывает на искомый символ
</pre>
</li>
<li>с SSE - 171 мс
<pre name="code" class="cpp">
//i - текущее положение в строке buffer размера size
char * needle = "\n&gt;";
while (i + 15 &lt; size)
{
	int r = get_first_index_of_symbol(
		(unsigned char *)needle, 2,
		(unsigned char *)(buffer + i),
		16);
	i += r;
	if (r != 16) break;
}
while (i &lt; size &amp;&amp; buffer[i] != '\n' &amp;&amp; buffer[i] != '&gt;') i++;
//i выходит за пределы строки или указывает на искомый символ
</pre>
</li>
</ul>
<p>Результат налицо (:</p>
<p>Интересно, а кто еще использовал SSE4.2 в решении? Насколько быстрее оно сделало ваш код?</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/23/sse42-sttni/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Решение задачи конкурса Accelerate 2012 при помощи классического суффиксного дерева</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/22/accelerate-2012-3/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/22/accelerate-2012-3/#comments</comments>
		<pubDate>Tue, 22 May 2012 06:10:09 +0000</pubDate>
		<dc:creator>leventov</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>
		<category><![CDATA[суффиксное дерево]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/22/accelerate-2012-3/</guid>
		<description><![CDATA[Мы выбрали суффиксное дерево в качестве базовой структуры данных для решения задачи конкурса Accelerate 2012, потому что во многих источниках, начиная с английской википедии и заканчивая рефератами, непосредственно касающихся поиска совпадающих частей в геномах, рекомендуется именно суффиксное дерево. Не буду описывать базовый алгоритм Укконена построения дерева, - это уже сделано за меня в огромном количестве [...]]]></description>
			<content:encoded><![CDATA[<p>Мы выбрали суффиксное дерево в качестве базовой структуры данных для решения <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-problem/">задачи конкурса Accelerate 2012</a>, потому что во многих источниках, начиная с <a href="http://en.wikipedia.org/wiki/Longest_common_substring_problem">английской википедии</a> и заканчивая рефератами, непосредственно касающихся поиска совпадающих частей в геномах, рекомендуется именно суффиксное дерево.<br />
<span id="more-2007711"></span><br />
Не буду описывать базовый алгоритм Укконена построения дерева, - это уже сделано за меня в огромном количестве мест, можно найти не одну готовую реализацию на C++. Для наилучшего понимания могу посоветовать эту лекцию на русском языке: <a href="http://yury.name/internet/01ianote.pdf">http://yury.name/internet/01ianote.pdf</a></p>
<p><strong>Построение дерева "оффлайн"</strong><br />
Преимуществом алгоритма Укконена считается возможность построить дерево "онлайн", то есть по строчке заранее неизвестного размера. Мне трудно представить, в какой реальной задаче это нужно, однако большинство открытых реализаций "наращивают" дерево по букве. Оффлайновый вариант проще для понимания и быстрее на 30%.</p>
<p><strong>Дерево только из короткой строки</strong><br />
В конкурсной задаче максимальные общие подстроки (LCS) ищутся <em>только из 2</em> источников. При таком ограничении смысла в так называемом <a href="http://en.wikipedia.org/wiki/Generalised_suffix_tree">обобщенном дереве</a> нет (вот было бы исходных строк 3 - пришлось бы строить обобщенное дерево по 2 из них). Вместо этого можно "пройтись второй строкой" по дереву, построенному по первой, запоминая "места" (пара: позиция в строке - позиция в дереве) максимального совпадения, и после несложной обработки получить из них ответы в <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-problem/">нужном формате</a>.</p>
<p>Строить дерево лучше по более короткой строке, потому что оно занимает очень много памяти.</p>
<pre name="code" class="cpp">typedef unsigned int uint;
// в 64-битных системах размер uint - 4 байта, любой ссылки - 8 байт.
// структура вершины дерева - 48 байт
struct vertex {
        uint left, right; // срез в строке, соотв. подвешивающему эту вершину ребру
        uint length; // right - left, используется очень часто
        // 5 8-байтных ссылок было бы уже слишком накладно
        // vertex *child = tree + v-&gt;children['A']; // арифметика указателей
        uint children[5]; // ACTG$
        // При особом желании можно было бы сэкономить еще 8 байт (8 + 8 - 4 - 4)
        // так же, как в children. Но неудобно, эти ссылки часто используются
        vertex *parent, *link;
};
// Дерево: C-style массив
vertex *tree;
</pre>
<p>Теоретически, для составленной из 4 букв строки длины L суффиксное дерево может содержать от 1,34L до 2L вершин, практический диапазон для генома - 1,6-1,7L. Таким образом, в среднем наше дерево жрет около 48*1,66 = 80 байт на каждую букву строки. Это критичный недостаток с точки зрения формальных конкурсных ограничений: для строки длиной в 2^32 буквы дерево занимает 320 Гб! Но вполне терпимо, если рассматриваются только реальные геномы: скажем, в самой длинной хромосоме человека 250 млн нуклеотидов, ее дерево - "всего" 20 Гб.</p>
<p><strong>Масштабирование</strong><br />
Справедливо отмечается, что построение суффиксного дерева (по крайней мере, алгоритмом Укконена) совсем или почти не поддается распараллеливанию. Мы решили занять ядра процессора проще: разбить самые длинные из входных строк на части, искать максимальные совпадения среди этих частей, а в конце, при необходимости, "склеить" совпадения, касающиеся одной исходной строки. Есть только одна проблема - возможная потеря решений:<br />
<img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/accelerate-split-problem.png" alt="" width="730" class="aligncenter" /><br />
Эту проблему можно решить модификацией (тривиальной при использовании суффиксных деревьев) алгоритма нахождения LCS: пусть он возвращает не только совпадения, большие M (min match length), но и любой длины, заканчивающиеся в конце одной или обоих строк (частей исходных строк, в реальности). Если какое-то большое фактическое совпадение из-за разбиения входных строк придется склеить из K совпадений между частями, то все они, кроме, возможно, последнего, как раз будут заканчиваться в точке разбиения. Например на рисунке выше совпадение 1 заканчивается в точке разбиения 1-й строки. Т. о. после "склейки" еще нужно попробовать продлить полученное совпадение вправо  - максимум на M - 1 символов. В примере выше получится продлить совпадение 1 на длину совпадения 2.</p>
<p>Однако, если на этом ограничиться, количество ответов будет слишком большим (допустим, если одна из строк заканчивается на 'A', придется вывести все позиции буквы A во второй строке, и наоборот). Поэтому мы разбиваем строки с фиксированным перекрытием (OL, overlap): очередная часть начинается на OL символов раньше, чем закончилась предыдущая. Тогда, очевидно, можно не выводить совпадения, заканчивающиеся в конце одной из строк и меньшие OL. Нетрудно заметить, что если M существенно меньше длин входных строк (а в реальности так оно и есть), можно разбивать их с перекрытием M и вообще отказаться от модификации алгоритма нахождения LCS.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/22/accelerate-2012-3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JNI-сигнатуры методов в Java</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/21/jni-java/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/21/jni-java/#comments</comments>
		<pubDate>Mon, 21 May 2012 13:17:03 +0000</pubDate>
		<dc:creator>Sergey Melnikov (Intel)</dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JNI]]></category>
		<category><![CDATA[С++]]></category>
		<category><![CDATA[Си++]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/21/jni-java/</guid>
		<description><![CDATA[Буквально на днях на ISN была затронута тема взаимодействия java и native. Я решил продолжить данный тренд и написать о том, что было для меня самым сложным на первом этапе при работе с jni. Это всего лишь формат сигнатур методов которые необходимо указывать, чтобы получить экземпляр jmethodID. К сожалению, не всегда возможно/удобно использовать javap, так [...]]]></description>
			<content:encoded><![CDATA[<p>Буквально на днях на ISN <a href="http://software.intel.com/ru-ru/blogs/2012/05/12/java-like-c/">была</a> <a href="http://software.intel.com/ru-ru/blogs/2012/05/15/cc-like-java/">затронута</a> тема взаимодействия java и native. Я решил продолжить данный тренд и написать о том, что было для меня самым сложным на первом этапе при работе с jni. Это всего лишь формат сигнатур методов которые необходимо указывать, чтобы получить экземпляр jmethodID. К сожалению, не всегда возможно/удобно использовать javap, так что умение писать jni-сигнатуры достаточно полезно.<br />
Как и положено, для начала посмотрим документацию на <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html">sun.com</a>:</p>
<blockquote>
<table border="1">
<tr>
<td>Type Signature</td>
<td>Java Type</td>
</tr>
<tr>
<td>Z</td>
<td>boolean</td>
</tr>
<tr>
<td>B</td>
<td>byte</td>
</tr>
<tr>
<td>C</td>
<td>char</td>
</tr>
<tr>
<td>S</td>
<td>short</td>
</tr>
<tr>
<td>I</td>
<td>int</td>
</tr>
<tr>
<td>J</td>
<td>long</td>
</tr>
<tr>
<td>F</td>
<td>float</td>
</tr>
<tr>
<td>D</td>
<td>double</td>
</tr>
<tr>
<td>L fully-qualified-class ;</td>
<td>fully-qualified-class</td>
</tr>
<tr>
<td>[ type</td>
<td>type[]</td>
</tr>
<tr>
<td>( arg-types ) ret-type</td>
<td>method type</td>
</tr>
</table>
<p> For example, the Java method:<br />
long f (int n, String s, int[] arr); </p>
<p> has the following type signature:<br />
(ILjava/lang/String;[I)J </p>
</blockquote>
<p>Хм.. не густо и не особо понятно. Попробую описать своими словами.</p>
<p>JNI-сигнатура состоит из списка типов формальных параметров и типа возвращаемого значения. Формат записи похож на Pascal-style определения методов: сначала параметры потом возвращаемое значение (к слову,  не многие знают что многие концепции Java были скопированы с языка и операционной системы Oberon являющихся дальнейшим развитием идей Н.Вирта, создателя Pascal). В JNI-сигнатурах пробелы запрещены - каждый символ имеет значение и не может быть выброшен без потери смысла. Элементы списка параметров не разделяются никакими символами. О обозначении типов формальных параметров ниже. Условно можно выделить 4 правила:</p>
<ol>
<li>Примитивные типы: кодируются соответствующей буквой латинского алфавита (см.таблицу выше):</li>
<li>Массивы: для обозначения того, чо будет передаваться не скалярный тип, а массив используется символ "[". Находящееся правее квадратной скобки обозначение типа - тип элемента массива. Размер массива не обозначается.</li>
<li>Ссылочные типы (объекты): Обозначение класса начинается с заглавной латинской L после которой идет без пробелов полное имя класса, состоящее из: пакета (вложенные пакеты разделяются слешами) и имени класса (также разделенных слешем). Запись является регистро-зависимой. После имени класса должна обязательно идти запятая, являющаяся окончанием определения типа. Обращаю внимание, что опускать запятую нельзя (в том числе и в описании типа возвращаемого значения метода).</li>
<li>Возвращаемый тип void (т.е. ничего) заменяются символом "V"</li>
</ol>
<p>Теперь продемонстрирую это на практике:</p>
<blockquote><p>1. int MyMethod1(float a, char b) -&gt; (FC)I</p></blockquote>
<blockquote><p>2. int[] MyMethod2(long[] a) -&gt; ([J)[I</p></blockquote>
<blockquote><p>3. void MyMethod3(package1.subpackage2.MyClass[] a, String b) -&gt; (Lpackage1/subpackage2/MyClass;Ljava/lang/String;)V</p></blockquote>
<blockquote><p>4. package1.subpackage2.MyClass[] MyMethod4(String a, Class[] b, float c) -&gt; (Ljava/lang/String;[java/lang/Class;F)[Lpackage1/subpackage2/MyClass;</p></blockquote>
<p>Так что магические на первый взгляд последовательности символов оказываются достаточно просты (с отличии от сигнатур в языке C).</p>
<p>P.S. Набирал эту заметку на планшете в метро. Оказалось, планшеты вполне пригодны для создания контента. Хотя над удобством клавиатуры андроида еще можно поработать))</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/21/jni-java/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Интересные подробности решения команды X!</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/21/x/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/21/x/#comments</comments>
		<pubDate>Mon, 21 May 2012 09:40:47 +0000</pubDate>
		<dc:creator>chasovshikova</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/21/x/</guid>
		<description><![CDATA[Привет всем! Я решила рассказать о том, что не вошло в наше отправленное решение на конкурс Accelereate 2012 из-за ограничения на размер. Для начала рекомендую прочитать краткую версию, чтобы быть в контексте. Итак: Получение бинарного кода символа Поскольку символов во входной последовательности всего 4, то их можно закодировать всего двумя битами. Решение "в лоб": switch [...]]]></description>
			<content:encoded><![CDATA[<p>Привет всем!</p>
<p>Я решила рассказать о том, что не вошло в наше отправленное решение на <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-main/">конкурс Accelereate 2012</a> из-за ограничения на размер.<br />
Для начала рекомендую прочитать <a href='http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/readme.pdf'>краткую версию</a>, чтобы быть в контексте.</p>
<p>Итак:</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/1283278043_1283276920_bazzinga-208.jpg"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/1283278043_1283276920_bazzinga-208-300x248.jpg" alt="" width="300" height="248" class="alignnone size-medium wp-image-2007762" /></a></p>
<p><strong>Получение бинарного кода символа</strong></p>
<p>Поскольку символов во входной последовательности всего 4, то их можно закодировать всего двумя битами.<br />
Решение "в лоб":</p>
<pre name="code" class="cpp">
switch (c)
{
case 'A':
	return 0;
case 'C':
	return 1;
case 'G':
	return 2;
case 'T':
	return 3;
}
</pre>
<p>Очень просто, но наличие ветвления в таком "популярном" месте не радует. Попробуем посмотреть внимательно на символы:</p>
<pre name="code" class="cpp">A = 0x41 = 1000(00)1
C = 0x43 = 1000(01)1
G = 0x47 = 1000(11)1
T = 0x54 = 1010(10)0</pre>
<p>Видно, что выделенные биты у каждого символа разные, поэтому switch можно заменить на</p>
<pre name="code" class="cpp">return c &gt;&gt; 1 &amp; 3;</pre>
<p><strong>Хранение тестовых последовательностей</strong></p>
<p align="justify">В промежуточном решении для хранения координаты суффикса из тестовой строки мы использовали пару <em>&lt;#тестовой&nbsp;строки, индекс&gt;</em>. Это неэффективно с точки зрения использования памяти (нужно 8 байт на каждый суффикс).</p>
<p align="justify">Немного поразмыслив, мы переписали работу с тестовыми строками: все они хранятся в сцепленном (concatenated) виде. Для разделения существует массив с информацией о длинах. Это позволило вдвое уменьшить размер структуры хранения суффиксов и на четверть — массива результатов (в одной записи результата мы теперь храним всего три 4-х байтовых числа - индексы рефересной и тестовой строк и длину).</p>
<p><strong>Параллельное чтение файлов</strong></p>
<p align="justify">Мы пытались реализовать чтение с помощью TBB Pipeline. Но, к сожалению, при использовании pipeline возникали странные артефакты в виде неправильного определения длины файла в Windows (на первый взгляд — из-за трактовки переносов строк). Наверняка рецепт исправления был прост, но поскольку времени уже не оставалось, решили просто обернуть процедуру чтения в OpenMP parallel for вместе с помещением чтения файла в критическую секцию.</p>
<p><strong>Вывод результатов</strong></p>
<p align="justify">Довольно значительную часть времени вывода результата занимает преобразование числа в строку. Для ускорения этого преобразования мы использовали стороннюю библиотеку <a href="http://code.google.com/p/stringencoders/">stringencoders</a>, а также кешировали последнее значение индекса окончания рефересной строки (именно он — "главный" в сортировке, поэтому при большом количестве результатов велика вероятность повторения).</p>
<p>Если у вас есть какие-либо вопросы — смело пишите. Я отвечу в комментариях или, если это будет возможно, дополню пост.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/21/x/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Вариант решения задачи конкурса Accelerate 2012</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/19/accelerate-2012-2/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/19/accelerate-2012-2/#comments</comments>
		<pubDate>Sat, 19 May 2012 13:17:22 +0000</pubDate>
		<dc:creator>flash2048</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/19/accelerate-2012-2/</guid>
		<description><![CDATA[Совсем недавно закончился конкурс Accelerate Your Code 2012. Решил поделиться своим решением. Конечно, вряд ли оно будет оптимальным, но некоторые идеи могут пригодиться… Не буду приводить условие задачи, его можно найти на этой странице. Идея моего решения, заключалась в постройке хэш-таблицы для ref-строки. Хэш строил по 6 символам, это связано с тем, что минимальная длина [...]]]></description>
			<content:encoded><![CDATA[<p>Совсем недавно закончился конкурс <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-main/">Accelerate Your Code 2012</a>. Решил поделиться своим решением. Конечно, вряд ли оно будет оптимальным, но некоторые идеи могут пригодиться…<br />
Не буду приводить условие задачи, его можно найти на <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-problem/">этой странице</a>.<br />
Идея моего решения, заключалась в постройке хэш-таблицы для ref-строки. Хэш строил по 6 символам, это связано с тем, что минимальная длина M равна 6.<br />
Если находить хэши из строк, состоящих их 6 символов, из алфавита, включающего в себя 4 символа (A, C, G, T), то всего получится 4^6  = 4096 возможных вариантов.<br />
Моя функция хэширования приобрела следующий вид:</p>
<pre name="code" class="cpp">int digit(char a1, char a2, char a3, char a4, char a5, char a6){
	int d = 0;
//1
	switch (a1){
		case 'C':
			d += 1024;
		break;
		case 'G':
			d += 2048;
		break;
		case 'T':
			d += 3072;
		break;

		default:  break;
	}
//2
	switch (a2){
		case 'C':
			d += 256;
		break;
		case 'G':
			d += 512;
		break;
		case 'T':
			d += 768;
		break;
		default:  break;
	}
//3
	switch (a3){
		case 'C':
			d += 64;
		break;
		case 'G':
			d += 128;
		break;
		case 'T':
			d += 192;
		break;
		default:  break;
	}
//4
	switch (a4){
		case 'C':
			d += 16;
		break;
		case 'G':
			d += 32;
		break;
		case 'T':
			d += 48;
		break;
		default:  break;
	}
//5
	switch (a5){
		case 'C':
			d += 4;
		break;
		case 'G':
			d += 8;
		break;
		case 'T':
			d += 12;
		break;
		default:  break;
	}
//6
	switch (a6){
		case 'C':
			d += 1;
		break;
		case 'G':
			d += 2;
		break;
		case 'T':
			d += 3;
		break;
		default:  break;
	}
	return d;
}</pre>
<p>На основе хэша, в данном случае легко получить исходную строку, но для решения поставленной задачи этого не требовалось.<br />
Для хранения хэшей ref-строки использовались вектора: <code>vector&lt;size_t&gt; **L = new vector&lt;size_t&gt;*[4096];</code><br />
Для нахождения общих подстрок использовался такой же принцип, как и в предоставленном решении, только вместо массива использовались 2 строки.<br />
Вот собственно и все…</p>
<p><em>С нетерпением жду решение победителя, очень хочется увидеть оптимальный вариант решения данной задачи.</em></p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/19/accelerate-2012-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Поиск одинаковых участков в нуклеотидных цепочках с помощью индексации</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/19/2007637/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/19/2007637/#comments</comments>
		<pubDate>Sat, 19 May 2012 06:10:02 +0000</pubDate>
		<dc:creator>dyam</dc:creator>
				<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>
		<category><![CDATA[openmp]]></category>
		<category><![CDATA[конкурс]]></category>
		<category><![CDATA[Конкурсы и мероприятия]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/19/2007637/</guid>
		<description><![CDATA[Данный пост написан в рамках конкурса Accelerate Your Code 2012. Самый простой и самый не быстрый из методов поиска одинаковых участков в нуклеотидных цепочках - "наивный" перебор строк со смещением. Он хорошо подходит для коротких цепочек, так как не требует предварительной обработки и дополнительных объемов памяти. Но требование O(nm) по времени в общем случае нас [...]]]></description>
			<content:encoded><![CDATA[<p>Данный пост написан в рамках конкурса <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">Accelerate Your Code 2012</a>.</p>
<p>Самый простой и самый не быстрый из <a href="http://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D0%B8%D1%81%D0%BA_%D0%BF%D0%BE%D0%B4%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B8">методов</a> поиска одинаковых участков в нуклеотидных цепочках - "наивный" перебор строк со смещением. Он хорошо подходит для коротких цепочек, так как не требует предварительной обработки и дополнительных объемов памяти.<br />
Но требование O(nm) по времени в общем случае нас не устроит, потому будем подбирать другие варианты.</p>
<p><strong>Подготовка данных</strong><br />
Для начала подумаем об ускорении используя подготовку данных.<br />
Так как мы будем проводить поиск в нуклеотидных цепочках, мы можем ограничить алфавит или же понизить битность на один символ. Всего таких символов у нас 4, а именно АЦГТ (ACGT). Это дает возможность использовать всего 2 бита вместо 8 (ANSII). Каким же образом построить соответствие? Если у нас нет дополнительных наполеоновских планов, можно в любом, но если вспомнить что эти нуклеотиды составляют пары (А-Т, Ц-Г) лучше на будущее провести такие аналогии:</p>
<ul>
<li>A = 00<sub>2</sub>=0</li>
<li>T = 11<sub>2</sub>=3</li>
<li>C = 01<sub>2</sub>=1</li>
<li>G = 10<sub>2</sub>=2</li>
</ul>
<p>Таким незамысловатым способом мы бы могли решить одну маленькую проблему - проверку пары нуклеотидов на комплементарность. В общем случае на это приходилось бы 8 проверок, в нашем же - только одна, проверка суммы значений на равенство 3. Уже неплохое ускорение в 8 раз. Правда времени потратим О(N). Ну да ладно - не смертельно, все-равно надо от мусора (перевод строк или пробелы) избавляться при считывании из неподготовленных данных.</p>
<p>Теперь подумаем вот о чем: а почему бы нам каким-нибудь образом сравнивать не по одной паре, а сразу по несколько. Ну что же, на помощь к нам придут хеш-функции для задания соответствия некоторой цепочки нуклеотидов длиной W и целого натурального числа. Таким образом, если два значения хеш-функции не совпадают, значит и цепочки гарантированно разные.<br />
С равенством возможны варианты. Если использовать хеш-функции без коллизий (например, представление цепочки как число по основанию 4), мы также сможем гарантировать и идентичность цепочек. Такая хеш-функция будет цикличной, что позволит использовать предыдущее рассчитанное значение при смещении на 1 нуклеотид. Но тут возникают некоторые трудности с хранением данных - в переменную размером К бит мы сможем сохранить только К/2 нуклеотида. Затраты на подготовку О(N), при использовании цикличных хеш-функций типа такой:</p>
<pre name="code" class="cpp">int32_t W_ = W - 1;
for (i = 0; i &lt; W_; i++) {
	key = ((key &lt;&lt; 2) + Seq[i]) &amp; mask;
}
for (i = W_, p = 0; i &lt; Seq_l; i++, p++) {
	key = ((key &lt;&lt; 2) + Seq[i]) &amp; mask;
	Table_Key[p] = key;
}</pre>
<p>Для снятия ограничения на максимальную длину цепочки можем, например, использовать несколько различных хеш-функций с коллизиями для уменьшения вероятности совпадений коллизий. Или просто проверить совпадение на идентичность.</p>
<p>Но ведь нам все-равно придется сравнивать одно и тоже по несколько раз. А если нам надо сравнить не одну пару, а некий набор пар в которые входит одна и та же строка? Для ускорения поиска можем построить индекс позиций подстрок у которых значения хеш-функций (одной или нескольких) совпадают. Этот этап мы можем организовать по-разному.</p>
<p><u>Первый способ</u><br />
После превращения цепочки в массив хешей, каждый элемент записать как пару хеш-позиция. Дальше нам предстоит отсортировать этот массив по порядку значений хешей. Затраты по времени от O(N*Log(N)).<br />
На этом можно и остановиться, а можно для ускорения поиска по нему преобразовать в двумерный массив. В нем элемент первого порядка будет соответствовать значению хеш-функции, а элементы второго порядка - позициям. При необходимости можно отсортировать и массив позиций - необходимо, если надо искать свертку одной строки на себя же (комплементарная связь, а не идентичность).<br />
Такой способ подойдет в том случае если значения хеш-функции находятся в большом диапазоне величин, но используемых намного меньше от суммарного.<br />
Поиск нужного значения хеш-функции возможен либо через бинарный поиск, либо через индексацию диапазонов с последующим бинарным поиском.</p>
<p><u>Второй способ</u><br />
По-другому можем поступить в случае, если хеш-функция имеет ограниченный в разумных рамках диапазон величин.<br />
Для примера используем хеш-функцию без коллизий, приведенную выше. В ней диапазон величин будет зависеть только от длины цепочки W, для которой мы строим соответствие. И этот диапазон составит от 0 до 4<sup>W</sup>-1. Таким образом мы сможем воспользоваться уже "линейной" сортировкой с временем О(N) для построения двумерного массива соответствий значений хеш-функции и позиции.<br />
Смысл простой. Создаем два массива: линейный S размером 4<sup>W</sup> и двумерный T размером по первому индексу также 4<sup>W</sup>, а по второму как получится (см. ниже).<br />
На первом этапе воспользуемся массивом S для построения гистограммы вхождений значений хеш-функции в массив за время O(N).<br />
На втором этапе выделяем память для подмассивов Т на количество элементов, указанных в соответствующей ячейке S.<br />
Для окончательного заполнения будем использовать массив S как счетчик заполненности. В Т первый индекс будет соответствовать значению хеш-функции, второй берем из S, значение - позиция хеша в начальном массиве. Время, затраченное на заполнение, также О(N).<br />
При желании и необходимости этот массив можно "причесать" - для хеш-функций соответствующих однородным строкам (АА..АА и подобных) выкинуть часть позиций по некоторым правилам.<br />
В итоге за линейное время получим двумерный массив-индекс Т для быстрого определения положений подстроки длиной W по части (так же длиной W) заданного шаблона.<br />
Затраты по времени общей подготовки О(N). Затраты на дополнительную память зависят от выбранной длинны W: массив хешей будет больше начальной строки в 2-4-... раза в зависим от выбранного типа данных (16, 32 и больше бит). Массив S будет содержать 4<sup>W</sup> элементов размером от 8 до 32+ бит в зависимости от размеров строк. Массив Т будет занимать по объему примерно такой же объем как S (первый уровень индексации) и начальный массив суммарно.<br />
Поиск нужного значения хеш-функции будет происходить за время О(1), но накладные затраты (массив S и первый уровень массива Т) могут быть больше чем полезная нагрузка (списки позиций).</p>
<p><strong>Поиск совпадений</strong><br />
Для коротких строк достаточно построение массива хеш-функций подстрок длиной W, с последующим расширением совпадающим участков по 1 или W нуклеотидов.<br />
Для длинных строк возможно будет иметь смысл сравнивать два массива индексов - только те позиции подстрок, хеши которых будут присутствовать в обоих.<br />
Разделение строк на длинные и короткие разное для разных случаев. И зависит от выигрыша в скорости работы.</p>
<p><strong>Сравнение с другими методами</strong><br />
Казалось бы, что приведенный выше метод не очень хорош, так как дает только "точки соприкосновения" двух подстрок. И дальнейшее расширение до необходимой минимальной длины может "съесть" все ускорение. Но:</p>
<ul>
<li>иногда минимальная необходимая длина достаточно маленькая (W = W<sub>min</sub>), что бы массив индексов был не столь уж и большим.</li>
<li>алгоритм поиска с помощью суффиксных деревьев/массивов точно потребует объем в много раз превышающий размер входных данных, при сравнимых скоростях поиска</li>
</ul>
<p><strong>Масштабируемость</strong><br />
В наше время когда корабли... О чем это мы... ах да... в наше время когда в каждом ПК минимум по 2 ядра, можно подумать и о параллельной обработке.<br />
Сразу оговорюсь, что использовал OpenMP. Ибо до этого параллельным программированием не пользовался, а данная технология оказалось весьма и весьма... простой в понимании и в реализации...<br />
Рассмотрим основные этапы алгоритма и мое решение по распараллеливанию.</p>
<p><u>1. Создание массива хешей подстрок</u><br />
Ну тут все довольно просто - делим начальную строку на участки, каждый участок своему потоку. Учитываем при этом, что на построение N хешей необходимо N+W-1 нуклеотидов. То есть нарезка происходит с перехлестами. Запись готовых результатов тоже разделена в памяти. Как дополнительные вычисления на каждый поток будет просчет первых W-1 хешей недостаточной длины. Но это явно мелочи по сравнению с выгодой в скорости. Но и кидаться в крайности тоже не стоит - должно выполняться условие ограничение разбиения P*W &lt;&lt; N, где P - количество участков.</p>
<p><u>2. Индексация подстрок</u><br />
Эту проблему я решил так же разбиением начального массива уже готовых хешей на несколько участков. Для разделения участков записи в конечных индекс воспользовался массивами счетчиками для каждого потока, с последующим суммирование нужных и использованием значения как сдвиг начала при записи. Основной проигрыш - использование дополнительных массивов под гистограммы и доступ к нужной ячейке с помощью сдвига. Тут тоже стоит себя ограничивать в бездумном дроблении - каждый массив под гистограмму имеет 4<sup>W</sup> элементов от 8 до 64 бит. Что при большом количестве потоков может занимать значимый объем памяти.</p>
<p><u>3. Поиск совпадений</u><br />
Тут еще проще - всю работу можно разделить на задания которые между собой не сильно связаны. Таким заданием, в случае сравнения массива и индекса, будет проверка совпадений для подстроки из шаблона и списка позиций возможных совпадений из индекса. Их будет приличное количество - динамическое распределение работы между потоками позволит адекватно балансировать нагрузку.<br />
Для сравнения двух индексов неделимым заданием может быть как сравнения списка позиций для совпадающих хешей, так и более мелким - просчет результата для пары из возможного списка комбинаций позиций. Это решит проблему перекоса в случае если в обоих индексах некоторые значения хеш-функций будут содержать значительно большее количество позиций, чем остальные.<br />
Еще вариант при котором будем отсекать некоторые варианты, если необходимо найти совпадение подстрок длиной намного больше чем 2*W. При этом возьмем две подстроки длиной W на таком расстоянии L, так что бы 2*W + L = W<sub>min</sub>. Дальше будем сравнивать массивы позиций из индекса для хешей этих двух строк. Если расстояние между определенными элементами будет также равно L - возможно совпадение больших подстрок, что подтвердится полной проверкой. Оценка по времени такой проверки на совпадения О(Log(n<sub>1</sub>)*Log(n<sub>2</sub>)), где n<sub>i</sub> - длины списков.<br />
Чтобы не использовать критические секции, для каждого потока можно использовать отдельные массивы для хранения найденных результатов. С последующим слиянием и сортировкой/выборкой за время O(К + К*Log(К)).</p>
<p>Общее время алгоритма стремится к О(N + K + K*Log(K)), где N общий объем входных данных, К - объем не фильтрованных выходных данных.</p>
<p>Данный пост написан в рамках конкурса <a href="http://software.intel.com/fr-fr/articles/AYC-early2012_home/">Accelerate Your Code 2012</a>.<br />
Спасибо за внимание. ЦУ буду рад прочитать в комментариях.</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/19/2007637/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Использование s-дерева для нахождения общих подстрок генетических последовательностей</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/18/s/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/18/s/#comments</comments>
		<pubDate>Fri, 18 May 2012 11:15:23 +0000</pubDate>
		<dc:creator>iamfullofspam</dc:creator>
				<category><![CDATA[Академическое сообщество]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>
		<category><![CDATA[s-дерево]]></category>
		<category><![CDATA[s-массив]]></category>
		<category><![CDATA[задача поиска общих подстрок]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/18/s/</guid>
		<description><![CDATA[В&#160;этой статье мы&#160;опишем разработанный нами метод решения задачи с конкурса параллельного программирования Accelerate 2012. В&#160;задаче требовалось найти наибольшие общие подстроки у&#160;двух генетических последовательностей (то&#160;есть строк, состоящих из&#160;символов A,G,T,C), превосходящие по&#160;длине наперёд заданную величину M&#160;&#8805; 6. Пожалуй, первая мысль человека, знакомого с&#160;алгоритмами поиска подстроки&#160;&#8212; построить суффиксное дерево, однако после просмотра ограничений на&#160;размеры строк (а&#160;именно&#160;&#8212; они не&#160;длиннее [...]]]></description>
			<content:encoded><![CDATA[<div style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;font-size: 13px">
<p>В&nbsp;этой статье мы&nbsp;опишем разработанный нами метод решения задачи с <a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-problem/"> конкурса параллельного программирования Accelerate 2012.</a> В&nbsp;задаче требовалось найти наибольшие общие подстроки у&nbsp;двух генетических последовательностей (то&nbsp;есть строк, состоящих из&nbsp;символов A,G,T,C), превосходящие по&nbsp;длине наперёд заданную величину M&nbsp;&#8805; 6. </p>
<p>Пожалуй, первая мысль человека, знакомого с&nbsp;алгоритмами поиска подстроки&nbsp;&mdash; построить суффиксное дерево, однако после просмотра ограничений на&nbsp;размеры строк (а&nbsp;именно&nbsp;&mdash; они не&nbsp;длиннее 2<sup>32</sup>) мысль отпадает. Несмотря на&nbsp;небольшую константу в&nbsp;оценке O(n), размер дерева будет весьма велик, и&nbsp;кэш процессора практически не&nbsp;будет задействоваться. В&nbsp;итоге перемещение по&nbsp;дереву станет довольно медленным.</p>
<p>Для построения суффиксного дерева есть линейные алгоритмы, например, <a href="http://yury.name/internet/01ianote.pdf">алгоритм Укконена</a>, однако с&nbsp;распараллеливанием у&nbsp;них дела обстоят не&nbsp;самым радужным образом. </p>
<p>Это ни&nbsp;в&nbsp;коем случае не&nbsp;приговор суффиксным деревьям. Наверняка будут эффективные решения на&nbsp;их&nbsp;основе&nbsp;&mdash; результаты конкурса покажут. Мы&nbsp;просто изложили соображения, которые подтолкнули нас к&nbsp;поиску другой структуры данных.</p>
</p>
<p>Обратимся к&nbsp;<a href="http://habrahabr.ru/post/115346/">суффиксному массиву</a>&nbsp;&mdash; другому варианту представления строки.<br />
Суффиксный массив весьма компактен, однако строится непросто.<br />
Основной сложностью является то, что при упорядочивании его элементов нам приходится сравнивать строки, а&nbsp;на&nbsp;последних этапах суффиксы, находящиеся в&nbsp;суффиксном массиве достаточно близко, имеют достаточно длинные совпадающие начала. </p>
<p>Эта проблема решается, но&nbsp;пока что нас это интересовать не&nbsp;будет.</p>
</p>
<p>Возьмем строку <i>mississippi</i> (кстати, как раз алфавит из&nbsp;четырех символов!), и&nbsp;рассмотрим её суффиксный массив: </p>
<p><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree1.png" width="200" /></p>
<p>Теперь представьте, как мы&nbsp;<a href="http://ru.wikipedia.org/wiki/Суффиксный_массив">будем искать</a> в&nbsp;нем строку&nbsp;&mdash; мы&nbsp;будем применять бинарный поиск, каждый раз сравнивая нашу строку с&nbsp;очередным суффиксом.<br />
Строка длинная, её суффиксный массив, соответственно, тоже&nbsp;&mdash; а&nbsp;значит, кэш нам уже вряд&nbsp;ли поможет. Хотелось&nbsp;бы завести простую структуру, которая смогла&nbsp;бы нам указать небольшой сегмент, где нам стоит искать нашу подстроку.</p>
<p>Решение в&nbsp;виде следующей структуры-подсказки очевидно:</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree2.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree2-300x207.png" alt="Структура-подсказка" width="300" height="207" class="alignnone size-medium wp-image-2007642" /></a></p>
<p>Структура просто показывает сегмент суффиксного массива, где находятся суффиксы, начинающиеся с&nbsp;определенного символа.</p>
<p>Давайте вспомним, что нам необходимо найти совпадение длины M. Заменим суффиксный массив на&nbsp;массив, &laquo;элементами&raquo; которого будут подстроки длины M. Он&nbsp;несильно отличается от&nbsp;суффиксного массива, как и&nbsp;техника его построения.<br />
В&nbsp;дальнейшем будем его называть <strong>s-массив</strong>.</p>
<p>s-массив сроки <i>missmississippi</i> (внимание&nbsp;&mdash; мы&nbsp;удлинили строку) для M=4 будет выглядеть так:</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/streesarray.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/streesarray-184x300.png" alt="s-массив" width="184" height="300" class="alignnone size-medium wp-image-2007651" /></a></p>
<p>Теперь структуру-подсказку можно немного модифицировать, и&nbsp;она может подсказывать, используя, скажем, две-три первых буквы строки:</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree41.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree41.png" alt="" width="500" class="alignnone size-full wp-image-2007645" /></a></p>
<p>Ещё один очевидный плюс от&nbsp;использования подсказки&nbsp;&mdash; мы&nbsp;можем теперь не&nbsp;сравнивать первые символы с&nbsp;элементами s-массива, поскольку мы&nbsp;уже установили, что они совпадают.</p>
<p>Однако что нам мешает сделать подсказку длины M? Понятно, что при M&gt;17, оперативной памяти может уже не&nbsp;хватить. Но, скажем, подсказку длины 14 построить можно.Ответ состоит в&nbsp;весьма возможной неравномерности распределения подстрок, то&nbsp;есть подстроки какого-то вида встречаются сильно чаще других. Это довольно характерно для генетических последовательностей. </p>
<p>В&nbsp;такой ситуации вероятен сценарий, когда&nbsp;б<em>о</em>льшая часть элементов длинной подсказки ведут к&nbsp;пустым сегментам, а&nbsp;часть&nbsp;&mdash; к&nbsp;сегментам большой длины, где поиск становится неэффективным (симптомы такого поведения заметны на&nbsp;предыдущей схеме).</p>
<p>Решение напрашивается: введем структуру-подсказку для длинных сегментов. Таким образом, структуры-подсказки образуют дерево, которое мы&nbsp;и&nbsp;назовем <strong>s-деревом</strong>. (В&nbsp;примере подсказки используют только один символ из&nbsp;соображений наглядности&nbsp;&mdash; ничто не&nbsp;мешает корню s-дерева использовать на&nbsp;реальных данных сразу 5 символов для подсказки, и&nbsp;это весьма эффективно). Количество символов,используемых подсказкой, назовем <strong>количеством ярусов</strong> узла s-дерева.</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree7.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree7.png" alt="Двухуровневое s-дерево" width="600" class="alignnone size-full wp-image-2007649" /></a></p>
<h2>Паралельное построение s-дерева и&nbsp;s-массива.</h2>
<p>Ранее мы&nbsp;подразумевали, что s-массив уже построен, и&nbsp;мы&nbsp;лишь строим структуру для ускорения поиска в&nbsp;нем. Однако все ещё интереснее&nbsp;&mdash; s-дерево и&nbsp;будет помогать строить s-массив. </p>
<p>Изначально произвольным образом инициализируем s-массив:</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree3.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree3-213x300.png" alt="Инициализированный s-массив" width="213" height="300" class="alignnone size-medium wp-image-2007643" /></a></p>
<p>И&nbsp;начнем строить дерево от&nbsp;корня: </p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree5.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree5.png" alt="Развернули корневой элемент s-дерева" width="500" class="alignnone size-full wp-image-2007647" /></a></p>
<p>Заметим, что фактически мы&nbsp;разбили элементы s-массива по&nbsp;первому символу. Эта процедура проводится сортировкой подсчетом (независимо от&nbsp;того, сколько ярусов имеет узел) и&nbsp;занимает линейное время. Заметим попутно, что трудность, связанная с&nbsp;необходимостью посимвольного сравнения достаточно близких суффиксов, исчезла автоматически. </p>
<p>Далее продолжаем иерархически упорядочивать s-массив: </p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree6.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/stree6-1024x666.png" alt="После разворачивания узлов" width="600" class="alignnone size-large wp-image-2007648" /></a></p>
<p>На&nbsp;схеме выше мы&nbsp;<strong>развернули</strong> узлы <em>i</em> и&nbsp;<em>s&nbsp;</em>. То&nbsp;есть будем считать, что раньше эти узлы были <em>неразвернуты</em>, а&nbsp;теперь&nbsp;&mdash; <em>развернуты</em>. </p>
<p>В&nbsp;нашем примере увеличивать глубину дерева особенного смысла нет. Обратите внимание на&nbsp;то, что наш массив ещё не&nbsp;до&nbsp;конца упорядочен:</p>
<p><a href="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/streealgvsarray.png"><img src="http://software.intel.com/ru-ru/blogs/wordpress/wp-content/uploads/streealgvsarray.png" alt="Сравнение." width="700" class="alignnone size-full wp-image-2007650" /></a></p>
<p>Можно провести упорядочивание до&nbsp;конца, отсортировав элементы в&nbsp;одном листе, но&nbsp;мы&nbsp;это не&nbsp;делали. </p>
<p>Таким образом, процедура поиска строки в&nbsp;s-дереве довольно проста </p>
<ol>
<li>Иерархически идем по&nbsp;дереву, пока это возможно.</li>
<li>Если мы&nbsp;дошли до&nbsp;листа, досравниваем свою строку со&nbsp;всеми	элементами суффиксного массива, относящимися к&nbsp;данному листу. Досравниваем&nbsp;&mdash; потому что начала нам уже сравнивать не&nbsp;надо.</li>
</ol>
<h2>Оптимизации</h2>
<p>Наверное, у&nbsp;читателя пока что складывается ощущение, что такая структура крайне неповоротлива. Покажем, как её можно адаптировать под разные ситуации и&nbsp;ускорить.</p>
<h4>Оптимизация 1. Ленивость.</h4>
<p>Предположим, что у&nbsp;нас действительно большая строка, для которой мы&nbsp;строим s-дерево. Если число строк, которые мы&nbsp;будем искать, существенно меньше длины исходной строки, то&nbsp;у&nbsp;s-дерева, вероятно, появится много узлов, которые в&nbsp;процедуре сравнения вообще не&nbsp;будут использоваться. Получается, что при построении мы&nbsp;сделали массу ненужной работы. </p>
<p>Давайте будем строить s-дерево лениво, то&nbsp;есть по&nbsp;требованию: скажем, мы&nbsp;построили уже корень s-дерева, и&nbsp;нам приходит запрос найти в&nbsp;дереве строку issi. В&nbsp;этот момент мы&nbsp;<strong>разворачиваем</strong> узел <em>i</em>. Если нам не&nbsp;пришлось искать строку, начинающуюся на&nbsp;<em>s</em>, узел <em>s</em> так и&nbsp;останется неразвернутым. </p>
<h4>Оптимизация 2. Многопоточность.</h4>
<p>На&nbsp;самом деле ту&nbsp;часть дерева, которая почти наверняка нам пригодится, мы&nbsp;построим заранее, причем это будет делаться сразу всеми доступными потоками. В&nbsp;частности, одно удовольствие строить корень s-дерева сразу многими потоками, потому что алгоритм сортировки подсчетом прекрасно распараллеливается. </p>
<p>После того, как эта часть дерева построена, все потоки начинают искать в&nbsp;дереве какие-то строки, если какой-то поток приходит в&nbsp;неразвернутый узел, он&nbsp;блокирует узел и&nbsp;разворачивает его (блокирует, чтобы другие потоки не&nbsp;пытались инициализировать узел в&nbsp;то&nbsp;же время). </p>
<h4>Оптимизация 3. Хранение строк.</h4>
<p>Обе строки будем хранить в&nbsp;массиве из&nbsp;int16 в&nbsp;следующем формате:<br />
каждому элементу в&nbsp;массиве будет соответствовать один элемент int16, который будет хранить в&nbsp;запакованном виде сам символ и&nbsp;семь, следующих за&nbsp;ним:
</p>
<pre style="font-family: monospace">

A  G  T  C  C  T  C  A  T     &lt;- cтрока
00 01 10 11 11 10 11 00 10    &lt;- коды символов

00 01 10 11 11 10 11 00       &lt;- бинарное представление первого элемента массива
   01 10 11 11 10 11 00 10    &lt;- второго
      10 11 11 10 11 00 10 00 &lt;- третьего (пустое место заполнили нулями)
                                 и т.д.
</pre>
<p></p>
<p>Подобный метод хранения данных позволяет быстро запрашивать от&nbsp;одного до&nbsp;восьми бит, а&nbsp;значит </p>
<ul>
<li>Быстро проводить подсчет в&nbsp;сортировке подсчетом по&nbsp;первым n&nbsp;символам,	если n&lt;9 </li>
<li>Быстро передвигаться при движении по&nbsp;s-дереву, когда для	передвижения в&nbsp;следующую вершину нам надо считать несколько символов.</li>
</ul>
<h4>Оптимизация 4. Эвристика совпавшего суффикса.</h4>
<p>Собственно, эвристика уже не&nbsp;связана напрямую с&nbsp;s-деревом, но&nbsp;имеет отношение к&nbsp;решению задачи.</p>
<p>Зададимся вопросом, как мы&nbsp;будем искать совпадения с&nbsp;другой строкой (например, <em>iispimispispi</em>). Мы&nbsp;будем двигаться с&nbsp;конца второй строки. Предположим, что мы&nbsp;ищем с&nbsp;указанного места совпадение минимальной длины M=4 с&nbsp;уже рассмотренной строкой <em>missmississippi</em>.
</p>
<pre style="font-family: monospace">
       &#8595;
iispimispispi
</pre>
<p></p>
<p>В&nbsp;процессе поиска в&nbsp;s-дереве мы&nbsp;выясним, что строка<br />
<em>missmississippi</em> не&nbsp;содержит даже подстроки <em>sp</em>. Очевидно, что мы&nbsp;уже не&nbsp;найдем совпадений длины 4, если начнем поиск с&nbsp;указанных позиций: </p>
<pre style="font-family: monospace">
     &#8595;&#8595;
iispimispispi
</pre>
<p></p>
<p>поэтому мы&nbsp;можем сразу сдвинуться на&nbsp;три позиции влево: </p>
<pre style="font-family: monospace">
    &#8595;
iispimispispi
</pre>
<p></p>
<p>
При достаточно большом параметре M&nbsp;это очень сильно ускоряет поиск, потому что мы&nbsp;будем смещаться почти на&nbsp;величину M. (Мы&nbsp;бы даже рискнули предположить, что все, кто окажется на&nbsp;пьедестале, будут использовать эту эвристику или подобную ей).
</p>
<p>Для тех, кто хочет поломать свою голову: </p>
<pre style="font-family: monospace">
    &#8595;
mmippi
</pre>
<p></p>
<p>В&nbsp;указанной выше ситуации мы&nbsp;<strong>не&nbsp;найдем</strong> совпадение <em>ippi</em> со&nbsp;строкой <em>missmissisippi</em>, если будем пользоваться построенным нами деревом. Задача: почему и&nbsp;как это исправить? </p>
<h2>Заключение </h2>
<p>Пожалуй, излагать ещё какие-то подробности будет излишним. </p>
<p>Подведем итог: </p>
<ol>
<li>Мы&nbsp;посмотрели на&nbsp;структуру, которую мы&nbsp;назвали s-деревом	<br />
	(вполне возможно, что она была уже раз десять изобретена до&nbsp;нас, но&nbsp;пока	мы&nbsp;об&nbsp;этом не&nbsp;в&nbsp;курсе:))</li>
<li>Посмотрели на&nbsp;процедуру построения этого дерева, которая может производиться	лениво&nbsp;&mdash; по&nbsp;мере надобности, а&nbsp;также хорошо распараллеливается.	</li>
<li>Познакомились с&nbsp;некоторыми приемами ускорения построения и&nbsp;использования	дерева
<ul type="disc">
<li>Ленивостью, которая позволяет экономить при малом количестве строк,	поданных на&nbsp;поиск в&nbsp;дереве <br />
	(переводя на&nbsp;язык	<a href="http://software.intel.com/ru-ru/articles/contest-accelerate-2012-problem/">	условий задачи</a>, при длине reference сильно больше длины input) </li>
<li>Эвристикой совпавшего суффикса, которая позволяет нам ускоряться при	большом значении параметра M</li>
<li>Так как при малой длине референсной строки дерево мало, мы&nbsp;защитились	от&nbsp;самых разных &laquo;перекосов&raquo; во&nbsp;входных данных.	</li>
</ul>
</li>
<li>Планируем поделиться умными мыслями, появившимися в&nbsp;процессе чтения статьи, в&nbsp;комментариях.	Да, это приглашение:)</li>
</ol>
<p>PS. Почему s-массив и&nbsp;s-дерево? Потому что почти суффиксные, но&nbsp;не&nbsp;совсем.</p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/18/s/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Поваренная книга &quot;Accelerate 2012&quot;, рецепт &quot;фильтрация строк&quot;</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/18/accelerate-2012/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/18/accelerate-2012/#comments</comments>
		<pubDate>Fri, 18 May 2012 06:10:27 +0000</pubDate>
		<dc:creator>asandrov</dc:creator>
				<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/18/accelerate-2012/</guid>
		<description><![CDATA[Этот пост будет интересен участникам конкурса Accelerate 2012. В нем мы расскажем, как справиться с проблемой выполнения условия фильтрации строк.]]></description>
			<content:encoded><![CDATA[<p>Решая задачу <a href="http://software.intel.com/ru-ru/articles/contest-acceler8-2011-main/">конкурса Accelerate 2012</a>, наверное, мы все столкнулись с проблемой выполнения условия фильтрации строк. В статье мы расскажем как мы решили эту проблему.<br />
В референсном коде за это отвечают строки:</p>
<pre name="code" class="cpp">//this match can be shifted on i and j
if(i + 1 < refSeq.length() &#038;& j + 1 < otherSeq.length() &#038;& L[i][j] <= L[i+1][j+1])
     continue;
//this match can be shifted on j
if(i < refSeq.length() &#038;& j + 1 < otherSeq.length() &#038;& L[i][j] <= L[i][j+1])
     continue;
//this match can be shifted on i
if(i + 1 < refSeq.length() &#038;& j < otherSeq.length() &#038;& L[i][j] <= L[i+1][j])
     continue;
</pre>
<p>Основная идея заключается в том, что указанный код эквивалентен 2 условиям:</p>
<blockquote><p><em>Строки начинающиеся с позиций <strong>i</strong> и <strong>j</strong> должны быть отброшены в том случае, если<br />
(1) число подряд идущих одинаковых символов с символов <strong>i</strong> и <strong>j</strong> различается или<br />
(2) символы в позициях <strong>i-1</strong> и <strong>j-1</strong> совпадают.</em></p></blockquote>
<p>Рассмотрим пример:</p>
<pre name="code" class="cpp">
1234567890123456
ATGCCCCCCCCCCGTA
TGACCCCCCATG
</pre>
<p>Строки 4-9 и 4-9 отбрасываем, так как в первой строке с 4 символа идут 10 одинаковых символов, а во второй только 6. С другой для строк 8-13 и 4-9 условие выполняется и они вполне могут быть искомым совпадением.</p>
<p>Сравнивать все подстроки, каждый раз подсчитывая сколько у них символов дальше совпадает, может выйти затратным по времени, поэтому необходимо сосчитать повторы заранее. Мы использовали следующий формат хранения числа повторов:</p>
<blockquote><p>Список из элементов, по два значения в каждом: {длина 1 участка, повторяются ли на нем символы}, {длина 2 участка, повторяются ли на нем символы}, {длина 3 участка, повторяются ли на нем символы}.</p></blockquote>
<p>Главное <strong>преимущество </strong>такого хранения — это очень низкие затраты памяти. <strong>Недостаток</strong>: нет возможности получить значение для случайной позиции. Но если список (строку) проходить последовательно, то получается очень быстро. Частично решить указанный недостаток можно, составив массив ключевых позиций, и тогда можно начинать просмотр с них.<br />
Уменьшить длину списка можно рассматривая участки с повторами длиной меньше или равной длине минимальной искомой строки как участки с не повторяющимися символами и группируя их с соседними участками (если там символы тоже не повторяются).</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/18/accelerate-2012/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Модификация Алгоритма Рабина-Карпа для поиска общих подстрок</title>
		<link>http://software.intel.com/ru-ru/blogs/2012/05/17/2007628/</link>
		<comments>http://software.intel.com/ru-ru/blogs/2012/05/17/2007628/#comments</comments>
		<pubDate>Thu, 17 May 2012 12:16:38 +0000</pubDate>
		<dc:creator>yvanko</dc:creator>
				<category><![CDATA[Intel Software Network]]></category>
		<category><![CDATA[Параллельное программирование]]></category>
		<category><![CDATA[Разработка софта]]></category>
		<category><![CDATA[Acceler8]]></category>
		<category><![CDATA[accelerate 2012]]></category>
		<category><![CDATA[Intel TBB]]></category>

		<guid isPermaLink="false">http://software.intel.com/ru-ru/blogs/2012/05/17/2007628/</guid>
		<description><![CDATA[Вот и подошел к концу период отправки решений на конкурс Accelerate Your Code 2012 и мы наконец-то можем поделиться своими идеями, использованными в решении задачи и пригласить остальных участников к обсужднию. Задача состояла в том, чтобы найти общие подстроки длины не менее N из заданной ref строки (представляющей собой участок ДНК и потом состоящей только [...]]]></description>
			<content:encoded><![CDATA[<p>Вот и подошел к концу период отправки решений на конкурс <a href="http://software.intel.com/fr-fr/articles/AYC-early2012_home/">Accelerate Your Code 2012 </a>и мы наконец-то можем  поделиться своими идеями, использованными в решении задачи и пригласить остальных участников к обсужднию.</p>
<p>Задача состояла в том, чтобы найти общие подстроки длины не менее N  из заданной ref строки (представляющей собой участок ДНК и потом состоящей только из символов  A, G, C, T) и некоторого набора in строк (найти нужно именно подстроки, не подпоследовательности, то есть связные общие части). Мы не сразу поняли одну особенность этого конкурса: здесь фактически нужно было не написать программу, полностью соответствующую описанию, а скорее оптимизировать <a href="http://intel-software-academic-program.com/contests/ayc/early2012/ayc.zip">прилагающееся референсное решение</a>, так как сама постановка была нечеткой, а на форуме мы получили ответ, что программа должна  дать абсолютно такой же результат как и референсный код. Ну что же, задание необычное, но тем более интересное.</p>
<p>Для начала проанализируем референсное решение. В исходном алгоритме для каждой in-строки динамически строится матрица <code>L[i][j]</code> - максимальная длина общей подстроки в ref и in строках, с позициями конца подстроки <code>i</code> и <code>j</code> соответственно.  После построения этой матрицы достаточно найти все элементы со значением не менее <code>N</code>. Также в референсном решении отсекаются случаи, когда можно сдвинуть правый конец одной или обоих подстрок на 1 вправо с неуменьшением длины общей подстроки.</p>
<p>Главная проблема исходного решения в том, что сложность и требование к памяти в этом решении – квадратичные. Это значит, что если входные данные будут иметь максимальный размер (по четыре миллиарда символов), то время исполнения будет исчисляться годами, не говоря уже про потребляемую память, поэтому в первую очередь мы стали искать более эффективный алгоритм. Существует <a href="http://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%A0%D0%B0%D0%B1%D0%B8%D0%BD%D0%B0_%E2%80%94_%D0%9A%D0%B0%D1%80%D0%BF%D0%B0">Алгоритм Рабина — Карпа</a> поиска подстроки в строке, вкратце его суть в том, что имея длинную строку и образец длины <code>m</code>, мы находим хеш подстроки и хеши всех подстрок длины <code>m</code> в большой строке. При аккуратном использовании хеш-функции (нужно считать новый хеш, исходя из старого за О(1), а не за O(m) ) </p>
<pre name="code" class="cpp">
h -= rem * int64(s[i-1]);
h = h * MOD + int64(s[i+minMatchLength-1]);
hash[i] = h;
</pre>
<p>мы потратим в среднем О(m+n) времени, просто сравнивая все хеши с хешем образца. Недостатком этого алгоритма является то, что в «плохих» случаях он может работать за квадратичное время, однако на форуме мы получили ответ, что плохих случаев в тестах не будет, что повлияло на выбор этого алгоритма. </p>
<p>Для данной конкретной задачи, конечно, пришлось делать модификации. Мы можем найти все хеши подстрок длины <code>N</code> из in строки и поместить их в хеш-таблицу, этот процесс можно распараллелить через библиотеку TBB. Далее мы можем пройтись по подстрокам длины <code>N</code> из ref строки и сверять их хеш с таблицей. При этом вставка в хеш-таблицу медленнее, чем проверка, поэтому заметим, что задача практически симметрична относительно ref и in строк, поэтому если ref строка длиннее, то создадим хеш таблицу для in строки и наоборот. Когда мы находим совпадение хешей, то находим наибольшую общую подстроку (зная начальные индексы) и запоминаем этот ответ. Есть еще небольшая сложность связанная с тем, что ответы получатся не в том порядке в котором они идут в референсном решении, поэтому придется еще применить сортировку для ответа, однако это не должно сильно замедлить алгоритм.</p>
<p>В итоге получается, что сложность этого алгоритма составляет O(N + KlogK), где N – суммарный размер входа, K – размер выхода, что уже является практически линейным решением.</p>
<p>Хотелось бы услышать комментарии, замечания или вопросы и от остальных участников и конечно, делитесь вашими идеями!</p>
]]></content:encoded>
			<wfw:commentRss>http://software.intel.com/ru-ru/blogs/2012/05/17/2007628/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

