14

一般来说,我在“网上”遇到的所有与 SSE/MMX 相关的东西都是作为向量和矩阵的数学东西出现的。但是,我正在寻找 SSE 优化的“标准函数”库,例如Agner Fog提供的库,或者 GCC 中一些基于 SSE 的字符串扫描算法。

作为一个快速的一般纲要:这些将是 memset、memcpy、strstr、memcmp BSR/BSF 之类的东西,即从 SSE 指令构建的 stdlib-esque

我希望它们使用内在函数而不是汇编用于 SSE1(正式 MMX2),但两者都可以。希望这不是太广泛的范围。

更新 1

经过一番搜索,我发现了一些有前途的东西,一个图书馆引起了我的注意:

  • LibFreeVec:似乎只有 mac/IBM(由于基于 AltiVec),因此用处不大(对我来说),而且我似乎找不到直接下载链接,也没有说明支持的最低 SSE 版本

我还看到了一篇关于一些向量化字符串函数(strlen、strstr strcmp)的文章。但是 SSE4.2 是我无法企及的(如前所述,我想坚持使用 SSE1/MMX)。

更新 2

Paul R 激励我做一些基准测试,不幸的是,由于我的 SSE 汇编编码经验接近 zip,我使用了别人的 ( http://www.mindcontrol.org/~hplus/ ) 基准代码并添加到它。所有测试(不包括原始版本,即 VC6 SP5)都在 VC9 SP1 下编译,并具有完整/自定义优化/arch:SSE等。

第一个测试是我的一台家用机器(AMD Sempron 2200+ 512mb DDR 333),上限为 SSE1(因此 MSVC memcpy 没有矢量化):

comparing P-III SIMD copytest (blocksize 4096) to memcpy
calculated CPU speed: 1494.0 MHz
  size  SSE Cycles      thru-sse    memcpy Cycles   thru-memcpy     asm Cycles      thru-asm
   1 kB 2879        506.75 MB/s     4132        353.08 MB/s     2655        549.51 MB/s
   2 kB 4877        598.29 MB/s     7041        414.41 MB/s     5179        563.41 MB/s
   4 kB 8890        656.44 MB/s     13123       444.70 MB/s     9832        593.55 MB/s
   8 kB 17413       670.28 MB/s     25128       464.48 MB/s     19403       601.53 MB/s
  16 kB 34569       675.26 MB/s     48227       484.02 MB/s     38303       609.43 MB/s
  32 kB 68992       676.69 MB/s     95582       488.44 MB/s     75969       614.54 MB/s
  64 kB 138637      673.50 MB/s     195012      478.80 MB/s     151716      615.44 MB/s
 128 kB 277678      672.52 MB/s     400484      466.30 MB/s     304670      612.94 MB/s
 256 kB 565227      660.78 MB/s     906572      411.98 MB/s     618394      603.97 MB/s
 512 kB 1142478     653.82 MB/s     1936657     385.70 MB/s     1380146     541.23 MB/s
1024 kB 2268244     658.64 MB/s     3989323     374.49 MB/s     2917758     512.02 MB/s
2048 kB 4556890     655.69 MB/s     8299992     359.99 MB/s     6166871     484.51 MB/s
4096 kB 9307132     642.07 MB/s     16873183        354.16 MB/s     12531689    476.86 MB/s

完整的测试

第二批测试是在大学工作站上完成的(Intel E6550、2.33Ghz、2gb DDR2 800?)

VC9 SSE/memcpy/ASM:
comparing P-III SIMD copytest (blocksize 4096) to memcpy
calculated CPU speed: 2327.2 MHz
  size  SSE Cycles      thru-sse    memcpy Cycles   thru-memcpy     asm Cycles      thru-asm
   1 kB 392         5797.69 MB/s    434         5236.63 MB/s    420         5411.18 MB/s
   2 kB 882         5153.51 MB/s    707         6429.13 MB/s    714         6366.10 MB/s
   4 kB 2044        4447.55 MB/s    1218        7463.70 MB/s    1218        7463.70 MB/s
   8 kB 3941        4613.44 MB/s    2170        8378.60 MB/s    2303        7894.73 MB/s
  16 kB 7791        4667.33 MB/s    4130        8804.63 MB/s    4410        8245.61 MB/s
  32 kB 15470       4701.12 MB/s    7959        9137.61 MB/s    8708        8351.66 MB/s
  64 kB 30716       4735.40 MB/s    15638       9301.22 MB/s    17458       8331.57 MB/s
 128 kB 61019       4767.45 MB/s    31136       9343.05 MB/s    35259       8250.52 MB/s
 256 kB 122164      4762.53 MB/s    62307       9337.80 MB/s    72688       8004.21 MB/s
 512 kB 246302      4724.36 MB/s    129577      8980.15 MB/s    142709      8153.80 MB/s
1024 kB 502572      4630.66 MB/s    332941      6989.95 MB/s    290528      8010.38 MB/s
2048 kB 1105076     4211.91 MB/s    1384908     3360.86 MB/s    662172      7029.11 MB/s
4096 kB 2815589     3306.22 MB/s    4342289     2143.79 MB/s    2172961     4284.00 MB/s

完整的测试

可以看出,SSE 在我的家庭系统上非常快,但落在 intel 机器上(可能是由于编码错误?)。我的 x86 汇编变体在我的家用机器上排在第二位,在英特尔系统上排在第二位(但结果看起来有点不一致,一个拥抱块它主导了 SSE1 版本)。MSVC memcpy 赢得了英特尔系统测试的胜利,这是由于 SSE2 矢量化,但在我的家用机器上,它惨遭失败,即使是可怕的__movsd击败它......

陷阱:内存是 2 的所有对齐幂。缓存(希望)被刷新。rdtsc 用于计时。

兴趣点:MSVC 有一个(未在任何参考中列出)__movsd内在函数,它输出与我正在使用的相同的汇编代码,但它失败得很惨(即使是内联的!)。这可能就是它未上市的原因。

VC9 memcpy 可以在我的非 sse 2 机器上强制向量化,但是它会破坏 FPU 堆栈,它似乎也有一个错误。

这是我用来测试的全部来源(包括我的改动,再次感谢http://www.mindcontrol.org/~hplus/的原始版本)。项目文件的二进制文件可应要求提供。

总之,似乎一个切换变体可能是最好的,类似于 MSVC crt 一个,只有更多的选项和单一的一次性检查(通过内联函数指针?或者像内部直接调用这样更狡猾的东西)更加坚固补丁),但是内联可能不得不使用最佳案例方法

更新 3

Eshan 提出的一个问题提醒了一些有用的和与此相关的东西,虽然BitMagic仅适用于位集和位操作,但对大型位集非常有用,它甚至还有一篇关于SSE2(位)优化的好文章。不幸的是,这仍然不是 CRT/stdlib esque 类型库。似乎这些项目中的大多数都致力于特定的一小部分(问题)。

这就提出了一个问题,是否值得创建一个开源的、可能是多平台性能的 crt/stdlib 项目,创建各种版本的标准化函数,每个版本都针对特定情况进行了优化,以及“最佳情况” '/该函数的通用变体,具有标量/MMX/SSE/SSE2+(à la MSVC)的运行时分支或强制编译时标量/SIMD 开关。

这对于 HPC 或每一点性能都很重要的项目(如游戏)很有用,使程序员不必担心内置函数的速度,只需进行少量调整即可找到最佳优化变体。

更新 4

我认为应该扩展这个问题的性质,包括可以使用 SSE/MMX 来优化非向量/矩阵应用程序的技术,这也可能用于 32/64 位标量代码。一个很好的例子是如何使用标量技术(位操作)、MMX 和 SSE/SIMD 立即检查给定的 32/64/128/256 位数据类型中是否出现字节

另外,我看到很多“只使用 ICC”的答案,这是一个很好的答案,这不是我的答案,因为首先,ICC 不是我可以持续使用的东西(除非英特尔有免费的学生版对于windows),由于 30 试用。其次,更重要的是,我不仅关注库本身,还关注用于优化/创建它们包含的功能的技术,以供我个人改进和改进,因此我可以将这些技术和原则应用于我自己的代码(如果需要),结合使用这些库。希望这可以清除那部分:)

