18

基本的にオフセットとカウントの観点から ArraySegment によって保持されるセグメントを返す方法について調べてきました。ArraySegment は完全な元の配列を保持しますが、セグメントへの変更が元の配列に反映されるという事実でそれを区切るだけです。ArraySegment の問題または制限は、セグメント自体が全体として返されず、値をトラバースする必要があることです。セグメント全体を返す最良の方法は何ですか?

 byte[] input = new byte[5]{1,2,3,4,5};
 ArraySegment<byte> delimited = new ArraySegment<byte>(input,0,2);
 byte[] segment = HERE I NEED SOMETHING THAT WILL RETURN THE SEGMENT i.e. [0,1,2]

最も重要な点は、セグメントはコピーであってはならず、元の配列を参照する必要があるということです。セグメントに何らかの変更を加えた場合は、元の配列に反映する必要があります。

どんなヒントでも大歓迎です、ありがとう!

課題のベンチマーク: ThomasdigEmAllからのいくつかの回答の後

わかりました、digEmAll と Thomas のコードに対していくつかのベンチマークを実行しましたが、驚いたことに、コードは圧倒的に高速です。まさに私が必死に探していたもの。これが結果です。

Construct             Size    Elements assigned    Iterations       Time
_______________________________________________________________________________

ArraySegmentWrapper   1500        1500              1000000       396.3 ms
Array.Copy            1500        1500              1000000       4389.04 ms

途方もない違いを見ることができるように、ArraySegment のコードを使用することは非常に明確です。以下はベンチマークコードです。「new」がループ内に入れられた理由について人々が主張するため、これは少し偏っている可能性があることに注意してください。コードの多くを移動せずに、現在手元にある状況を可能な限り再現しようとしています。これは私の一日を作りました!

namespace ArraySegmentWrapped
{
    class Program
    {

        public static Stopwatch stopWatch = new Stopwatch();
        public static TimeSpan span = new TimeSpan();
        public static double totalTime = 0.0;
        public static int iterations = 1000000;

        static void Main(string[] args)
        {
            int size = 1500;
            int startIndex = 0;
            int endIndex = 1499;
            byte[] array1 = new byte[size];
            byte[] array2 = null;

            for (int index = startIndex; index < size; index++)
            {
                array1[index] = (byte)index;
            }

            ArraySegmentWrapper<byte> arraySeg;

            for (int index = 0; index < iterations; index++)
            {
                stopWatch.Start();
                arraySeg = new ArraySegmentWrapper<byte>(array1, startIndex, endIndex);            
                stopWatch.Stop();
                totalTime += stopWatch.Elapsed.TotalMilliseconds;
            }

            Console.WriteLine("ArraySegment:{0:F6}", totalTime / iterations);
            stopWatch.Reset();
            totalTime = 0.0;

            for (int index = 0; index < iterations; index++)
            {
                stopWatch.Start();
                array2 = new byte[endIndex - startIndex + 1];
                Array.Copy(array1, startIndex, array2, 0, endIndex);
                stopWatch.Stop();
                totalTime += stopWatch.Elapsed.TotalMilliseconds;
            }
            Console.WriteLine("Array.Copy:{0:F6}", totalTime / iterations);                        


        }
    }
// Code for ArraySegmentWrapper goes here    

}

アクセス ベンチマーク (更新) Thomasがベンチマークについて指摘し、単純な配列へのアクセスは ArraySegmentに比べて高速であると言った後、彼は完全に正しかった。しかし、リリース モードでテストする必要があることを digEmAll が指摘したので (デバッグ モードでのテストの古い間違いで申し訳ありません)、コードを上記とほぼ同じままにしました (反復をゼロ 2 つ減らしました - 出力が完了するまで非常に長く待つことができませんでした申し訳ありません)と同じ数の要素にアクセスするためのいくつかの変更、以下は私が得たものです。

Construct             Size    Elements accessed    Iterations       Time
_______________________________________________________________________________

ArraySegmentWrapper   1500        1500              1000000       5268.3 ms
Array.Copy            1500        1500              1000000       4812.4 ms

割り当ては非常に高速ですが、ArraySegments を介したアクセスは遅いと結論付けました。

4

4 に答える 4

6

次の一連の拡張メソッドを使用して、配列セグメントを操作します。

    #region ArraySegment related methods

    public static ArraySegment<T> GetSegment<T>(this T[] array, int from, int count)
    {
        return new ArraySegment<T>(array, from, count);
    }

    public static ArraySegment<T> GetSegment<T>(this T[] array, int from)
    {
        return GetSegment(array, from, array.Length - from);
    }

    public static ArraySegment<T> GetSegment<T>(this T[] array)
    {
        return new ArraySegment<T>(array);
    }

    public static IEnumerable<T> AsEnumerable<T>(this ArraySegment<T> arraySegment)
    {
        return arraySegment.Array.Skip(arraySegment.Offset).Take(arraySegment.Count);
    }

    public static T[] ToArray<T>(this ArraySegment<T> arraySegment)
    {
        T[] array = new T[arraySegment.Count];
        Array.Copy(arraySegment.Array, arraySegment.Offset, array, 0, arraySegment.Count);
        return array;
    }

    #endregion

次のように使用できます。

byte[] input = new byte[5]{1,2,3,4,5};
ArraySegment<byte> delimited = input.GetSegment(0, 2);
byte[] segment = delimited.ToArray();
于 2011-04-22T15:03:02.433 に答える
2

C# (および一般的な .NET) では、別の配列の内部を「指す」標準の配列参照を作成することはできません。そのため、使用する API を変更して ArraySegment インスタンスを処理できるようにするか、データのコピーを作成して、コピーを操作した後に変更を元に戻す必要があります。とにかく、配列への参照を渡すと絶縁が壊れ、配列のコンシューマーの数が増えるにつれてバグを追跡するのが難しくなるため、これは一般的により安全なアプローチです。.NET では、配列のサイズが極端に大きくない限り、新しい配列インスタンスの構築と値のコピーは比較的安価であるため、パフォーマンスへの影響は一般的に無視できます。

パフォーマンスの問題が発生していて、マイクロ最適化が必要な場合は、安全でない C# コード (配列参照を修正してポインターを渡すことができる場所) を使用するか、パフォーマンスが重要なコードを C++/CLI に引き出すことをお勧めします。アンマネージ メモリを使用して計算を実行できるアセンブリ。最初にコードをプロファイリングして、これが本当にボトルネックであることを確認することをお勧めします。圧縮 GC ヒープの性質上、頻繁に小さいメモリを割り当てると C よりも安価になるため、.NET で新しいメモリを割り当てることを恐れる必要はありません。 .)

于 2011-04-22T15:14:17.567 に答える
1

私がこのトピックに投稿した回答を確認してくださいこちら.

基本的に必要なことは、ArraySegment を IList にキャストして、期待する機能を取得することだけです。

于 2015-05-19T23:43:41.627 に答える