【问题标题】:Python parsing stdin much faster than C++Python 解析标准输入比 C++ 快得多
【发布时间】:2015-07-16 12:04:13
【问题描述】:

我有一个 python 函数,我希望将其翻译成 C++ 以尝试获得一些额外的速度(因为它将用于解析 >100GB 的文件)。我对 C++ 非常缺乏经验,并且很震惊地发现我的 C++ 函数在我的基本翻译之后运行得慢得多。任何关于为什么会这样,或者我可以做些什么来改进我的 C++ 代码的指针将不胜感激。

脚本概述:该函数从另一个程序读取标准输入,检查每一行是否有任何子字符串匹配,并将每一行打印到标准输出

Python 函数:

def find_tagPy(conditions):
    # conditions e.g. ['TTAT', 'TAT'] etc   
    for line in stdin:
        # Check conditionss against this line
        l = line.split("\t")

        if l[0][0] == "@":
            stdout.write(line)
            continue

        FLAG = int(l[1])

        if 1 & FLAG:  # Read has a pair
            for bases in conditions:
                if bases in l[9]:
                    ADD_MATE = 1
                    stdout.write(line)
                    break  # stop looking

C++ 函数:

void find_tagCpp (vector<string> conditions) {
    cin.sync_with_stdio(false);
    cin.tie(NULL);

    string line;
    while (getline(cin, line)) {
        vector<string> l;
        boost::split(l, line, boost::is_any_of("\t"), boost::token_compress_on);

        if (l[0][0] == '@') {
            cout << line << "\n";
            continue;
        }

        int FLAG = stoi(l[1]);
        int pair_FLAG = 1;

        if (pair_FLAG & FLAG) {  // Read has a pair
            for (int i=0; i < conditions.size(); i++) {  // If bases in SEQ
                if (l[9].find(conditions[i]) != string::npos) {
                    cout << line << "\n";
                    break;  // Stop looking
                }
            }
        }
    }
}

标准输入行的一个例子是:

FCC2CCMACXX:4:1105:10758:14389#81 CHRM 1 32 10S90M = 16151 16062 CATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTTTCCATGCATTTGGTATTTTCGTCTGGGGGGTGTGCACGCGATAGCATTG BBB ^ Wcbbbbccbbbcbccbba] WQG ^ bbcdcb_ ^_ C_ ^`ccdddeeeeeffggggiiiiihiiiiihiiihihiiiihghhiihgfgfgeeeeebbb NM:I:1为:I:85 XS:I: 65 RG:Z:1_DB31

在我的机器上,python 函数需要 1.97 秒,C++ 函数需要 11.05 秒(文件大小约为 25 mb,但这包括使用上游和下游工具进行处理)

编辑:

我在 boost::split 中发现了一个瓶颈,这有点令人惊讶:

Python:

for i in range(100000):
    l = line.split("\t")

C++:

for (int i=0; i < 100000; i++) {
    vector<string> l;
    boost::split(l, line, boost::is_any_of("\t"), boost::token_compress_on);
}

Python = 0.0325 秒

C++ = 1.245 秒

但是我的文件只有 156,980 行,所以这不是问题的全部。

【问题讨论】:

  • 看代码,我会把钱花在std::string 性能上。
  • plus1 用于编译器标志。根据我的经验,-O0 -g3 代码甚至可能比 C# 代码慢。
  • @ChrisMaes vector“不是很快”到底是怎么回事?
  • @kezzos 您的代码中存在一些微不足道的低效问题,但我主要关注boost::split stackoverflow.com/questions/7930796/…
  • Python 是否有可能将表达式 bases in l[9] 优化为一个有限状态机,该机器可以及时搜索与干草堆字符串长度近似线性的任何碱基? C++ 代码明确地单独检查每个碱基,这在 haystack 字符串的长度上也近似线性,但具有与碱基数量成比例的更高常数因子。

标签: c++ stdout stdio


【解决方案1】:

拆分将片段复制到新字符串中。这很慢,您不需要它们。而是在该行中搜索您想要的片段的开头( 10th ),然后从那里开始调用 find 。

