一个时钟周期内的 C++ 字符串比较

Posted

技术标签:

【中文标题】一个时钟周期内的 C++ 字符串比较【英文标题】:C++ string comparison in one clock cycle 【发布时间】:2009-07-14 21:17:47 【问题描述】:

是否可以在单个处理器周期内比较整个内存区域?更准确地说,是否可以使用某种 MMX 汇编指令在一个处理器周期内比较两个字符串?或者strcmp-implementation 是否已经基于该优化?

编辑: 或者是否可以指示 C++ 编译器删除重复的字符串,以便可以通过它们的内存位置简单地比较字符串?而不是memcmp(a,b)a==b 进行比较(假设ab 都是本机const char* 字符串)。

【问题讨论】:

在英特尔上,可以用一条指令来完成。当然不是在一个周期内。 @avakar,周期只有一个,但可能太长了 :) 你想要的叫做符号:tinyurl.com/nmoxme @jia3ep:Avakar 是正确的。该单条指令问题将花费许多时钟周期。 【参考方案1】:

只需使用标准 C strcmp() 或 C++ std::string::operator==() 进行字符串比较。

它们的实现相当不错,并且可能被编译成一个高度优化的程序集,即使是才华横溢的程序集程序员也很难与之匹敌。

所以不要为小事而烦恼。我建议考虑优化代码的其他部分。

【讨论】:

shl %cl,%esi ... and %esi,%edx 根据\0 之前的字节调整结果位掩码,这是由pcmpeqb (%rdi),%xmm2 发现的。 ...我认为。 Marco:16 字节对齐是关键 - 因为读取总是与 16 字节边界对齐,因此它们永远不会跨越页面边界。 +1 只使用标准实现并且不要为小事操心 这不是 gcc 优化的魔力,而是 grayfade 的幻觉艺术,向我们展示了一个函数的 C 和完全不同的函数的 asm(它可能一开始是用 asm 编写的)。 @R..:恰恰相反。我使用 objdump 工具从我最近编译的一个 glibc 的目标文件中提取了反汇编。我什至通过手动重新编译仔细检查了 GCC 选项。【参考方案2】:

您可以使用Boost Flyweight 库来实习您的不可变字符串。字符串相等/不等测试然后变得非常快,因为此时它所要做的就是比较指针(双关语不是故意的)。

【讨论】:

【参考方案3】:

不是真的。您典型的 1 字节比较指令需要 1 个周期。 您最好的选择是使用 MMX 64 位比较指令(请参阅 this page for an example)。但是,这些操作在寄存器上运行,必须从内存中加载。内存负载会严重损害您的时间,因为您将最多只能到 L1 缓存,这会增加 10 倍的时间减速*。如果您正在执行一些繁重的字符串处理,您可能会在那里获得一些不错的加速,但同样,它会受到伤害。

其他人建议预先计算字符串。也许这适用于您的特定应用程序,也许不会。你比较字符串吗?你能比较数字吗?

您的编辑建议比较指针。这是一个危险的情况,除非您可以明确保证您不会进行子字符串比较(即,您正在比较一些两个字节字符串:[0x40, 0x50] 和 [0x40, 0x42]。这些不是“相等”,而是指针比较会说它们是)。

你看过 gcc strcmp() 源代码吗?我建议这样做是理想的起点。

* 简单地说,如果一个循环需要 1 个单位,L1 命中需要 10 个单位,L2 命中需要 100 个单位,而实际 RAM 命中需要 非常长

【讨论】:

是的,在我的情况下,用枚举替换字符串可以解决问题。但是我必须通过添加前缀(或后缀)来修改我的字符串,因为这些字符串名称已经被其他代码保留。 @aarep:你不能把枚举放到它们自己的类/命名空间中吗? ID是否被其他代码使用? @Paul:GLIBC strcmp() 实现是最简单的实现:读取连续字节直到它们不同。使用 gcc-4 -O2 编译,它使用 pcmpeqb 和 pmovmskb 指令。我看不出它怎么能更有效率。 看我上面的评论,16字节的预读是不可能的,因为#0之后的内存可能无法访问。您首先需要长度,但 C 字符串不能以这种方式工作(或需要额外的通过)。可能是试图将比较次数从 2(一个用于 \0 一个用于相等)减少到 1 1/16(一个用于 \0 和 16 一次)左右。但我怀疑它提供了多少。 根据我上面的回复评论,\0 之后的内存只有在不同的页面中才能有不同的保护,并且由于预读始终是 16 字节对齐的,因此它们永远不会读过去\0 所在页面的末尾。【参考方案4】:

