字符串匹配性能:gcc对比CPython

12 浏览
0 Comments

字符串匹配性能:gcc对比CPython

在研究Python和C++的性能权衡时,我设计了一个小示例,主要关注愚蠢的子字符串匹配。

下面是相关的C++代码:

using std::string;
std::vector matches;
std::copy_if(patterns.cbegin(), patterns.cend(), back_inserter(matches),
   [&fileContents] (const string &pattern) { return fileContents.find(pattern) != string::npos; } );

上述代码是使用-O3编译的。

这里是Python:

def getMatchingPatterns(patterns, text):
    return filter(text.__contains__, patterns)

它们都使用一组相对较大的模式和输入文件,并使用愚蠢的子字符串搜索筛选出在文件中找到的匹配的模式。

版本如下:

  • gcc- 4.8.2(Ubuntu)和4.9.2(cygwin)
  • python- 2.7.6(Ubuntu)和2.7.8(cygwin)

让我感到惊讶的是性能。我在低规格的Ubuntu上运行它们,Python快了大约20%。在中规格的cygwin上也是如此- Python快了两倍。

剖析器显示99%以上的周期都用于字符串匹配(字符串复制和列表推导不重要)。

显然,Python的实现是本地C,我预期大致与C ++相同,但没有预期它会那么快。

任何有关与gcc相比的CPython优化的见解都将不胜感激。

为了参考,这里是完整的示例。输入只需要一组50K个HTML文件(每个测试都从磁盘读取所有文件,没有特殊缓存):

Python:

import sys
def getMatchingPatterns(patterns, text):
   return filter(text.__contains__, patterns)
def serialScan(filenames, patterns):
   return zip(filenames, [getMatchingPatterns(patterns, open(filename).read()) for filename in filenames])
if __name__ == "__main__":
   with open(sys.argv[1]) as filenamesListFile:
      filenames = filenamesListFile.read().split()
   with open(sys.argv[2]) as patternsFile:
      patterns = patternsFile.read().split()
   resultTuple = serialScan(filenames, patterns)
   for filename, patterns in resultTuple:
      print ': '.join([filename, ','.join(patterns)])

C ++:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using MatchResult = unordered_map<string, vector>;
static const size_t PATTERN_RESERVE_DEFAULT_SIZE = 5000;
MatchResult serialMatch(const vector &filenames, const vector &patterns)
   {
   MatchResult res;
   for (auto &filename : filenames)
      {
      ifstream file(filename);
      const string fileContents((istreambuf_iterator(file)),
                                         istreambuf_iterator());
      vector matches;
      std::copy_if(patterns.cbegin(), patterns.cend(), back_inserter(matches),
                   [&fileContents] (const string &pattern) { return fileContents.find(pattern) != string::npos; } );
      res.insert(make_pair(filename, std::move(matches)));
      }
   return res;
   }
int main(int argc, char **argv)
    {
    vector filenames;
    ifstream filenamesListFile(argv[1]);
    std::copy(istream_iterator(filenamesListFile), istream_iterator(),
             back_inserter(filenames));
    vector patterns;
    patterns.reserve(PATTERN_RESERVE_DEFAULT_SIZE);
    ifstream patternsFile(argv[2]);
    std::copy(istream_iterator(patternsFile), istream_iterator(),
             back_inserter(patterns));
    auto matchResult = serialMatch(filenames, patterns);
    for (const auto &matchItem : matchResult)
      {
      cout << matchItem.first << ": ";
      for (const auto &matchString : matchItem.second)
         cout << matchString << ",";
      cout << endl;
      }
    }

admin 更改状态以发布 2023年5月20日
0
0 Comments

Python 3.4代码b'abc' in b'abcabc'(或者在你的例子中为b'abcabc'.__contains__(b'abc'))执行bytes_contains方法,该方法调用内联函数stringlib_find;它将搜索代理给FASTSEARCH

FASTSEARCH函数然后使用简化的Boyer-Moore搜索算法(Boyer-Moore-Horspool):

快速搜索/计数实现,基于Boyce-Moore和Horspool的组合,并且增加一些其他功能。有关更多背景,请参见:http://effbot.org/zone/stringlib.htm

还有一些修改,如注释所示:

注意:fastsearch可能会访问s[n],在使用Python的普通字符串类型时不会有问题,但如果在其他上下文中使用此代码,则可能会出现问题。另外,如果在目标字符串中不可能有匹配项,则计数模式返回-1,如果实际上已经检查过匹配项但没有找到,则返回0。调用者请注意!

中文翻译:


GNU C++标准库basic_string::find()的实现尽可能通用(且愚蠢); 它只是愚蠢地在每个连续字符位置上尝试匹配模式,直到找到匹配为止。


TL;DR: C++标准库之所以与Python相比速度较慢,是因为它在std::basic_string的基础上尝试进行通用算法,但对于更有趣的情况,无法有效地执行; 而在Python中,程序员基于每种情况使用最有效的算法是免费的。

0