我目前有这种循环
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中进行字符串比较?
答案 0 :(得分:11)
strcmp
通常由所有供应商优化。但是,如果您对此不满意,可以尝试:
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
。这很简单,因为您需要的两个功能是标准功能 - qsort
和bsearch
。
二进制搜索到排序数组只需要关于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);
?>
此脚本有两个参数。
char*
的大小。示例:$ ./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 */ }