. 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.TryStartNoGCRegion ~ GC.EndNoGCRegion 로 둘러싸 본다

속도가 중시되는 경우 GC 발생이 병목이 될 수 가능성이 있다.

 

GC.TryStartNoGCRegion에 메모리 사이즈(바이트 단위)를 지정한다.

이곳에서 지정한 사이즈가 소비될 때까지 GC가 억제되게 된다.

 

예를 들어 GC.TryStartNoGCRegion(15728640)라고 지정하면 15MB 소비할 때까지 GC를 억제하게 된다.

또한 지정 가능한 최대 크기는 실행 환경에 의해서 결정된다.

상세는 Fundamentals of Garbage Collection을 참조.

https://msdn.microsoft.com/en-us/library/ee787088(v=vs.110).aspx

 

일반적인 클라이언트 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.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의 억제 상태가 해제된다.

 

 

MSDN: GC.TryStartNoGCRegion

https://msdn.microsoft.com/en-us/library/system.gc.trystartnogcregion(v=vs.110).aspx

 

동시에 복수의 GC.TryStartNoGCRegion을 호출한 경우 InvalidOperationException이 발생하므로, 멀티 쓰레드에서 GC.TryStartNoGCRegion~GC.EndNoGCRegion을 할 때는 반드시 동기화 해야 한다.

 

출처: http://qiita.com/Temarin_PITA/items/749ad661fd13d7402794

 

저작자 표시
신고
by 흥배 2016.08.01 08:00

성능을 중요시 하는 프로그램을 만들 때(특히 Unity를 사용한 게임) GC를 조심해야 하므로 불필요한 new를 사용하지 않아야 한다.

그러니 일부 눈에 보이는 코드에는 new를 사용하지 않지만 내부에서 사용되는 경우가 있다.

 

1. 박싱화

var x = (object)10;

 

void Test(object o)

{

}

 

Test(10);

 

 

2. 제너릭을 사용할 때 Object를 사용하는 메소드 사용

bool IsSame<T>(T t1, T t2)

{

    return t1.Equals(t2);

}

위의 IsSame은 제너릭 함수라서 박싱화가 일어나지 않을 것이라고 생각하지만 Equals 메소드가 Object를 사용해서 박싱화가 일어난다.

public virtual bool Equals(Object obj);

 

이 문제를 회피하기 위해서는 아래처럼 사용해야 한다.

public interface IEquatable<T>

{

    bool Equals(T other);

}

 

bool IsSame<T>(T t1, T t2) where T : IEquatable<T>

{

    return t1.Equals(t2);

}

 

혹은

bool IsSame<T>(T t1, T t2)

{

    return EqualityComparer<T>.Default.Equals(t1, t2);

}

 

 

3. 람다식의 보이지 않는 new

static int DoubleStatic(int x)

{

    return x * 2;

}

 

int DoubleInstance(int x)

{

    return x * 2;

}

 

void Run()

{

    var two = int.Parse("2");

 

    Enumerable.Range(1, 1).Select(DoubleStatic);           // 1

    Enumerable.Range(1, 2).Select(DoubleInstance);         // 2

    Enumerable.Range(1, 3).Select(x => x * 2);             // 3

    Enumerable.Range(1, 4).Select(x => x * two);           // 4

    Enumerable.Range(1, 5).Select(x => DoubleStatic(x));   // 5

    Enumerable.Range(1, 6).Select(x => DoubleInstance(x)); // 6

}

 

패턴 1, 2는 메소드를 직접 넣은 경우로 델리게이트를 사용하므로 new를 사용한다.

패턴 3은 일반적으로 자주 사용하는 패턴으로 new를 사용하지 않는다.

패턴 4도 자주 사용되는 패턴으로 로컬 변수를 사용해서 클래스가 만들어지므로 new를 사용한다.

패턴 5static 메소드를 사용해서 첫 호출 이외에는 캐시된 것을 사용하므로 별 문제 되지 않는다.

패턴 6은 델리게이트를 사용하므로 new를 사용한다.

 

위의 코드의 안 보이는 코드를 표현하면 아래와 비슷해진다.

static Func<int, int> cacheA;

static Func<int, int> cacheB;

 

internal static int LambdaA(int x)

{

           return x * 2;

}

 

class Closure

{

    internal int two;

 

    internal int LambdaB(int x)

    {

        return x * two;

    }

}

 

internal static int LambdaC(int x)

{

           return DoubleStatic(x);

}

 

internal static int LambdaD(int x)

{

           return DoubleInstance(x);

}

 

void Run()

{

    var two = int.Parse("2");

 

    // 1 - Select(DoubleStatic)

    Enumerable.Range(1, 1).Select(new Func<int, int>(DoubleStatic));

 

    // 2 - Select(DoubleInstance)

    Enumerable.Range(1, 2).Select(new Func<int, int>(DoubleInstance));

 

    // 3 - Select(x => x * 2)

    if(cacheA != null)

    {

        cacheA = new Func<int, int>(LambdaA);

    }

    Enumerable.Range(1, 3).Select(cacheA);

 

    // 4 - Select(x => x * two)

    var closure = new Closure();

    closure.two = two;

    Enumerable.Range(1, 4).Select(new Func<int, int>(closure.LambdaB));

 

    // 5 - Select(x => DoubleStatic(x))

    if(cacheB != null)

    {

        cacheB = new Func<int, int>(LambdaC);

    }

    Enumerable.Range(1, 5).Select(cacheB);

 

    // 6 - Select(x => DoubleInstance(x))

    Enumerable.Range(1, 6).Select(new Func<int, int>(LambdaD));

}

 

 

 

출처: http://neue.cc/2016/01/06_525.html

 

저작자 표시
신고
by 흥배 2016.02.03 07:52

. NET4에서는 워크 스테이션 GC용으로 백그라운드 GC를 가능하게 했다. 그 이후 힙 사이즈의 상한이 몇 기가바이트에서 수십 기가바이트인 컴퓨터를 자주 보게 되었다.

 

NET Framework 팀이 소유한 최적화된 병렬 가베지컬렉터라도 이런 큰 힙은 컬렉션에 몇 초가 걸리고 그 동안 응용 프로그램 스레드를 차단한다. 서버용 백그라운드 GC에서는 서버의 가베지컬렉터에  동시 실행(병렬 실행) 가베컬렉션의 지원을 도입하고 있다. 이에 따라 애플리케이션의 쓰루풋이 높은 상태를 유지하면서 장시간의 블록이 발생하는 가베 컬렉션을 최소한으로 억제할 수 있다.

 

서버 GC를 사용하는 경우 서버의 백그라운드 GC가 자동적으로 시작되도록 이 새 기능을 사용하는데 필요한 것은 아무것도 없다. 클라이언트 GC 및 서버 GC라도 백그라운드 GC의 고도의 특성은 같으며 다음과 같은 특성이 있다.

 

l  완전 GC(세대 2)만을 백그라우드로 할 수 있다.

l  백그라운드 GC는 최적화를 실시하지 않는다.

l  포어 그라운드 GC(세대 0/세대 1 GC)는 백그라운드 GC 안에서 할 수 있으며, 서버 GC는 전용 서버 GC 스레드에서 행해진다.

l  완전한 블로킹 GC도 전용의 서버 GC 스레드에서 행한다.

 

 

출처: http://msdn.microsoft.com/ko-kr/magazine/hh882452(en-us).aspx

저작자 표시
신고
by 흥배 2013.11.21 08:00
| 1 |