5

重複の可能性:
c#int配列を初期化するためのよりスリムな方法

基本的に、以下に示すものよりも効率的なコードがあるかどうかを知りたいです

    private static int[] GetDefaultSeriesArray(int size, int value)
    {
        int[] result = new int[size];
        for (int i = 0; i < size; i++)
        {
            result[i] = value;
        }
        return result;
    }

ここで、サイズは10から150000まで変化します。小さなアレイの場合は問題になりませんが、上記を行うためのより良い方法があるはずです。VS2010(.NET 4.0)を使用しています

4

6 に答える 6

8

C#/ CLRには、デフォルト以外の値で配列を初期化する方法が組み込まれていません。

コードは、アイテムごとの操作で測定した場合に得られるのと同じくらい効率的です。

巨大な配列のチャンクを並列に初期化すると、初期化が高速になる可能性があります。このアプローチでは、マルチスレッド操作のコストが重要であるため、注意深く調整する必要があります。

ニーズを分析し、初期化全体を完全に削除することで、はるかに優れた結果を得ることができます。つまり、配列に通常定数値が含まれている場合、オブジェクトに最初はバッキング配列がなく、simpyが定数値を返す、ある種のCOW(コピーオンライト)アプローチを実装できます。要素への書き込み時に、バッキング配列が作成されます(部分的である可能性があります)。変更されたセグメントの場合。

遅くなりますが、よりコンパクトなコード(読みやすくなる可能性があります)は、を使用することEnumerable.Repeatです。ToArray大きな配列に大量のメモリが割り当てられることに注意してください(LOHに割り当てられる可能性もあります)-Enumerable.Rangeでメモリを大量に消費しますか?

 var result = Enumerable.Repeat(value, size).ToArray();
于 2012-12-20T21:18:02.353 に答える
4

速度を向上させる1つの方法は、を利用することArray.Copyです。それは、メモリのより大きなセクションを一括して割り当てる、より低いレベルで機能することができます。

割り当てをバッチ処理することで、配列を1つのセクションからそれ自体にコピーすることができます。

その上、バッチ自体を非常に効果的に並列化できます。

これが私の最初のコードです。サイズが1,000万アイテムのサンプル配列を備えた私のマシン(コアが2つしかない)では、15%程度のスピードアップが得られました。バッチサイズを試して(効率を維持するためにページサイズの倍数にとどまるようにしてください)、所有しているアイテムのサイズに合わせて調整する必要があります。小さい配列の場合、最初のバッチがいっぱいになることはないため、コードとほぼ同じになりますが、そのような場合でも(著しく)悪化することはありません。

private const int batchSize = 1048576;
private static int[] GetDefaultSeriesArray2(int size, int value)
{

    int[] result = new int[size];

    //fill the first batch normally
    int end = Math.Min(batchSize, size);
    for (int i = 0; i < end; i++)
    {
        result[i] = value;
    }

    int numBatches = size / batchSize;

    Parallel.For(1, numBatches, batch =>
    {
        Array.Copy(result, 0, result, batch * batchSize, batchSize);
    });

    //handle partial leftover batch
    for (int i = numBatches * batchSize; i < size; i++)
    {
        result[i] = value;
    }

    return result;
}
于 2012-12-20T22:01:19.647 に答える
1

パフォーマンスを向上させるもう1つの方法は、非常に基本的な手法であるループ展開です。

2,000万個のアイテムで配列を初期化するコードをいくつか作成しました。これは、100回繰り返し実行され、平均が計算されます。ループを展開しない場合、これには約44MSかかります。10のループ展開で、プロセスは23MSで終了します。

 private void Looper()
        {
            int repeats = 100;
            float avg = 0;

            ArrayList times = new ArrayList();

            for (int i = 0; i < repeats; i++)
                times.Add(Time()); 

            Console.WriteLine(GetAverage(times)); //44

            times.Clear();

            for (int i = 0; i < repeats; i++)            
                times.Add(TimeUnrolled()); 

            Console.WriteLine(GetAverage(times)); //22

        }

 private float GetAverage(ArrayList times)
        {
            long total = 0;
            foreach (var item in times)
            {
                total += (long)item;
            }

            return total / times.Count;
        }

        private long Time()
        {
            Stopwatch sw = new Stopwatch();
            int size = 20000000;
            int[] result = new int[size];
            sw.Start();


            for (int i = 0; i < size; i++)
            {
                result[i] = 5;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            return sw.ElapsedMilliseconds;
        }

        private long TimeUnrolled()
        {
            Stopwatch sw = new Stopwatch();
            int size = 20000000;
            int[] result = new int[size];
            sw.Start();


            for (int i = 0; i < size; i += 10)
            {
                result[i] = 5;
                result[i + 1] = 5;
                result[i + 2] = 5;
                result[i + 3] = 5;
                result[i + 4] = 5;
                result[i + 5] = 5;
                result[i + 6] = 5;
                result[i + 7] = 5;
                result[i + 8] = 5;
                result[i + 9] = 5;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            return sw.ElapsedMilliseconds;
        }
于 2012-12-20T22:21:36.333 に答える
0
Enumerable.Repeat(value, size).ToArray();
于 2012-12-20T21:18:07.537 に答える
0

Enumerable.Repeatの読み取りは、ops標準のforループよりも20倍遅く、速度を向上させる可能性があると私が見つけた唯一のことは

private static int[] GetDefaultSeriesArray(int size, int value)
{
    int[] result = new int[size];
    for (int i = 0; i < size; ++i)
    {
        result[i] = value;
    }
    return result;
}

i++が++iに変更されていることに注意してください。i ++はiをコピーし、iをインクリメントして、元の値を返します。++iはインクリメントされた値を返すだけです

于 2012-12-20T21:32:19.300 に答える
-2

すでに述べたように、次のような並列処理を活用できます。

int[] result = new int[size];
Parallel.ForEach(result, x => x = value);
return result;

申し訳ありませんが、これでパフォーマンステストを行う時間がありませんでした(このマシンにVSがインストールされていません)が、それを実行して結果を共有できれば素晴らしいと思います。

編集:コメントによると、パフォーマンスの点では同等だと思いますが、並列forループを試すことができます:

Parallel.For(0, size, i => result[i] = value);
于 2012-12-20T21:28:08.660 に答える