从一个长字符串(300万个字符)中读取大量(100万个字符宽)的子字符串
本文关键字:字符 字符串 100万 300万 一个 读取 | 更新日期: 2023-09-27 18:28:33
如何在C#中有效地从一个超过300万个字符的字符串中提取100万个子字符串?我写了一个程序,它涉及从一个300万个字符的字符串中读取长度为100的随机DNA读数(来自随机位置的子字符串)。这样的阅读量有100万。目前,我运行了一个while循环,运行了100万次,并从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++;
}
使用StringBuilder组装字符串将使处理量增加600倍(因为它避免了每次附加到字符串时重复创建对象。
循环之前(初始化容量可以避免在StringBuilder中重新创建后备数组):
StringBuilder sb = new StringBuilder(1000000 * ReadLength);
环路中:
sb.Append(all.Substring(randomPos, ReadLength) + Environment.NewLine);
后循环:
readString = sb.ToString();
使用char数组而不是字符串来提取值可以再提高30%,因为在调用Substring()时可以避免创建对象:
循环前:
char[] chars = all.ToCharArray();
环路中:
sb.Append(chars, randomPos, ReadLength);
sb.AppendLine();
编辑(不使用StringBuilder并在300ms内执行的最终版本):
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++;
}
}
我认为会有更好的解决方案,但.NET StringBuilder类实例比String类实例更快,因为它将数据作为流处理。
您可以将数据拆分为多个部分,并使用.NET任务并行库进行多线程和并行
编辑:为循环外的变量指定固定值,以避免重新计算;
int x = len-100
int y = len-ReadLength
使用
StringBuilder readString= new StringBuilder(ReadLength * numberOfSubStrings);
readString.AppendLine(all.Substring(randomPos, ReadLength));
对于并行性,您应该将输入拆分为多个部分。然后在单独的线程中对工件运行这些操作。然后将结果合并。
重要信息:正如我之前的经验所表明的,这些操作在.NET v2.0而不是v4.0中运行得更快,因此您应该更改项目的目标框架版本;但是你不能在.NET v2.0中使用任务并行库,所以你应该像一样使用老式的多线程
Thread newThread ......
长时间有多长?应该不会那么久。
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,字符串是4055442个字符。
编辑:我刚刚注意到你想要一个字符串作为结果,所以我上面的解决方案只是改为…
var results = string.Join(Environment.NewLine,range.Select( i => s.Substring(r.Next(s.Length - 100),100)).ToArray());
并且增加了大约100毫秒,所以总共还不到一秒。
Edit:我放弃了使用memcpy的想法,我认为结果非常棒。我在43毫秒内把一根3米长的绳子分成了30公里长的100根绳子。
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