C中的快速字符串比较

时间:2012-05-23 14:48:03

标签: c string compare

我目前有这种循环

while(1)
{
    generate_string(&buffer);

    for(int i = 0; i < filelines; i++)
    {
        if(strcmp(buffer,line[i]) == 0)
        {
           /*  do something  */
        }
    }
}

我有一个包含几百万个字符串的文件(希望很快就会减半),所有这些字符串的数量都存储在 filelines

line [i]基本上是存储字符串本身的位置。

目前,由于这些百万字符串的比较,函数 generate_string(&amp; buffer); 每秒执行约42次。 有没有更快的方法在C中进行字符串比较?

9 个答案:

答案 0 :(得分:11)

strcmp通常由所有供应商优化。但是,如果您对此不满意,可以尝试:

  • 查找Burst Tries
  • 使用后缀树进行快速字符串比较 - 请参阅this文章
  • 根据应用程序中字符串的大小,您可以编写自定义字符串比较器。例如:GNU libc曾经对小字符串进行优化,他们测试小于5个字节的字符串作为整数。 MS cl也对小字符串进行了一些优化(请查看)。

但更重要的是要确保strcmp 真正的 瓶颈。

答案 1 :(得分:5)

我可以向你保证,功能strcmp绝对不是瓶颈。通常,strcmp经过了很好的优化,可以对长度超过4/8字节的字符串进行32位或64位比较,具体取决于体系结构。 newlib和GNU libc都这样做。但是,即使你要查看两个字符串中的每个字节20次,也没有算法和算法那么重要。这里做出的数据结构选择。

真正的瓶颈是O(N)搜索算法。文件中的单个O(N log N)传递可用于进行O(log N)查找的适当数据结构(无论是正常的BST,trie还是仅仅是简单的排序数组)。

在这里跟我一起 - 接下来是很多数学。但我认为这是一个很好的机会来说明为什么选择算法&amp;数据结构有时比字符串比较方法更重要。史蒂夫谈到这一点,但我想更深入地解释它。

当N = 1e6时,log(1e6,2)= 19.9,因此在理想数据结构上进行20次比较。

目前,您正在进行O(N)或1e6操作的最坏情况搜索。

所以说你只需要构建一个红色黑树,插入时间为O(log N),然后插入N个项目,即构建树的O(N log N)时间。这就是构建树所需的1e6 x 20或20e6操作。

在您当前的方法中,构建数据结构是O(N)或1e6操作,但最坏情况下的搜索时间也是O(N)。因此,当您阅读文件并执行20次搜索操作时,您将达到21,000,000次操作的理论最坏情况。相比之下,使用红黑树和20次搜索的最坏情况是20,000,400次操作,或比未排序阵列上的O(N)搜索更好的999,600次操作。因此,在20次搜索中,您处于第一个更复杂的数据结构真正得到回报的地方。但看看1000次搜索会发生什么:

未排序数组=初始化+ 1000 x搜索时间= O(N)+ 1000 * O(N)= 1,000,000 + 2,000,000,000 = 2,001,000,000次操作。

红黑=初始化+ 1000 x搜索时间= O(N日志N)+ 1000 * O(日志N)= 20,000,000 + 20,000 = 20,020,000次操作。

2,001,000,000 / 20,020,000~ = 100倍于O(N)搜索的操作。

在1e6次搜索中,这是(1e6 + 1e6 * 1e6)/(20e6 + 1e6 * 20)= 25,000x次操作。

假设您的计算机可以处理在1分钟内进行日志N搜索所需的40e6'操作'。使用当前算法执行相同的工作需要25,000分钟或17天。或者另一种观察方式是O(N)搜索算法在O(log N)算法可以做1,000,000时只能处理39次搜索。你做的搜索越多,它就会变得更加丑陋。

请参阅史蒂夫和dirkgently的回复,以便更好地选择数据结构和数据结构。算法。我唯一需要注意的是,Steve 建议的qsort()可能具有O(N * N)的最坏情况复杂度,其远远超过O(N log) N)你得到了一个堆或各种树状结构。

答案 2 :(得分:4)

Optimization of Computer Programs in C

  

在拨打电话之前,您可以通过检查相关字符串的前几个字符来节省一些时间。显然,如果第一个字符不同,则没有理由调用strcmp来检查其余字符。由于自然语言中字母的分布不均匀,收益率不是26:1,而是大写数据的15:1。

#define QUICKIE_STRCMP(a, b)  (*(a) != *(b) ? \  
  (int) ((unsigned char) *(a) - \
         (unsigned char) *(b)) : \
  strcmp((a), (b)))

如果您正在使用的单词词典定义良好(意味着您不介意strcmp的返回值,但0 ==相等),例如,一组以相同前缀开头的命令行参数,例如: tcp-accept,tcp-reject比你可以重写宏并做一些指针算术来比较第一个而不是第N个char,在这种情况下,第四个char,ex:

   #define QUICKIE_STRCMP(a, b, offset) \
            (*(a+offset) != *(b+offset))\ ? -1 : strcmp((a), (b)))

答案 3 :(得分:2)