4

8 回答 8

2

这是一篇关于如何使用 SIMD 指令向量化字符计数的文章:

http://porg.es/blog/ridiculous-utf-8-character-counting

于 2010-10-06T10:52:01.063 回答
1

也许是 libSIMDx86?

http://simdx86.sourceforge.net

于 2010-10-05T12:32:21.203 回答
1

这是一个 C 中的快速 memcpy 实现,如果需要,它可以替换标准库版本的 memcpy:

http://www.danielvik.com/2010/02/fast-memcpy-in-c.html

于 2010-10-08T09:44:01.667 回答
1

老实说,我要做的只是安装英特尔 C++ 编译器并学习各种可用的自动 SIMD 优化标志。通过简单地使用 ICC 进行编译,我们在优化代码性能方面获得了非常好的经验。

请记住,整个 STL 库基本上只是头文件,因此整个内容都编译到您的 exe/lib/dll 中,因此可以根据需要进行优化。

ICC 有许多选项,可让您(最简单地)指定要定位的 SSE 级别。您还可以使用它来生成具有多个代码路径的二进制文件,这样如果您编译的最佳 SSE 配置不可用,它将运行为功能较弱的 SIMD CPU 配置的一组不同的(仍然优化的)代码.

于 2010-10-08T09:11:17.703 回答
1

