English 中文(简体)
C 中的快速字符串比较
原标题:Fast string comparison in C

我现在有这种循环

while(1)
{
    generate_string(&buffer);

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

我有一份有几百万条字符串的文件(希望很快能被削减一半),所有这些字符串的数量都存储在 < 坚固 > 的文件中 < / 坚固 > 。

线条(i) 基本上是在字符串本身被存储的地方。

Currently, due to the comparison of these million strings, function generate_string(&buffer); is executed around 42 times per second. Is there a faster way to do string comparison in C?

最佳回答

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

  • Lookup Burst Tries
  • Use a suffix tree for fast string comparison -- see this article
  • Depending on the size of strings in your application you can write a custom string comparator. E.g: GNU libc used to have this optimization for small strings where they tested strings smaller than five bytes as integers. MS cl also has some optimizations for small-strings (do look it up).

但更重要的是,确保strcmp 是您的“emgener > real /strg%/em>瓶颈。

问题回答

我可以向你保证, < strong> 函数 < code>strcmp 是ABSOLUITY 而不是瓶颈 。 通常, strcmp 是最优化的, 可以对字符串进行32或64位比特的比较, 字符串长度超过 4/8 字节, 取决于建筑结构。 新lib 和 GNU libc 都这样做。 但是, 即使您用两个字符串来查看每个字节, 20 次, 也和这里所做的 algo & amp; 数据结构选择一样重要 。

真正的瓶颈是 O(N) 搜索算法 。 在文件上的单 O(Nlog N) 通过可用于进行 O(log N) 搜索的适当数据结构( 无论是正常的 BST 、 trie 、 还是简单排序的阵列 ) 。

在此,许多数学都随之而来。但我认为这是一个很好的机会来说明为什么选择算法 & amp; 数据结构有时比字符串比较方法更为重要。 Steve提到了这一点,但我想更深入地解释一下。

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

您正在对 O( N) 或 1e6 操作进行最差的搜索 。

所以说您只需用 O( log N) 插入时间构建一棵红黑树, 然后插入 N 项, 即 s O( N log N) 时间来构建树。 因此您需要用 s 1e6 x 20 或 20e6 操作来构建您的树 。

在目前的方法中,建立数据结构是 O(N), 或 1e6 操作, 但最差的搜索时间也是 O(N) 。 所以当您阅读文件并只做20次搜索操作时, 您的理论中最差的情况是 21,000,000 个操作。 相比之下, 您最差的情况是红树和 20 个搜索是 20,000 个操作, 或者 999, 600 操作比 O( N) 搜索未分类的阵列要好。 因此, 在 20 个搜索中, 您会发现一个更复杂的数据结构真正能起作用的起始点 。 但是看看1000 搜索中发生的情况 :

未分类阵列=初始化+1000x搜索时间=O(N)+1000 *O(N)=1,000,000+2,000,000=2,001,000,000操作。

红色-黑色 = 初始化+ 1000 x 搜索时间 = O(Nlog N) + 1000 * O(log N) = 20,000,000 + 20,000 = 20,020,000 操作。

2 001 000 000 / 20 020 000 100x O(N) 搜索作业次数。

At 1e6 搜索, s (1e6 + 1e6 * 1e6) / (20e6 + 1e6 * 20) = 25 000x 作业。

假设您的计算机能处理40e6操作, 也就是在1分钟内进行对数 N 搜索所需的40e6操作。 使用您目前的算法, 需要25,000分钟或17天才能做同样的工作。 或者另一种方法来查看, O( N) 搜索算法只能在 O( log N) 算法能够完成 1 000 000 时处理39次搜索。 越多搜索, 搜索就会越难看。

Steve 和 dirkgently 对数据结构 & amp; 算法的几种更好的选择做出回应。 我唯一的额外告诫是, Steve < em> might 建议的 < code> < qsort () 具有O( N*N) 最坏的复杂情况, 远比您用堆积或各种树状结构得到的 O( Nlog N) 还要糟糕。

计算机程序在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 = = equal), 例如, 一组命令行参数, 以相同的前缀开头, 例如 : tcp- 接受, tcp- reject, 而不是您可以重写宏, 并且做一些指针算术, 来比较不是第一个, 而是 Nth 字符, 在这种情况下, 4 字典, 例如 :

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

如果我正确得到您的问题, 您需要检查字符串是否与目前所读到的线条一致。 我建议使用TRIE 或更佳的 < a href=" http:// en. wikipedia. org/ wiki/ Patricia_ tree" rel = "nofollow" > Patricia tree 。 这样的方式, 而不是在所有的线条上都进行线性检查, 如果你的字符串是否存在( 并且再多做点努力 - 在哪里) 。

您可以在第一个字符串的基础上尝试一些廉价的筛选。 如果第一个字符串不匹配, 字符串无法等同。 如果匹配, 请调用 strcmp 来比较整个字符串。 如果您的情况适合的话, 您可以考虑更好的算法; 示例是排序文件/ 行, 使用散列表格或类似的字符串表格技术进行二进制搜索 。

