如何加速这段代码?

3
我有以下方法,用于读取txt文件并返回字典。它需要大约7分钟来读取一个大小为5MB的文件(67000行,每行70个字符)。
public static Dictionary<string, string> FASTAFileReadIn(string file)
{
    Dictionary<string, string> seq = new Dictionary<string, string>();

    Regex re;
    Match m;
    GroupCollection group;
    string currentName = string.Empty;

    try
    {
        using (StreamReader sr = new StreamReader(file))
        {
            string line = string.Empty;
            while ((line = sr.ReadLine()) != null)
            {
                if (line.StartsWith(">"))
                {// Match Sequence
                    re = new Regex(@"^>(\S+)");
                    m = re.Match(line);
                    if (m.Success)
                    {
                        group = m.Groups;
                        if (!seq.ContainsKey(group[1].Value))
                        {
                            seq.Add(group[1].Value, string.Empty);
                            currentName = group[1].Value;
                        }
                    }
                }
                else if (Regex.Match(line.Trim(), @"\S+").Success &&
                            currentName != string.Empty)
                {
                    seq[currentName] += line.Trim();
                }
            }
        }
    }
    catch (IOException e)
    {
        Console.WriteLine("An IO exception has benn thrown!");
        Console.WriteLine(e.ToString());
    }
    finally { }

    return seq;
}

哪部分代码耗时最长,如何加快速度?

谢谢。


2
相关:https://dev59.com/FnVD5IYBdhLWcg3wXaYd - Brian Cain
@Brian,谢谢,这节省了一些时间。 :) - sarnold
3
不要每次都创建一个新的正则表达式。创建一个,然后使用 RegexOptions.Compiled 标志来获得额外性能提升。 - Ry-
尽量避免使用正则表达式,因为它们通常需要更多时间。 - COLD TOLD
2
你经常使用字符串和字符串拼接; 请考虑使用 StringBuilder - 在你的情况下,它应该有助于显著提高速度。你可以从重写你的 Dictionary 开始,将其改为 Dictionary<string, StringBuilder> - newfurniturey
4个回答

3

我希望编译器能够自动完成这个操作,但我首先注意到的是您在每一行匹配时都重新编译了正则表达式:

            while ((line = sr.ReadLine()) != null)
            {
                if (line.StartsWith(">"))
                {// Match Sequence
                    re = new Regex(@"^>(\S+)");

如果您能完全去除常规表达式,那就更好了;大多数语言都提供某种形式的split函数,通常比正则表达式更快...


1
同意,在循环外定义're'会更清晰明了。 - matchdav
1
我对此进行了统计,最好的方法是将它们设为静态并使用 RegexOptions.Compiled - Yuriy Faktorovich

2

缓存并编译正则表达式,重新排序条件,减少修剪数量等。

public static Dictionary<string, string> FASTAFileReadIn(string file) {
    var seq = new Dictionary<string, string>();

    Regex re = new Regex(@"^>(\S+)", RegexOptions.Compiled);
    Regex nonWhitespace = new Regex(@"\S", RegexOptions.Compiled);
    Match m;
    string currentName = string.Empty;

    try {
        foreach(string line in File.ReadLines(file)) {
            if(line[0] == '>') {
                m = re.Match(line);

                if(m.Success) {
                    if(!seq.ContainsKey(m.Groups[1].Value)) {
                        seq.Add(m.Groups[1].Value, string.Empty);
                        currentName = m.Groups[1].Value;
                    }
                }
            } else if(currentName != string.Empty) {
                if(nonWhitespace.IsMatch(line)) {
                    seq[currentName] += line.Trim();
                }
            }
        }
    } catch(IOException e) {
        Console.WriteLine("An IO exception has been thrown!");
        Console.WriteLine(e.ToString());
    }

    return seq;
}

然而,这只是一种天真的优化。在了解FASTA格式后,我写出了以下代码:

public static Dictionary<string, string> ReadFasta(string filename) {
    var result = new Dictionary<string, string>
    var current = new StringBuilder();
    string currentKey = null;

    foreach(string line in File.ReadLines(filename)) {
        if(line[0] == '>') {
            if(currentKey != null) {
                result.Add(currentKey, current.ToString());
                current.Clear();
            }

            int i = line.IndexOf(' ', 2);

            currentKey = i > -1 ? line.Substring(1, i - 1) : line.Substring(1);
        } else if(currentKey != null) {
            current.Append(line.TrimEnd());
        }
    }

    if(currentKey != null)
        result.Add(currentKey, current.ToString());

    return result;
}

告诉我它是否有效;它应该更快。

1
string line in File.ReadAllLines() 这段代码是一次性从文件中构建整个数组或列表,还是按需逐行构建每个 line - sarnold
@sarnold:抱歉,你是对的。我指的是ReadLines(),它会创建一个IEnumerable<string>。(不过如果文件只有5MB,最好一开始就全部读进来可能更好一些...) - Ry-
如果当前键(序列的标识符)后面跟着制表符而不是空格会发生什么? - Cristian Ciupitu
1
另一个微小的问题:line.IndexOf(' ') -> line.IndexOf(' ', 1),因为您已经检查了第一个字符。 - Cristian Ciupitu
1
@CristianCiupitu:刚想说这个 :) 实际上,它应该是2,因为它不应该是空的。我认为至少不是这样...为什么我找不到实际的规范呢? - Ry-
显示剩余2条评论

1

通过使用BufferedStream,您可以大大提高阅读速度:

using (FileStream fs = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (BufferedStream bs = new BufferedStream(fs))
using (StreamReader sr = new StreamReader(bs))
{
    // Use the StreamReader
}

如果您的处理时间约为5分钟,那么@sarnold提到的Regex重新编译可能是您最大的性能杀手。


哈哈,当我看到你的答案时,我的第一个想法是,“嘿,我打赌90%的减速都来自那里”。 - sarnold

1
这是我的写法。没有更多的信息(例如平均字典条目长度),我无法优化StingBuilder容量。您也可以遵循Eric J.的建议并添加BufferedStream。如果您想提高性能,最好完全放弃正则表达式,但是它们更容易编写和管理,因此我理解为什么您想使用它们。
public static Dictionary<string, StringBuilder> FASTAFileReadIn(string file)
{
    var seq = new Dictionary<string, StringBuilder>();
    var regName = new Regex("^>(\\S+)", RegexOptions.Compiled);
    var regAppend = new Regex("\\S+", RegexOptions.Compiled);

    Match tempMatch = null;
    string currentName = string.Empty;
    try
    {
        using (StreamReader sReader = new StreamReader(file))
        {
            string line = string.Empty;
            while ((line = sReader.ReadLine()) != null)
            {
                if ((tempMatch = regName.Match(line)).Success)
                {
                    if (!seq.ContainsKey(tempMatch.Groups[1].Value))
                    {
                        currentName = tempMatch.Groups[1].Value;
                        seq.Add(currentName, new StringBuilder());
                    }
                }
                else if ((tempMatch = regAppend.Match(line)).Success && currentName != string.Empty)
                {
                    seq[currentName].Append(tempMatch.Value);
                }
            }
        }
    }
    catch (IOException e)
    {
        Console.WriteLine("An IO exception has been thrown!");
        Console.WriteLine(e.ToString());
    }

    return seq;
}

正如您所见,我已经稍微修改了您的字典,以使用优化的StringBuilder类来附加值。我还预编译了正则表达式一次,以确保您不会重复地重新编译相同的正则表达式。另外,我也将您的“添加”情况提取出来编译成正则表达式。

如果这样有助于您的性能,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接