6

私のプロジェクトでは、まずファイルから画像を読み込み、すべてのピクセルを 2Dpixels[,]配列に入れます。次に、各ピクセルを調べて、色に基づいて「ビン」に分割し、各ビンを並べ替えます。したがって、 aBinをカプセル化するオブジェクトがあり、(比較的少数の) ビンを含む がありますList<Pixel>List<Bin>

私の問題は、非常に大きな画像 (1920x1200 = 230 万ピクセルなど) からこれらのビンを埋めようとすると、使用しているアルゴリズムが思ったよりも遅くなり、問題をいくつかの C# まで追跡したことです。私が予想していたよりも時間がかかっているように見える言語固有の機能。これらのボトルネックを解消するために C# をより適切に使用する方法についてアドバイスをお願いします。

画像を読み込んだ後、「fillBinsFromSource」という関数を呼び出します。この関数は、列挙可能なピクセルのリストを取得し、それらが属するビンを見つけてそこに配置します。

public void fillBinsFromSource(IEnumerable<Pixel> source)
    {
        Stopwatch s = new Stopwatch();
        foreach (Pixel p in source)
        {
            s.Start();
            // algorithm removed for brevity
            // involves a binary search of all Bins and a List.Add call
            s.Stop();
        }           
    }

大きな画像の場合、私のアルゴリズムにはしばらく時間がかかることが予想されますが、ストップウォッチを関数呼び出しの外に置くと、で発生した時間の約 2 倍の時間がかかることがわかりましsforeach。この関数の半分の時間 (1920x1200 画像の場合、1.6 秒のうち約 800 ミリ秒)。

列挙可能なリストを渡す必要がある理由は、ユーザーが画像全体ではなく、画像の小さな領域のみを追加する場合があるためです。時間のかかる呼び出しでは、最初に画像のリストから、次にリスト内の各画像から、次のように (簡略化して) 複数のイテレータを渡します。

class ImageList : IEnumerable<Pixel>
{
    private List<Image> imageList;
    public IEnumerator<Pixel> GetEnumerator()
    {
        foreach (Image i in imageList)
            foreach (Pixel p in i)
                yield return p;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    } 

class Image : IEnumerable<Pixel>
{
    private Pixel[,] pixels; // all pixels in the image        
    private List<Pixel> selectedPixels;// all pixels in the user's selection

    public IEnumerator<Pixel> GetEnumerator()
    {
        if (selectedPixels == null)
            for (int i = 0; i < image.Width; i++)
                for (int j = 0; j < image.Height; j++)
                    yield return pixels[i, j];
        else
            for (int i = 0; i < selectedPixels.Count; i++)
                yield return selectedPixels[i];
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

それから最後に私はこれを呼び出します

ImageList list; // pretend it contains only 1 image, and it's large
fillBinsFromSource(list);

質問 1) ピクセルの 2D 配列と選択した領域の両方を列挙する必要があるため、ユーザーが選択した内容に応じて、列挙は非常に遅くなります。どうすればこれをスピードアップできますか?

Pixel次に、これらすべてのビンをオブジェクトで満たした後、それらを並べ替えます。次のように、を呼び出しList<Pixel>.Sort()て依存しIComparableます。

ImageList list; // pretend it contains only 1 image, and it's large
fillBinsFromSource(list);
foreach(Bin b in allBins)
    b.Sort(); // calls List<Pixel>.Sort()


class Pixel : IComparable
{
    // store both HSV and RGB
    float h, s, v;
    byte r, g, b;

    // we sort by HSV's value property
    public int CompareTo(object obj)
    {
        // this is much faster than calling CompareTo on a float
        Pixel rhs = obj as Pixel;
        if (v < rhs.v)
            return -1;
        else if (v > rhs.v)
            return 1;
        return 0;
    }

問題 2) allBins7 つの要素があるとします。合計 230 万Pixelの s を持つ 7 つの個別のリストを並べ替えるには、約 2 秒かかります。230 万個の random の 1 つのリストを並べ替えるにintは、200 ミリ秒未満かかります。プリミティブ型を使用するとある程度のスピードアップがあることは理解できますが、実際に使用すると 10 倍以上遅くなりますIComparableか? ここで必要なスピードアップはありますか?

長い質問で申し訳ありませんが、何かアドバイスがあればよろしくお願いします。

4

3 に答える 3

3

コードのプロファイリングを行い、何が遅いのかを確認する必要があります。

最も明白な:

  • PixelジェネリックIComparable<Pixel>を実装していないため、Compare はさらに多くの作業を行う必要がありました。
  • Pixel 値の型を作成してみてください。ほとんどの場合、パフォーマンスの低下が見られますが、そうでない場合もあります。
  • Pixelパフォーマンスが許容範囲を下回っている場合は、2d 範囲 (四角形) を渡し、イテレータではなくインデックスでオブジェクトに直接アクセスすることを検討してください。イテレータは便利ですが、無料ではありません。
于 2012-11-30T22:57:23.887 に答える
2

生の金属パフォーマンスが必要な場合、ビジター パターンや仮想継承などのあらゆる種類の間接化は毒です。仮想呼び出し、割り当て、予測不可能な分岐は、時間の 99.99% が費やされる小さくタイトな内部ループがある種類のアルゴリズムに多くのダメージを与えます。

なんで?CPU は多数 (数十個) の命令を並行して実行することを好むためです。それができるのは、命令ストリームを先読みできる場合だけです。前述のことがそれを防ぎます。

最も内側のループを正しくする必要があります。そこに割り当てないでください。仮想関数 (またはインターフェイス メソッドまたはデリゲート) を呼び出さないでください。

おそらく、最も内側のループは、ハードコーディングされたカーネルを使用して、特定の画像の四角形を処理する必要があります。処理関数をピクセルごとに実装する代わりに、長方形ごとに実装します。

対照的に、画像のストリームをどのように提供するかは問題ではありません。そこでLINQを自由に使用してください。画像ごとに数百万のピクセルがあるため、これは少量の操作です。

于 2012-11-30T23:48:53.337 に答える
1

イテレータを使用する代わりに、または最初からピクセルの配列/リストを作成する代わりに、ビジター パターンを使用できます。任意の選択を表す画像、画像リスト、およびその他のオブジェクトはすべて、単一のメソッドを持つビジター クラスを受け入れ、VisitPixelオブジェクトが表す各ピクセルに対してそのメソッドを呼び出すことができます。ビジター クラスは、訪問されたすべてのピクセルをビニングする役割を担います。

これにより、すべてのピクセルを別の配列に抽出する必要がなくなる場合があります。forまた、単純なループを優先してイテレータの作成を排除することもできます。

Alexei Levenkov は、2 番目の質問に関していくつかの良い点を述べています。のインスタンスを取るSort オーバーロードを使用すると、さらに高速になる場合がありますIComparer<T>

于 2012-11-30T23:23:05.103 に答える