你已经在以优化的方式编集了,对吗?

如果您有三合一或易碎的数据结构 分布在周围, 准备使用, 那么你应该使用。

如果做不到,一个相当容易的改变是,在开始生成要搜索的字符串之前,先对您的数组进行排序 line 。然后在排序的数组中,对 buffer 进行二进式搜索,因为您需要的两个函数是标准的 -- -- qsort bsearch

对排序数组的二进制搜索只需要对日志 < sub> 2 (filelines) 字符串比较来做, 而不是对文件行进行比较。 因此, 在您的情况中, 每个调用 < code> generate_ string 而不是数百万个调用时, 只需要对20个约20个的字符串比较。 从您给出的数字来看, 我认为您可以合理地预计它会更快到20- 25倍, 虽然我什么也不会答应 。

如果您事先知道字符串长度, 您可以使用字节参考宏, 而不是 strcmp () 来实现非常快速的字符串比较( 标准 8- bit < code> char ) 。 我根据 glibc s strcmp () 将字节参考宏作为基准, 而宏版本大大超过 strcmp () 执行; 它利用 CPU s < a href=" https:// en.wikipedia.org/wiki/Vector_ proctor" rel= "nofoln noreferr" > > victor processor

示例:

#define str3_cmp(x, y0, y1, y2, y3) x[0] == y0 && x[1] == y1 && x[2] == y2 && x[3] == y3
static inline bool str3_cmp_helper(const char *x, const char *y) {
    return str3_cmp(x, *y, *(y + 1), *(y + 2), *(y + 3));
}

const char *i = "hola"; // dynamically generated (eg: received over a network)

if (str3_cmp_helper(i, "hola")) {
     /* do something */ 
} else { 
    /* do something else */ 
}

然而,写作这样的宏是烦琐的, 所以我包括了一个 PHP 脚本来生成宏。 此脚本需要两个参数, (1) 需要比较的字符串长度( 这个参数是 < a href="https:// en.wikipedia. org/ wiki/ Variadic_ conformation" rel=" nofollow noreferrerr" > variadic , 以便写入您想要的多个宏) 和 (2) 输出文件名 。

#!/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)".
                                "{
		return str".$num."cmp_macro(ptr, ";
        for($x = 0; $x < $num; $x++){
                $generated_macro .= " *(cmp+".$x.")";
                if($x != $num-1){ $generated_macro .= ", "; }
        }
        $generated_macro .= ");
}
";
        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])."
";
                $gen_macro .= generate_static_inline_fn($macro, $argv[$x+1]);
        }
        file_put_contents($out_filename, $gen_macro);
}
handle_generation($argc, $argv);
?>

脚本示例: $./gen_faststrcmp.php 3 5 fast_strcmp.h

这将生成 fast_strcmp.h 和宏, 用于比较长度 3 和 5 的字符串 :

#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 */ }

我不知道有比调用 strcmp 更快的方法来做字符串比较, 但是您也许可以如此多地 避免 调用 strcmp 。 使用散列表格来存储您的字符串, 然后您就可以检查在 buffer 中的字符串是否在散列表格中。 如果您在“ 做某事” 时, 点击的索引很重要, 表格可以将字符串映射为索引 。

在此情况下,您也许能够通过二进制比较来克服问题,因为您的程序实际上不是sort ,而是平等比较。

您也可以通过提前确定长度来提高比较速度(当然长度要变化足够多)。如果长度不匹配, do something 就不会发生。

当然,这里的散列将是另一个考虑因素 取决于你读多少次散列值。

这取决于字符串的长度。

如果时间不长,您可以尝试比较字节字节 :

str[0] == str2[0] && str[1] == str2[1] && str[2] == str2[2]

否则,使用 emcmp () 来比较内存块块。

对正则字符串使用 strcmp 。 但如果字符串长的话, 您可以使用 memcmp 。 它会比较内存的块块 。





相关问题
Simple JAVA: Password Verifier problem

I have a simple problem that says: A password for xyz corporation is supposed to be 6 characters long and made up of a combination of letters and digits. Write a program fragment to read in a string ...

Case insensitive comparison of strings in shell script

The == operator is used to compare two strings in shell script. However, I want to compare two strings ignoring case, how can it be done? Is there any standard command for this?

Trying to split by two delimiters and it doesn t work - C

I wrote below code to readin line by line from stdin ex. city=Boston;city=New York;city=Chicago and then split each line by ; delimiter and print each record. Then in yet another loop I try to ...

String initialization with pair of iterators

I m trying to initialize string with iterators and something like this works: ifstream fin("tmp.txt"); istream_iterator<char> in_i(fin), eos; //here eos is 1 over the end string s(in_i, ...

break a string in parts

I have a string "pc1|pc2|pc3|" I want to get each word on different line like: pc1 pc2 pc3 I need to do this in C#... any suggestions??

Quick padding of a string in Delphi

I was trying to speed up a certain routine in an application, and my profiler, AQTime, identified one method in particular as a bottleneck. The method has been with us for years, and is part of a "...