.NET 4:スリムシンク

最終的に、 RTMバージョンの .NET 4およびVisual Studio 2010がリリースされ、プラットフォームの最終バージョンでの最終的な最適化が完了し、安全にテストできます。



.NET 4の重要な革新の1つがParallel Extensions (コードの並列化を促進し、マルチスレッド環境で動作するためのツールのセット)であることは秘密ではありません。 このセットの他のツールの中には、同期プリミティブもあり、これらも処理されています。



特に、非常に人気の高いプリミティブManualResetEventの修正バージョンが登場しました 。 このツールにあまり慣れていない人のために:このツールを使用すると、異なるスレッドで動作するコードのセクションの実行を同期できます。 オブジェクトには、インストールとアンインストールの2つの状態があります。 一方から他方への遷移は、Set()およびReset()メソッドを使用して実行されます。 簡単に言えば、それがどのように機能するか(ここでmreはManualResetEvent型のインスタンスです):

ストリーム1 ストリーム2 時間
mre.Reset();

mre.WaitOne();
//コード実行 0
//待つ //コード実行 1
//待つ //コード実行 2
//待つ //コード実行 3
//待つ mre.Set(); 4
//コード実行 // ... 5


.NET 4からのこのプリミティブの改良版はManualResetEventSlimと呼ばれます 。 主なアイデアは、1つのスレッドのみがプリミティブにアクセスする場合のオーバーヘッドを削減することです。 いわゆる 「ハイブリッドスキーム」。次のように実装できます。

internal sealed class SimpleHybridLock : IDisposable

{

private Int32 m_waiters = 0;

private AutoResetEvent m_waiterLock = new AutoResetEvent( false );



public void Enter()

{

if (Interlocked.Increment( ref m_waiters) == 1)

return ;

m_waiterLock.WaitOne();

}



public void Leave()

{

if (Interlocked.Decrement( ref m_waiters) == 0)

return ;

m_waiterLock.Set();

}



public void Dispose()

{

m_waiterLock.Dispose();

}

}




* This source code was highlighted with Source Code Highlighter .






これは、リヒターの著書「C#を介したCLR」第3版の例です。 SimpleHybridLockプリミティブには、パブリックのEnter()およびLeave()メソッドがいくつかあります。 これらのメソッドを呼び出すと、コードのクリティカルセクションがフレーム化され、常に1つのスレッドでのみ実行されます。 クラスコードは非常に透過的です。Enter()を呼び出した最初のスレッドは内部カウンターを1増やします。2番目のスレッドもカウンターを増やし、誰かがm_waiterLockオブジェクトのSet()を呼び出すまでブロックされます。 T.O. プリミティブへの競合アクセスがない場合、パフォーマンスの点で非常に「重い」WaitOne()およびSet()メソッドは呼び出されません。 これは、コードの速度にプラスの影響を与える可能性があります。



ManualResetEventSlimは、同様の原理に基づいて構築されています。 たとえば、再帰呼び出しの制御など、よりインテリジェントなメカニズムがあると思います。 プラットフォームのエンドユーザーとして、ManualResetEventとその* -Slimバージョンの実際のパフォーマンスの違いに興味がありました。 彼女を見つけるために、小さな「ベンチマーク」を用意しました。 これはこの種のコンソールアプリケーションです。

static void Main( string [] args)

{

ManualResetEventSlim mres = new ManualResetEventSlim( false );

ManualResetEventSlim mres2 = new ManualResetEventSlim( false );



ManualResetEvent mre = new ManualResetEvent( false );



long total = 0;

int COUNT = 50;



for ( int i = 0; i < COUNT; i++)

{

mres2.Reset();

//

Stopwatch sw = Stopwatch.StartNew();



//

ThreadPool.QueueUserWorkItem((obj) =>

{

//Method(mres, true);

Method2(mre, true );

mres2.Set();

});

//

//Method(mres, false);

Method2(mre, false );



//,

mres2.Wait();

sw.Stop();



Console .WriteLine( "Pass {0}: {1} ms" , i, sw.ElapsedMilliseconds);

total += sw.ElapsedMilliseconds;

}



Console .WriteLine();

Console .WriteLine( "===============================" );

Console .WriteLine( "Done in average=" + total / ( double )COUNT);

Console .ReadLine();

}



// ManualResetEventSlim

private static void Method(ManualResetEventSlim mre, bool value )

{

//

for ( int i = 0; i < 9000000; i++)

{

if ( value )

{

mre.Set();

}

else

{

mre.Reset();

}

}

}



// ManualResetEvent

private static void Method2(ManualResetEvent mre, bool value )

{

//

for ( int i = 0; i < 9000000; i++)

{

if ( value )

{

mre.Set();

}

else

{

mre.Reset();

}

}

}

}




* This source code was highlighted with Source Code Highlighter .






Main()メソッドでは、プリミティブのインスタンスを作成し、メインスレッドとプールスレッドの2つのスレッドからそれらへのアクセスをモデル化します。 この場合、プールスレッドはループ内の状態を設定し、メインスレッドはリセットされます。 実験をCOUNT回繰り返し、画面に平均値を表示します。 これが私のラップトップ(2コアCPU T7250、Win 7 x64)で起こったことです。



ManualResetEvent ManualResetEvent Slim
画像画像


違いは明らかであり、非常に重要です-約10倍。



T.O. Set()およびReset()を呼び出すとき、Windowsカーネルのオブジェクトへの長い呼び出しであるとは限らず、かなりの速度で勝つことができるため、ManualResetEventSlimを使用することをお勧めします。



All Articles