不可能在一个周期内执行通用字符串操作,但您可以通过额外信息应用许多优化。

如果您的问题域允许对适合机器寄存器的字符串使用对齐的、固定大小的缓冲区,则可以执行单周期比较(不计算加载指令)。 如果您始终跟踪字符串的长度,您可以比较长度并使用memcmp,它比strcmp 更快。如果您的应用程序是多文化的,请记住这仅适用于 ordinal string comparison。 看来您使用的是 C++。如果您只需要与不可变字符串进行相等比较,则可以使用字符串实习解决方案(复制/粘贴链接,因为我是新用户)来保证相等的字符串存储在相同的内存位置,此时您可以简单地比较指针。见en.wikipedia.org/wiki/String_interning 另外,请查看英特尔优化参考手册第 10 章,了解有关 SSE 4.2 文本处理指令的详细信息。 www.intel.com/products/processor/manuals/

编辑:如果您的问题域允许使用枚举,那您的单周期比较解决方案。不要与之抗争。

【讨论】:

【参考方案5】:

如果您正在优化字符串比较,您可能需要使用字符串表(那么您只需要比较两个字符串的索引,这可以在一条机器指令中完成)。

如果这不可行,您还可以创建一个包含字符串和散列的散列字符串对象。然后,大多数情况下,如果字符串不相等,您只需比较哈希值。如果哈希值确实匹配,则您必须进行全面比较以确保它不是误报。

【讨论】:

【参考方案6】:

这取决于你做了多少预处理。 C# 和 Java 都有一个称为 interning strings 的过程,如果它们具有相同的内容,它会使每个字符串映射到相同的地址。假设这样的过程,您可以使用一条比较指令进行字符串相等比较。

订购有点困难。

编辑:显然,这个答案回避了尝试在单个周期内进行字符串比较的实际问题。但这是唯一的方法,除非您碰巧有一系列指令,可以在恒定时间内查看无限量的内存以确定 strcmp 的等价物。这是不可能的,因为如果你有这样的架构,卖给你的人会说“嘿,这是一个很棒的指令,可以在一个周期内进行字符串比较!这有多棒?”并且您不需要在 *** 上发布问题。

但这只是我的合理意见。

【讨论】:

【参考方案7】:

或者可以指导c++ 编译器删除重复的字符串, 以便可以简单地比较字符串 通过他们的记忆位置?

没有。编译器可能会在内部删除重复项,但我知道没有编译器可以保证或提供访问这种优化的设施(除非可能将其关闭)。当然,C++ 标准在这方面没有什么可说的。

【讨论】:

【参考方案8】:

假设您的意思是 x86 ... Here 是英特尔文档。

但在我看来,不,我认为您一次只能比较一个寄存器的大小。

出于好奇,你为什么要问?我是最后一个过早调用 Knuth 的人,但是……strcmp 通常做得很好。

编辑:链接现在指向现代文档。

【讨论】:

我懒得创建枚举,想在应该很快的低级函数中使用字符串。看到这样的代码让我很伤心: if(input=="someString1")... if(input=="someString2")... if(input=="someString3")... 每帧都执行: ) 如果你真的热衷于使用汇编代码,请查看手册中的 CMPS 说明。但我鼓励您先探索其他一些答案中推荐的映射和散列方法。【参考方案9】:

您当然可以在一个循环中比较多个字节。如果我们以 x86-64 为例,您可以在单个指令 (cmps) 中比较最多 64 位(8 个字节),这不一定是一个周期,但通常会在低个位数(确切的速度取决于特定的处理器版本)。

但是,这并不意味着您可以比strcmp 更快地完成比较内存中两个数组的所有工作:-

    不仅仅是比较 - 您需要比较两个值,检查它们是否相同,如果相同,则转到下一个块。 大多数 strcmp 实现已经高度优化,包括检查 a 和 b 是否指向相同的地址,以及任何合适的指令级优化。

