web-dev-qa-db-ja.com

アクセス違反例外ミステリー

私はかなり長い間EMGU + OpenCVを使用していて、このAccessViolationExceptionの謎に遭遇しました。

まず最初に、コード:

_class AVE_Simulation
    {
        public static int Width = 7500;
        public static int Height = 7500;
        public static Emgu.CV.Image<Rgb, float>[] Images;

        static void Main(string[] args)
        {
            int N = 50;
            int Threads = 5;

            Images = new Emgu.CV.Image<Rgb, float>[N];
            Console.WriteLine("Start");

            ParallelOptions po = new ParallelOptions();
            po.MaxDegreeOfParallelism = Threads;
            System.Threading.Tasks.Parallel.For(0, N, po, new Action<int>((i) =>
            {
                Images[i] = GetRandomImage();
                Console.WriteLine("Prossing image: " + i);
                Images[i].SmoothBilatral(15, 50, 50);
                GC.Collect();
            }));
            Console.WriteLine("End");
        }

        public static Emgu.CV.Image<Rgb, float> GetRandomImage()
        {
            Emgu.CV.Image<Rgb, float> im = new Emgu.CV.Image<Rgb, float>(Width, Height);

            float[, ,] d = im.Data;
            Random r = new Random((int)DateTime.Now.Ticks);

            for (int y = 0; y < Height; y++)
            {
                for (int x = 0; x < Width; x++)
                {
                    d[y, x, 0] = (float)r.Next(255);
                    d[y, x, 1] = (float)r.Next(255);
                    d[y, x, 2] = (float)r.Next(255);
                }
            }
            return im;
        }

    }
_

コードは単純です。画像の配列を割り当てます。ランダムな画像を生成し、乱数を入力します。画像に対してバイラテラルフィルターを実行します。それでおしまい。

このプログラムを単一のスレッド(Threads = 1)で実行すると、すべてが問題なく正常に動作しているように見えます。しかし、同時スレッドの数を5に増やすと、AccessViolationExceptionがすぐに発生します。

私はOpenCVコードを調べ、OpenCV側に割り当てがないことを確認しました。また、固定されていないオブジェクトやその他の問題を検索するEMGUコードも調べましたが、すべてが正しいようです。

いくつかの注意:

  1. GC.Collect()を削除すると、AccessViolationExceptionの取得頻度は低くなりますが、最終的には発生します。
  2. これは、リリースモードで実行された場合にのみ発生します。デバッグモードでは、例外は発生しませんでした。
  3. 各イメージは675MBですが、割り当てに問題はなく(メモリが割り当てられています)、システムのメモリが不足した場合に備えて「OutOfMemoryException」がスローされます。
  4. バイラテラルフィルターを使用しましたが、他のフィルター/関数でもこの例外が発生します。

どんな助けでもいただければ幸いです。私はこれを1週間以上修正しようとしています。

i7(オーバークロックなし)、Win7 64ビット、32GB RAM、VS 2010、Framework 4.0、OpenCV 2.4.3

スタック:

_Start
Prossing image: 20
Prossing image: 30
Prossing image: 40
Prossing image: 0
Prossing image: 10
Prossing image: 21

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.Tasks.ThreadPoolTaskScheduler.TryExecuteTaskInline(Task task, Boolean taskWasPreviouslyQueued)
   at System.Threading.Tasks.TaskScheduler.TryRunInline(Task task, Boolean taskWasPreviouslyQueued)
   at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler, Boolean waitForCompletion)
   at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Func`4 bodyWithLocal, Func`1 loc
