从长字符串中读取大量(100万)子字符串(100个字符宽)读取数(300万个字符)

时间:2012-03-21 09:32:03

标签: c# string substring processing-efficiency

如何从C#中有效超过300万个字符的字符串中获取100万个子字符串?我编写了一个程序,涉及从300万个字符的字符串中读取长度为100的随机DNA读取(随机位置的子串)。有100万这样的读数。目前我运行一个运行100万次的while循环,并从具有300万字符的字符串中读取100个字符长度的子字符串。这需要很长时间。我能做些什么来更快地完成这个?

继承我的代码, len 是原始字符串的长度,在这种情况下为300万,它可能低至50,这就是为什么在while循环中检查。

while(i < 1000000 && len-100> 0) //len is 3000000
            {
                int randomPos = _random.Next()%(len - ReadLength);
                readString += all.Substring(randomPos, ReadLength) + Environment.NewLine;
                i++;


            }

4 个答案:

答案 0 :(得分:2)

使用StringBuilder来组合字符串将使处理量增加600倍(因为它可以避免每次附加到字符串时重复创建对象。

在循环之前(初始化容量避免在StringBuilder中重新创建支持数组):

StringBuilder sb = new StringBuilder(1000000 * ReadLength);

循环:

sb.Append(all.Substring(randomPos, ReadLength) + Environment.NewLine);
循环后

readString = sb.ToString();

使用char数组而不是字符串来提取值,因为您在调用Substring()时避免了对象创建,因此可以进一步提高30%:

循环之前

char[] chars = all.ToCharArray();

循环:

sb.Append(chars, randomPos, ReadLength);
sb.AppendLine();

编辑(最终版本不使用StringBuilder并在300毫秒内执行):

char[] chars = all.ToCharArray();    
var iterations = 1000000;
char[] results = new char[iterations * (ReadLength + 1)];    
GetRandomStrings(len, iterations, ReadLength, chars, results, 0);    
string s = new string(results);

private static void GetRandomStrings(int len, int iterations, int ReadLength, char[] chars, char[] result, int resultIndex)
{
    Random random = new Random();
    int i = 0, index = resultIndex;
    while (i < iterations && len - 100 > 0) //len is 3000000 
    {
        var i1 = len - ReadLength;
        int randomPos = random.Next() % i1;

        Array.Copy(chars, randomPos, result, index, ReadLength);
        index += ReadLength;
        result[index] = Environment.NewLine[0];
        index++;

        i++;
    }
}

答案 1 :(得分:1)

我认为会有更好的解决方案,但.NET StringBuilder类实例比String类实例更快,因为它以Stream形式处理数据。

您可以分割数据并使用.NET任务并行库进行多线程和并行化

编辑:为循环中的变量分配固定值以避免重新计算;

int x = len-100 
int y = len-ReadLength 

使用

StringBuilder readString= new StringBuilder(ReadLength * numberOfSubStrings);
readString.AppendLine(all.Substring(randomPos, ReadLength));

对于Parallelism,您应该将输入分成几部分。然后在单独的线程中对这些操作运行这些操作。然后结合结果。

重要:正如我之前的经验表明,这些操作使用.NET v2.0而不是v4.0运行得更快,因此您应该更改项目目标框架版本;但是你不能在.NET v2.0中使用任务并行库,所以你应该像老式的那样使用多线程

Thread newThread ......

答案 2 :(得分:0)

编辑:我放弃了使用memcpy的想法,我认为结果非常好。 我已经将一个3米长的字符串分成了30k字符串,每行长度为100,长度为43毫秒。

private static unsafe string[] Scan(string hugeString, int subStringSize)
{
    var results = new string[hugeString.Length / subStringSize];

    var gcHandle = GCHandle.Alloc(hugeString, GCHandleType.Pinned);

    var currAddress = (char*)gcHandle.AddrOfPinnedObject();

    for (var i = 0; i < results.Length; i++)
    {
        results[i] = new string(currAddress, 0, subStringSize);
        currAddress += subStringSize;
    }

    return results;
}

要使用问题所示案例的方法:

const int size = 3000000;
const int subSize = 100;

var stringBuilder = new StringBuilder(size);
var random = new Random();

for (var i = 0; i < size; i++)
{
    stringBuilder.Append((char)random.Next(30, 80));
}

var hugeString = stringBuilder.ToString();

var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++)
{
    var strings = Scan(hugeString, subSize);
}
stopwatch.Stop();

Console.WriteLine(stopwatch.ElapsedMilliseconds / 1000); // 43

答案 3 :(得分:0)

很长时间了?它应该不会那么久。

var file = new StreamReader(@"E:\Temp\temp.txt");
var s = file.ReadToEnd();
var r = new Random();
var sw = new Stopwatch();
sw.Start();
var range = Enumerable.Range(0,1000000);
var results = range.Select( i => s.Substring(r.Next(s.Length - 100),100)).ToList();
sw.Stop();
sw.ElapsedMilliseconds.Dump();
s.Length.Dump();

所以在我的机器上结果是807ms,字符串是4,055,442个字符。

编辑:我刚注意到你想要一个字符串作为结果,所以我的上述解决方案只是改为......

var results = string.Join(Environment.NewLine,range.Select( i => s.Substring(r.Next(s.Length - 100),100)).ToArray());

并且增加了大约100毫秒,所以仍然不到一秒钟。