<?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>算法 &#8211; 寂静花园</title>
	<atom:link href="https://www.addesp.com/archives/tag/%E7%AE%97%E6%B3%95/feed" rel="self" type="application/rss+xml" />
	<link>https://www.addesp.com</link>
	<description>寂静中交织着热情</description>
	<lastBuildDate>Fri, 22 Oct 2021 14:13:49 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>
	<item>
		<title>SIMD 指令集友好的子串匹配算法</title>
		<link>https://www.addesp.com/archives/5486</link>
					<comments>https://www.addesp.com/archives/5486#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 21 Oct 2021 16:52:56 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[算法]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=5486</guid>

					<description><![CDATA[标题简单明了，这是一篇看了之后也没什么用的文章。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">标题简单明了，这是一篇看了之后也没什么用的文章。</p>



<p class="wp-block-paragraph">这是我在看 <a href="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/" target="_blank" rel="noreferrer noopener" title="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/">Building even faster interpreters in Rust</a> 时发现的，至于我为什么要看，可能是因为我也开发了一个<a href="https://github.com/ADD-SP/ngx_waf" target="_blank" rel="noreferrer noopener" title="https://github.com/ADD-SP/ngx_waf">防火墙模块</a>。</p>



<h2 class="wp-block-heading">什么是 SIMD？</h2>



<p class="wp-block-paragraph">SIMD（<strong>S</strong>ingle <strong>I</strong>nstruction <strong>M</strong>ultiple <strong>D</strong>ata）即单指令流多数据流，翻译成人话就是用一条指令操作多组数据。举个例子就是通常一条指令只能完成一对寄存器间的一次加法，现在我们可以用一条指令完成多对寄存器间的多次加法。</p>



<h2 class="wp-block-heading">常规子串匹配算法的缺点</h2>



<p class="wp-block-paragraph">常规的子串匹配算法基本是下列流程。</p>



<pre class="wp-block-code language-pseudocode"><code>k = needle.length
for i = 0 to haystack.length - k - 1 {
    if haystack&#91;i] == needle&#91;0] {
        for j = 1 to k - 1 {
            if haystack&#91;i + j] != needle&#91;j] {
                break
            }
        }
        if j == k {
            return true
        }
    }
}
return false</code></pre>



<p class="wp-block-paragraph">原理很简单，就是一个一个字符地匹配，但是有下列缺点。</p>



<ul class="wp-block-list"><li>对于现代处理器来说，对 8 bit、16 bit、32 bit 甚至 64 bit 进行一次运算的耗时是一样的。如果还这样逐字节比较就浪费了硬件资源。</li><li>逐字节比较也要逐次访问内存，浪费 CPU 周期。</li><li>错误的分支预测则会浪费更多的 CPU 周期。</li><li>这类代码难以乱序执行。</li></ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>寄存器：可以认为是变量。</p><cite><a href="https://baike.baidu.com/item/%E6%B2%83%E5%85%B9%E5%9F%BA%E7%A1%95%E5%BE%B7/50646842" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/%E6%B2%83%E5%85%B9%E5%9F%BA%E7%A1%95%E5%BE%B7/50646842">沃·兹基硕德</a></cite></blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>* There is no difference in comparing one, two, four or 8 bytes on a 64-bit CPU. When a processor supports SIMD instructions, then comparing vectors (it means 16, 32 or even 64 bytes) is as cheap as comparing a single byte.</p><p>* Thus comparing short sequences of chars can be faster than fancy algorithms which avoids such comparison.</p><p>* Looking up in a table costs one memory fetch, so at least a L1 cache round (~3 cycles). Reading char-by-char also cost as much cycles.</p><p>* Mispredicted jumps cost several cycles of penalty (~10-20 cycles).</p><p>* There is a short chain of dependencies: read char, compare it, conditionally jump, which make hard to utilize out-of-order execution capabilities present in a CPU.</p><cite><a href="http://0x80.pl/articles/simd-strfind.html#performance-results-update" target="_blank" rel="noreferrer noopener" title="http://0x80.pl/articles/simd-strfind.html#performance-results-update">SIMD-friendly algorithms for substring searching</a></cite></blockquote>



