.NET 4.6で追加された - GC.TryStartNoGCRegion~GC.EndNoGCRegionを使ってみる

  • 11
    Like
  • 2
    Comment
More than 1 year has passed since last update.

.NET 4.6でGC.TryStartNoGCRegion~GC.EndNoGCRegionというものが追加されました。
GC.TryStartNoGCRegionからGC.EndNoGCRegionまでの間、自動GCを抑制することができる。

処理中にGCが起きえるケース

下記のコードは、1MBのバイト配列を生成し、なおかつGCの回収対象となりえる構成です。

using System;
using System.Runtime;
using System.Threading;

namespace TestSpace {
    class Program {
        static void Main( string[] args ) {
            for( int i = 0; i < 30; i++ ) {
                var n = create();
                Thread.Sleep( 100 );
                GC.KeepAlive( n );

                Console.WriteLine( $"GC:{GC.CollectionCount( 0 ) } {GC.GetTotalMemory( false ):#,##byte} " );
            }
            Console.ReadLine();

        }

        public static WeakReference<byte[]> create() {
            var weak = new WeakReference<byte[]>( new byte[1048576] );
            return weak;
        }

    }
}

動作環境により変動しますが、何度かGCが発生していることが確認できます。

結果例
GC:0 3,314,892byte
GC:1 1,142,896byte
GC:1 2,191,504byte
GC:1 3,240,112byte
GC:2 1,142,184byte
GC:2 2,190,792byte
GC:2 3,239,400byte
GC:3 1,141,544byte
GC:3 2,190,152byte

GC.TryStartNoGCRegion~GC.EndNoGCRegionで囲んでみる

速度が重視されるような場合、GCの発生がボトルネックになりえる可能性があります。

GC.TryStartNoGCRegionにメモリサイズ(バイト単位)を指定します。
ここで指定したサイズが消費されるまでGCが抑制されるようになります。

例えば、GC.TryStartNoGCRegion( 15728640 ) と指定すると、15MB消費するまで
GCを抑制することになります。

なお、指定可能な最大サイズは、実行環境によって決められています。
詳細は、Fundamentals of Garbage Collectionを参照のこと。

一般的なクライアントPCで32ビット環境では、16MB、64ビット環境では、256MBです。
下記の例は、32ビット環境を想定したものです。

using System;
using System.Runtime;
using System.Threading;

namespace TestSpace {
    class Program {
        static void Main( string[] args ) {

            if( GC.TryStartNoGCRegion( 15728640 ) ) {
                for( int i = 0; i < 30; i++ ) {
                    var n = create();
                    Thread.Sleep( 100 );
                    GC.KeepAlive( n );

                    Console.WriteLine( $"GC:{GC.CollectionCount( 0 ) } {GC.GetTotalMemory( false ):#,##byte} " );
                }

                if( GCSettings.LatencyMode == GCLatencyMode.NoGCRegion )
                    GC.EndNoGCRegion();
            }
            Console.ReadLine();

        }

        public static WeakReference<byte[]> create() {
            var weak = new WeakReference<byte[]>( new byte[1048576] );
            return weak;
        }

    }

}
実行結果
GC:1 1,217,676byte
GC:1 2,274,476byte
GC:1 3,323,084byte
GC:1 4,371,692byte
GC:1 5,420,300byte
GC:1 6,468,908byte
GC:1 7,517,516byte
GC:1 8,566,124byte
GC:1 9,614,732byte
GC:1 10,663,340byte
GC:1 11,711,948byte
GC:1 12,760,556byte
GC:1 13,809,164byte
GC:1 14,857,772byte
GC:1 15,906,380byte
GC:1 16,954,988byte
GC:2 1,145,160byte
GC:2 2,193,768byte
GC:2 3,242,376byte
GC:3 1,145,088byte
GC:3 2,193,696byte

GC.TryStartNoGCRegionで指定した15MBに達するまでGCが行われていないことが確認できます。
しかし、15MBを超えた時点で自動的にGCが発生するようになっていることも確認できます。

注意事項

GC.TryStartNoGCRegionを呼び出してから、新しく割り当てられたメモリが指定サイズに達するまでGCを抑制するものです。
既に割り当てられていたメモリサイズは、関係ありません。

GC.TryStartNoGCRegionを多重に呼び出すことは、できません。
GC.TryStartNoGCRegionに成功すると、GCSettings.LatencyModeの値がGCLatencyMode.NoGCRegionに変更されます。
逆に言えば、GCSettings.LatencyMode == GCLatencyMode.NoGCRegionであれば、GC抑制中だと判断できます。

GC.EndNoGCRegion()は、GCSettings.LatencyModeがGCLatencyMode.NoGCRegionである場合にのみ実行可能です。
GC.EndNoGCRegion()を呼び出さなくても自動的に解除されることかあります。

  • GC.TryStartNoGCRegionで指定したサイズに達した
  • GC.Collectなどが呼び出され、手動によるGCが発生した
  • GC.GetTotalMemory( true ) が実行された

このような場合、その時点でGCの抑制状態が解除されることになります。