如果我正确地得到你的问题,你需要检查一下字符串是否沿着所有读到目前为止的行。我建议从文件行使用TRIE或更好的Patricia tree。这种方式不是遍布所有行,而是可以线性检查字符串是否存在(并且需要更多努力 - 在哪里)。

答案 4 :(得分:1)

您已经使用优化进行编译,对吧?

如果您有一个Trie或哈希表数据结构,可以随时使用,那么您应该这样做。

如果失败了,一个相当容易的改变可能会加快速度,就是在开始生成要搜索的字符串之前,对数组line进行一次排序。然后在排序数组中二进制搜索buffer。这很简单,因为您需要的两个功能是标准功能 - qsortbsearch

二进制搜索到排序数组只需要关于log 2 (filelines)字符串比较,而不是关于文件行。因此,在您的情况下,每次调用generate_string而不是几百万的字符串比较为20。从你给出的数字来看,我认为你可以合理地预期它会快20-25倍,尽管我什么也不做。

答案 5 :(得分:0)

我不知道有一种比调用strcmp来进行字符串比较更快的方法,但你可以避免调用strcmp这么多。使用哈希表来存储字符串,然后您可以检查buffer中的字符串是否在哈希表中。如果在“执行某些操作”时命中的索引很重要,则表可以将字符串映射到索引。

答案 6 :(得分:0)

你可以尝试一些“便宜”的东西,比如基于第一个字符的筛选。如果第一个字符不匹配,则字符串不能相等。如果匹配,则调用strcmp来比较整个字符串。如果适合您的情况,您可能希望考虑更好的算法;示例将使用哈希表或类似的字符串表技术对文件/行进行排序并进行二进制搜索。

答案 7 :(得分:0)

在这种情况下,你可以通过二进制比较来解决问题,因为你的程序实际上不是排序,而是比较相等。

您还可以通过提前确定长度来提高比较速度(前提是它们的变化足够大)。当长度与此处不匹配时,do something将不会发生。

当然,这里的散列将是另一个考虑因素,具体取决于您读取散列值的次数。

答案 8 :(得分:0)

我以使用strcmp()和一个宏来逐字节比较宏为基准。与strcmp()相比,宏版本“非常”快得多。通常用于字符串比较,使用字节比较器宏要快得多,而不是strcmp()。 例如:

#define str3_cmp(m, c0, c1, c2, c3) m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3

strcmp()相比,它“非常”快。但是写下这些内容是一件很痛苦的事情,您需要按char拆分字符串char,因此我编写了一个方便的PHP脚本来为您生成该文件作为头文件。

您可以在热循环中使用此字符串比较,在那里您确切知道要比较的char*的大小。

#!/usr/bin/php
<?php
function generate_macro($num) : string {
        $returner = "#define str".$num."cmp_macro(ptr, ";
        for($x = 0; $x < $num; $x++){
                $returner .= "c".$x;
                if($x != $num-1){ $returner .= ", "; }
        }
        $returner .= ") ";
        for($x = 0; $x < $num; $x++){
                $returner .= "*(ptr+".$x.") == c".$x;
                if($x != $num-1){ $returner .= " && "; }
        }
        return $returner;
}
function generate_static_inline_fn(&$generated_macro, $num) : string {
        $generated_macro .= "static inline bool str".$num."cmp(const char* ptr, const char* cmp)".
                                "{\n\t\treturn str".$num."cmp_macro(ptr, ";
        for($x = 0; $x < $num; $x++){
                $generated_macro .= " *(cmp+".$x.")";
                if($x != $num-1){ $generated_macro .= ", "; }
        }
        $generated_macro .= ");\n}\n";
        return $generated_macro;
}

function handle_generation($argc, $argv) : void {
        $out_filename = $argv[$argc-1];
        $gen_macro = "";
        for($x = 0; $x < $argc-2; $x++){
                $macro = generate_macro($argv[$x+1])."\n";
                $gen_macro .= generate_static_inline_fn($macro, $argv[$x+1]);
        }
        file_put_contents($out_filename, $gen_macro);
}
handle_generation($argc, $argv);
?>

此脚本有两个参数。

  1. 您要比较的char*的大小。
  2. 输出头文件名。

示例:$ ./gen_faststrcmp.php 3 5 fast_strcmp.h 这将生成带有内容的fast_strcmp.h

#define str3cmp_macro(ptr, c0, c1, c2) *(ptr+0) == c0 && *(ptr+1) == c1 && *(ptr+2) == c2
static inline bool str3cmp(const char* ptr, const char* cmp){
                return str3cmp_macro(ptr,  *(cmp+0),  *(cmp+1),  *(cmp+2));
}
#define str5cmp_macro(ptr, c0, c1, c2, c3, c4) *(ptr+0) == c0 && *(ptr+1) == c1 && *(ptr+2) == c2 && *(ptr+3) == c3 && *(ptr+4) == c4
static inline bool str5cmp(const char* ptr, const char* cmp){
                return str5cmp_macro(ptr,  *(cmp+0),  *(cmp+1),  *(cmp+2),  *(cmp+3),  *(cmp+4));
}

在您的代码中,您可以使用类似的功能

const char* compare_me = "Hello";
if(str5cmp(compare_me, "Hello")) { /* code goes here */ }