<h2 class="wp-block-heading">优化算法</h2>



<p class="wp-block-paragraph">那么如何使用 SMID 来优化算法性能呢？</p>



<p class="wp-block-paragraph">假设我们有一些 8-byte 的寄存器，我们要在字符串 &#8220;a_cat_tries&#8221; 中搜索子串 &#8220;cat&#8221;。</p>



<p class="wp-block-paragraph">首先我们将 “cat” 的第一个字节和最后一个字节填充到两个寄存器中，并尽可能地重复知道寄存器被填满。</p>



<pre class="wp-block-code language-ascii-art"><code>F    = &#91; c | c | c | c | c | c | c | c ]
L    = &#91; t | t | t | t | t | t | t | t ]</code></pre>



<p class="wp-block-paragraph">然后我们将字符串  &#8220;a_cat_tries&#8221;  加载到另外两个寄存器中，其中一个寄存器从第二个字符开始加载。</p>



<pre class="wp-block-code language-ascii-art"><code>A    = &#91; a | _ | c | a | t | _ | t | r ]
B    = &#91; c | a | t | _ | t | r | i | e ]</code></pre>



<p class="wp-block-paragraph">然后比较两组寄存器的内容，对应位置的内容相同为 1，反之则为 0。</p>



<pre class="wp-block-code language-ascii-art"><code>AF   = (A == F)
     = &#91; 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 ]

BL   = (B == L)
     = &#91; 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 ]</code></pre>



<p class="wp-block-paragraph">最后我们将两个寄存器的内容合并，即 “位与” 运算。</p>



<pre class="wp-block-code language-ascii-art"><code>mask = &#91; 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 ]</code></pre>



<p class="wp-block-paragraph">此时我们发现只有第 2 位（从零开始）是 1，这说明只有从此处开始搜索才有可能搜索到子串。着就减少了我们的搜索次数，下面是一种实现。</p>



<pre class="wp-block-code language-cpp"><code>size_t avx2_strstr_anysize(const char* s, size_t n, const char* needle, size_t k) {

    // 向寄存器中填充 needle 的第一个字节
    const __m256i first = _mm256_set1_epi8(needle&#91;0]);
    // 向寄存器中填充 needle 的最后一个字节
    const __m256i last  = _mm256_set1_epi8(needle&#91;k - 1]); 

    for (size_t i = 0; i &lt; n; i += 32) {

        // 向寄存器中填充 s 的部分内容
        const __m256i block_first = _mm256_loadu_si256(reinterpret_cast&lt;const __m256i*&gt;(s + i));

        // 向寄存器中填充 s 的部分内容，相对于上一行，本次填充的内容有所偏移
        const __m256i block_last  = _mm256_loadu_si256(reinterpret_cast&lt;const __m256i*&gt;(s + i + k - 1));
    

        // 比较两组寄存器
        const __m256i eq_first = _mm256_cmpeq_epi8(first, block_first);
        const __m256i eq_last  = _mm256_cmpeq_epi8(last, block_last);

        // 合并两个寄存器的比较结果
        uint32_t mask = _mm256_movemask_epi8(_mm256_and_si256(eq_first, eq_last));

        while (mask != 0) {

            // 找到第一个值为 1 的 bit 的下标
            const auto bitpos = bits::get_first_bit_set(mask);

            if (memcmp(s + i + bitpos + 1, needle + 1, k - 2) == 0) {
                return i + bitpos;
            }

            mask = bits::clear_leftmost_set(mask);
        }
    }

    return std::string::npos;
}</code></pre>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/" target="_blank" rel="noreferrer noopener" title="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/">Building even faster interpreters in Rust</a></li><li><a href="http://0x80.pl/articles/simd-strfind.html#algorithm" target="_blank" rel="noreferrer noopener" title="http://0x80.pl/articles/simd-strfind.html#algorithm">SIMD-friendly algorithms for substring searching</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/5486/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