您可以使用苹果的或 OpenSolaris 的 libc。这些 libc 实现包含您要查找的内容。大约 6 年前,我一直在寻找这类东西,我不得不痛苦地写下来。

很久以前,我记得参加过一个名为“ fastcode ”项目的编码竞赛。他们当时使用 Delphi 做了一些很棒的突破性优化。查看他们的结果页面。由于它是用 Pascal 的快速函数调用模型(将参数复制到寄存器)编写的,因此转换为 C 风格的 stdc 函数调用模型(推入堆栈)可能有点尴尬。这个项目很久没有更新了,特别是没有为SSE4.2编写代码。

Solaris -> src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libc/

Apple -> www.opensource.apple.com/source/Libc/Libc-594.9.1/
于 2010-10-05T15:05:15.610 回答
1

对于诸如 memset、memcpy 等计算量很少的简单操作,SIMD 优化没有什么意义,因为内存带宽通常是限制因素。

于 2010-10-05T10:52:53.750 回答
1

strstr 很难优化,因为 (a) \0-termination 意味着无论如何您都必须读取每个字节,并且 (b) 它也必须在所有边缘情况下都很好。

话虽如此,您可以使用 SSE2 操作将标准 strstr 提高 10 倍。我注意到 gcc 4.4 现在将这些操作用于 strlen,但不用于其他字符串操作。有关如何将 SSE2 寄存器用于 strlen、strchr、strpbrk 等的更多信息,请访问mischasan.wordpress.com。请原谅我的超简洁代码布局。

#include <emmintrin.h> // Other standard #includes you can figure out...

static inline unsigned under(unsigned x)
    { return (x - 1) & ~x; }
static inline __m128i xmfill(char b)
    { return _mm_set1_epi8(b); }
static inline __m128i xmload(void const*p)
    { return _mm_load_si128((__m128i const*)p); }
static inline unsigned xmatch(__m128i a, __m128i b)
    { return _mm_movemask_epi8(_mm_cmpeq_epi8(a, b)); }

char const *sse_strstr(char const *tgt, char const *pat)
{
    unsigned    len = sse_strlen(pat);
    if (len == 0) return tgt;
    if (len == 1) return sse_strchr(tgt,*pat);
    __m128i     x, zero = {};
    __m128i     p0 = _m_set1_epi8(pat[0]), p1 = _m_set1_epi8(pat[1]);
    uint16_t    pair = *(uint16_t const*)pat;
    unsigned    z, m, f = 15 & (uintptr_t)tgt;
    char const* p;

    // Initial unaligned chunk of tgt:
    if (f) {
        z = xmatch(x = xmload(tgt - f), zero) >> f;
        m = under(z) & ((xmatch(x,p0) & (xmatch(x,p1) >> 1)) >> f);
        for (; m; m &= m - 1)
             if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
                return p;
        if (z)
            return NULL;
        tgt += 16 - f;
        if (*(uint16_t const*)(tgt - 1) == pair
                && !memcmp(tgt+1, pat+2, len-2))
            return tgt - 1;
    }

    // 16-byte aligned chunks of tgt:
    while (!(z = xmatch(x = xmload(tgt), zero))) {
        m = xmatch(x,p0) & (xmatch(x,p1) >> 1);
        for (; m; m &= m - 1)
             if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
                return p;
        tgt += 16;
        if (*(uint16_t const*)(tgt - 1) == pair && !memcmp(tgt+1, pat+2, len-2))
            return tgt - 1;
    }

    // Final 0..15 bytes of tgt:
    m = under(z) & xmatch(x,p0) & (xmatch(x,p1) >> 1);
    for (; m; m &= m - 1)
        if (!memcmp((p = tgt+ffs(m)-1)+2, pat+2, len-2))
            return p;

    return NULL;
}
于 2011-09-18T00:45:54.223 回答
0

我个人不会费心尝试编写 libc 函数的超级优化版本,试图以良好的性能处理所有可能的场景。

相反,为特定情况编写优化版本,在这些情况下,您对手头的问题有足够的了解,可以编写正确的代码……以及重要的地方。memset和之间存在语义差异ClearLargeBufferCacheWriteThrough

于 2010-10-08T09:00:50.927 回答