2

C#で300万文字を超える文字列から100万の部分文字列を効率的に取得するにはどうすればよいですか?私は、300万文字の文字列から100と言う長さのランダムなDNA読み取り(ランダムな位置からの部分文字列)を読み取るプログラムを作成しました。そのような読み取りは100万あります。現在、100万回実行されるwhileループを実行し、300万文字の文字列から100文字の長さの部分文字列を読み取ります。これには長い時間がかかります。これをより速く完了するために何ができますか?

ここに私のコードがあります。lenは元の文字列の長さです。この場合は300万です。これは、whileループのチェックが50になる理由です。

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


            }
4

4 に答える 4

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を使用せず、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++;
    }
}
于 2012-03-21T09:56:54.297 に答える
1

より良い解決策が来ると思いますが、.NET StringBuilderクラスインスタンスは、データをストリームとして処理するため、Stringクラスインスタンスよりも高速です。

データを分割して、マルチスレッド化と並列処理に.NETタスク並列ライブラリを使用できます。

編集:再計算を回避するために、ループ外の変数に固定値を割り当てます。

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

使用する

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

並列処理の場合は、入力を分割する必要があります。次に、これらの操作を別々のスレッドのピースに対して実行します。次に、結果を組み合わせます。

重要:以前の経験では、これらの操作はv4.0ではなく.NET v2.0でより高速に実行されることが示されているため、プロジェクトのターゲットフレームワークバージョンを変更する必要があります。ただし、.NET v2.0ではタスク並列ライブラリを使用できないため、次のような昔ながらの方法でマルチスレッドを使用する必要があります。

Thread newThread ......
于 2012-03-21T09:37:28.940 に答える
0

編集:私はmemcpyを使用するという考えを放棄しました、そして私は結果が非常に素晴らしいと思います。私は3mの長さの文字列を43ミリ秒でそれぞれ100の長さの30kの文字列に分割しました。

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
于 2012-03-21T10:00:35.843 に答える
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();

したがって、私のマシンでは、結果は807ミリ秒で、文字列は4,055,442文字です。

編集:結果として文字列が必要なことに気付いたので、上記のソリューションは次のように変更されます...

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

そして、約100msを追加するので、合計で1秒未満です。

于 2012-03-21T10:10:57.707 に答える