【讨论】:

  • boost::split 的文档暗示它只是制作成对的迭代器,而不是复制字符串。可能后续操作会强制 C++ 实例化一个字符串。
  • 我认为pythons split应该做同样的事情
  • "每个部分都被复制并作为一个新元素添加到输出容器中。" boost.org/doc/libs/1_49_0/doc/html/boost/algorithm/…
  • 如果split 从每个令牌中生成一对插入器,那么l 应该是vector&lt;pair&lt;iterator, iterator&gt;&gt;(其中iterator 甚至可以是const char *,而不是vector&lt;string&gt; .
  • 在我之前的评论中引用的文档告诉我们 split 没有创建一对迭代器。请阅读文档。
【解决方案2】:

我意识到我的原始代码不适合测试,所以我想我会在这里重构它,并讨论我的发现。 我按照建议使用 -Ofast(最快、最激进的优化,Apple LLVM 6.1)打开了编译器优化,相比之下,Python 是 2.7.10。

Python 函数

import time

def fun(line):
    l = line.split(" ", 10)
    if 'TTAGGG' in l[9]:
        pass

line = "FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062 CATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTTTCCATGCATTTGGTATTTTCGTCTGGGGGGTGTGCACGCTTAGGGGATAGCATTG bbb^Wcbbbbccbbbcbccbba]WQG^bbcdcb_^_c_^`ccdddeeeeeffggggiiiiihiiiiihiiihihiiiihghhiihgfgfgeeeeebbb NM:i:1 AS:i:85 XS:i:65 RG:Z:1_DB31"

time0 = time.time()
for i in range(100000):
    fun(line)

print time.time() - time0

C++ 函数

void fun(string* line, string* substring) {
    vector<string> l;
    boost::split(l, *line, boost::is_any_of(" "));
    if (l[9].find(*substring) != string::npos) {
        // Do nothing
    }
}

int main(int argc, const char * argv[]) {
    string line = "FCC2CCMACXX:4:1105:10758:14389# 81 chrM 1 32 10S90M = 16151 16062 CATCACGATGGATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTTTCCATGCATTTGGTATTTTCGTCTGGGGGGTGTGCACGCTTAGGGGATAGCATTG bbb^Wcbbbbccbbbcbccbba]WQG^bbcdcb_^_c_^`ccdddeeeeeffggggiiiiihiiiiihiiihihiiiihghhiihgfgfgeeeeebbb NM:i:1 AS:i:85 XS:i:65 RG:Z:1_DB31";
    string substring = "TTAGGG";
    boost::timer t;

    for (int i=0; i<100000; i++) {
        fun(&line, &substring);
    }

    cout << t.elapsed() << endl;
    return 0;
}

在我的机器上,我现在将 c++ 函数计时为 205 毫秒,将 python 函数计时为 66 毫秒。有趣的是,现在几乎整个运行时都被 boost::split 函数占用了。

如果我去掉这个函数并使用 string.find 搜索整行(虽然不是我想要的):

if ((*line).find(*substring) != string::npos) {
    // Do nothing
}

c++ 运行时间减少到大约

【讨论】:

    【解决方案3】:

    用一些优化试试这个代码

    C++ 函数:

    void find_tagCpp (vector<string> conditions) {
        cin.sync_with_stdio(false);
        cin.tie(NULL);
    
        string line;
        vector<string> l;
        while (getline(cin, line)) {
            l.clear();
            boost::split(l, line, boost::is_any_of("\t"), boost::token_compress_on);
    
            if (l[0][0] == '@') {
                cout << line << "\n";
                continue;
            }
    
            int FLAG = stoi(l[1]);
            int pair_FLAG = 1;
    
            if (pair_FLAG & FLAG) {  // Read has a pair
                for (int i=0; i < conditions.size(); i++) {  // If bases in SEQ
                    if (l[9].find(conditions[i]) != string::npos) {
                        printf("%s\n", line.c_str());
                        break;  // Stop looking
                    }
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多