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

时间:2009-07-14 21:17:48

标签: c++ string assembly comparison

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

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

11 个答案:

答案 0 :(得分:19)

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

它们的实现相当不错,可能编译成一个非常高度优化的程序集,即使是才华横溢的程序集程序员也会发现难以匹配。

所以不要为小东西流汗。我建议您考虑优化代码的其他部分。

答案 1 :(得分:10)

您可以使用Boost Flyweight库来实习您的不可变字符串。字符串相等/不等式测试然后变得非常快,因为在那一点上它必须做的就是比较指针(双关语)。

答案 2 :(得分:8)

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

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

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

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

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

答案 3 :(得分:6)

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

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

编辑:如果您的问题域允许使用枚举,则 是您的单周期比较解决方案。不要打它。

答案 4 :(得分:5)

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

订购有点困难。

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

但这只是我的理由。

答案 5 :(得分:5)

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

如果这不可行,您还可以创建包含字符串和哈希的散列字符串对象。那么大多数时候你只需要比较字符串不相等的哈希值。如果哈希确实匹配,你必须做一个完整的比较,但要确保它不是误报。

答案 6 :(得分:4)

  

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

没有。编译器可能会在内部删除重复项,但我知道没有编译器可以保证或提供访问此类优化的工具(除非可能将其关闭)。当然,C ++标准在这个领域无话可说。

答案 7 :(得分:2)

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

但是,我不认为,我不认为你一次可以比一个寄存器的大小更多。

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

修改:现在链接指向现代文档。

答案 8 :(得分:2)

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

然而,这并不意味着你能够比strcmp更快地比较内存中两个数组的所有工作: -

  1. 不仅仅是比较 - 您需要比较两个值,检查它们是否相同,如果是,则转到下一个块。
  2. 大多数strcmp实现已经过高度优化,包括检查a和b是否指向同一地址,以及任何合适的指令级优化。
  3. 除非你在strcmp看到很多时间,否则我不会担心 - 你是否有一个特定的问题/用例你想要改进?

答案 9 :(得分:1)

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

编辑(回复您的编辑):

  1. 你不能指示编译器统一所有重复的字符串 - 大多数编译器可以做这样的事情,但它只是最好的努力(我不知道它在编译单元中工作的任何编译器)。

  2. 您可以通过将字符串添加到地图并在此之后比较迭代器来获得更好的性能...比较本身可能是一个周期(或者不多)然后

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

答案 10 :(得分:0)

这是一个使用枚举值而不是字符串的解决方案。它支持枚举值继承,因此支持类似于子字符串比较的比较。它还使用特殊字符“¤”进行命名,以避免名称冲突。您可以使用任何类,函数或变量名称并使其成为枚举值(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。今天早上我手上的空闲时间太多了,但重新发明轮子有时太有趣了...... :)