alInit, Action`1 localFinally)
   at System.Threading.Tasks.Parallel.For(Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action`1 body)
   at TestMemoryViolationCrash.AVE_Simulation.Main(String[] args) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 35

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()

Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
   at Emgu.CV.CvInvoke.cvSmooth(IntPtr src, IntPtr dst, SMOOTH_TYPE type, Int32 param1, Int32 param2, Double param3, Double param4)
   at TestMemoryViolationCrash.AVE_Simulation.<Main>b__0(Int32 i) in C:\branches\1.1\TestMemoryViolationCrash\Program.cs:line 32
   at System.Threading.Tasks.Parallel.<>c__DisplayClassf`1.<ForWorker>b__c()
   at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
   at System.Threading.Tasks.Task.<>c__DisplayClass10.<ExecuteSelfReplicating>b__f(Object param0)
   at System.Threading.Tasks.Task.Execute()
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
   at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
Press any key to continue . . .
_
23
Gilad

あなたの例は、Image.SmoothBilatralからの結果画像への参照を保持していません。入力画像は静的配列に基づいているため、問題ありません。

Emgu.CVイメージのデータ配列は実際のイメージ内のGCHandleに固定されます。これは、イメージに配列が含まれ、GCHandleのポインターがアンマネージコードによって使用されている間(イメージへの管理されたルート)。

Image.SmoothBilatralメソッドは、ポインターを渡して返す以外に一時的な結果画像を処理しないため、スムースの処理中に結果画像を収集できる範囲で最適化されると思います。

このクラスのファイナライザーがないため、opencvはアンマネージドイメージヘッダー(マネージドイメージデータへのポインターを持つ)を解放するように要求されないため、opencvは引き続き使用可能なイメージ構造を持っていると見なします。

SmoothBilatralの結果を参照し、それを使って何かを行う(破棄するなど)ことで修正できます。

この拡張メソッドも機能します(つまり、結果を使用せずにベンチマークで正常に呼び出せるようにします)。

public static class BilateralExtensionFix
{
    public static Emgu.CV.Image<testchannels, testtype> SmoothBilateral(this Emgu.CV.Image<testchannels, testtype> image, int p1, int p2 , int p3)
    {
        var result = image.CopyBlank();
        var handle = GCHandle.Alloc(result);
        Emgu.CV.CvInvoke.cvSmooth(image.Ptr, result.Ptr, Emgu.CV.CvEnum.SMOOTH_TYPE.CV_BILATERAL, p1, p1, p2, p3);
        handle.Free();
        return result;
    }
}

EmguCVがすべきことは、相互運用呼び出しを行うときにopencvに渡すポインターを固定することだけだと思います。

p.s OpenCvバイラテラルフィルターは、すべてのチャネルにわたってゼロ変動(min()= max())で渡されたあらゆる種類のフロート画像でクラッシュします(問題と非常によく似たエラーを生成します)。それがどのように構築するかという理由で、ビン化されたexp()ルックアップテーブルだと思います。

これは次の方法で再現できます。

    // create new blank image
    var zeroesF1 = new Emgu.CV.Image<Rgb, float>(75, 75);
    // uncomment next line for failure
    zeroesF1.Data[0, 0, 0] += 1.2037063600E-035f;
    zeroesF1.SmoothBilatral(15, 50, 50);

テストコードのバグが原因で実際​​にこのエラーが発生することがあったため、これは混乱を招きました...

12
Peter Wishart

どのバージョンのEmguCVを使用していますか? 2.4.3バージョンが見つかりませんでした。

コードに問題がないことを確認してください

Emgu.CV.Imageコンストラクターに同時実行性の問題がある可能性があります(マネージラッパーまたはアンマネージコードのいずれかで)。 Emgu CVトランクでマネージドデータ配列が処理される方法はしっかりしているように見えますが、イメージコンストラクター中に割り当てられたアンマネージドデータがいくつかありますが、これは間違っている可能性があります。

試してみるとどうなりますか:

  • Images[i] = GetRandomImage();を並列For()の外に移動します。
  • lock()Imageコンストラクターの周りにGetRandomImage()を叩きます

同様の問題を抱えている誰かのクローズドバグレポートがあることに気づきました(イメージコンストラクターの呼び出しは並行して発生しますが、イメージ自体はスレッド間で共有されません) ここ

[編集]

はい、これは奇妙なものです。在庫の2.4.2バージョンとOpenCVバイナリで再現できます。

並列のスレッドの数がコアの数(私にとっては2を超える)を超えた場合にのみクラッシュするようです。テストシステムにコアがいくつあるかを知ることは興味深いことです。

また、コードがデバッガーにアタッチされておらず、コードの最適化が有効になっている場合にのみクラッシュします-デバッガーがアタッチされたリリースモードでそれを観察したことがありますか?

SmoothBilateral関数はCPUにバインドされているため、コアの数を超えてMaxDegreeOfParallelismを使用しても実際には何のメリットもありません。したがって、スレッドとコアの数がリグにも当てはまる場合、数について私が見つけたものを想定した完全な回避策があります(sodsの法則は次のように予測しています:そうではありません)。

したがって、Emguには同時実行性/揮発性の問題があり、JIT最適化が実行されたとき、およびGCが管理対象データを移動しているときにのみ発生すると思います。しかし、あなたが言うように、Emguコードには、管理対象オブジェクトへの固定されていないポインタの明らかな問題はありません。

私はまだそれを適切に説明することはできませんが、これまでに見つけたものは次のとおりです。

GC.Collect +コンソールログが削除され、GetRandomImage()の呼び出しがシリアル化され、MSVCの外部でコードが実行されたため、問題を再現できませんでした(これにより頻度が減った可能性があります)。

            public static int Width = 750;
            public static int Height = 750;
...
                int N = 500;
                int Threads = 11;
                Images = new Emgu.CV.Image<Rgb, float>[N];
                Console.WriteLine("Start");
                ParallelOptions po = new ParallelOptions();
                po.MaxDegreeOfParallelism = Threads;
                for (int i = 0; i < N; i++)
                {
                    Images[i] = GetRandomImage();
                }
                System.Threading.Tasks.Parallel.For(0, N, po, new Action<int>((i) =>
                {
                    //Console.WriteLine("CallingSmooth");
                    Images[i].SmoothBilatral(15, 50, 50);
                    //Console.WriteLine("SmoothCompleted");
                }));
                Console.WriteLine("End");

GCを起動するタイマーを追加しました。並列の外側で収集しますが、通常よりも頻繁に起動します。

        var t = new System.Threading.Timer((dummy) => { 
            GC.Collect(); 
        }, null, 100,100);

そして、この変更ではまだ問題を再現できませんが、スレッドプールがビジーであるため、GCコレクトはデモよりも一貫して呼び出されていません。また、メインループで管理された割り当ては発生していません(またはほとんど発生していません)。集める。 SmoothBilatral呼び出しの周りのコンソールログのコメントを解除すると、エラーがかなり迅速に再現されます(GCに収集するものを与えることによって)。

[別の編集]

OpenCV 2.4.2リファレンスマニュアル は、cvSmoothが非推奨であり、「メディアンおよびバイラテラルフィルターは1チャネルまたは3チャネル8ビットイメージで機能する」と述べています。画像をインプレースで処理することはできません。」...あまり勇気づけられません!

バイトまたはフロート画像にメディアンフィルターを使用し、バイト画像にバイラテラルを使用すると問題なく機能することがわかりました。CLR/ GCの問題がこれらのケースにも影響しない理由がわかりません。

したがって、C#テストプログラムへの奇妙な影響にもかかわらず、これはEmgu/OpenCVのバグであると私はまだ考えています。

まだ行っていない場合は、自分でコンパイルしたopencvバイナリでテストする必要があります 自分でコンパイル それでも失敗する場合は、テストをC++に変換します。

N.b.そのOpenCVには 独自の並列処理の実装 があり、おそらくより速く動作します。

2
Peter Wishart