除非您看到在strcmp 上花费了很多时间,否则我不会担心 - 您是否有想要改进的特定问题/用例?

【讨论】:

那么使用 pragma pack 8 并一次比较 8 个字节的字符串是有意义的,因为它会快速跳过不相等的字符串。至少在我的情况下,字符串开头有相同的 8 个字符是非常罕见的。 @AareP:好吧,您需要确保有 8 个字节的数据可供加载 - 字符串是未知的、任意长度的,因此您可能只是盲目地加载了 8 个字节最终读取字符串的结尾和段错误。【参考方案10】:

即使两个字符串都被缓存,也不可能在单个处理器周期内比较(任意长的)字符串。现代编译器环境中 strcmp 的实现应该是经过优化的,所以你不应该费心去优化太多。

编辑(回复您的编辑):

    您不能指示编译器统一所有重复的字符串 - 大多数编译器都可以这样做,但这只是尽力而为(而且我不知道任何编译器可以跨编译单元工作)。

    通过将字符串添加到映射并在此之后比较迭代器,您可能会获得更好的性能...比较本身可能是一个循环(或不多)然后

    如果要使用的字符串集是固定的,请使用枚举 - 这就是它们的用途。

【讨论】:

好吧,但是你认为下面的函数:__inline bool test(string a)return a=="x"; test("x");会编译成“真”吗?即使我使用字符串类而不是 const char*?我可能应该自己测试一下…… 我不这么认为——即使是最优化的编译器也不会对字符串类的实现看那么远。在这种情况下,使用 const char* == 不能保证给出正确的结果......【参考方案11】:

这是一种使用类似枚举的值而不是字符串的解决方案。它支持枚举值继承,因此支持类似于子字符串比较的比较。它还使用特殊字符“¤”进行命名,以避免名称冲突。您可以取任何类、函数或变量名并将其转换为枚举值(SomeClassA 将变为 ¤SomeClassA)。

struct MultiEnum

    vector<MultiEnum*> enumList;
    MultiEnum()
    
        enumList.push_back(this);
    
    MultiEnum(MultiEnum& base)
    
        enumList.assign(base.enumList.begin(),base.enumList.end());
        enumList.push_back(this);
    
    MultiEnum(const MultiEnum* base1,const MultiEnum* base2)
    
        enumList.assign(base1->enumList.begin(),base1->enumList.end());
        enumList.assign(base2->enumList.begin(),base2->enumList.end());
    
    bool operator !=(const MultiEnum& other)
    
        return find(enumList.begin(),enumList.end(),&other)==enumList.end();
    
    bool operator ==(const MultiEnum& other)
    
        return find(enumList.begin(),enumList.end(),&other)!=enumList.end();
    
    bool operator &(const MultiEnum& other)
    
        return find(enumList.begin(),enumList.end(),&other)!=enumList.end();
    
    MultiEnum operator|(const MultiEnum& other)
    
        return MultiEnum(this,&other);
    
    MultiEnum operator+(const MultiEnum& other)
    
        return MultiEnum(this,&other);
    
;

MultiEnum 
    ¤someString,
    ¤someString1(¤someString),  // link to "someString" because it is a substring of "someString1"
    ¤someString2(¤someString);


void Test()

    MultiEnum a = ¤someString1|¤someString2;
    MultiEnum b = ¤someString1;

    if(a!=¤someString2)
    if(b==¤someString2)
    if(b&¤someString2)
    if(b&¤someString)  // will result in true, because someString is substring of someString1

PS。今天早上我的空闲时间确实太多了,但有时重新发明***实在是太有趣了……:)

【讨论】:

特殊字符?这……不是个好主意。聪明可爱,但代码不好。 整个解决方案是概念验证,只是为了好玩而编写的。自从那天早上我写了它之后就再也没有看过它。 :)

以上是关于一个时钟周期内的 C++ 字符串比较的主要内容,如果未能解决你的问题,请参考以下文章

并行全比较排序算法&并对角标排序

以成员函数为参数的结构

C++中两个字符串的比较

按单个字符比较两个字符串 C++

请问C++中两个字符串的大小是怎样比较的?

CUDA clock() 导致零时钟周期