[c#] C #에서 작은 코드 샘플을 벤치마킹하면이 구현을 개선 할 수 있습니까?

꽤 자주 그래서 나는 어떤 구현이 가장 빠른지 확인하기 위해 작은 코드 덩어리를 벤치마킹합니다.

벤치마킹 코드가 지팅이나 가비지 수집기를 고려하지 않는다는 의견을 자주 봅니다.

천천히 진화 한 다음과 같은 간단한 벤치마킹 기능이 있습니다.

  static void Profile(string description, int iterations, Action func) {
        // warm up 
        func();
        // clean up
        GC.Collect();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    }

용법:

Profile("a descriptions", how_many_iterations_to_run, () =>
{
   // ... code being profiled
});

이 구현에 결함이 있습니까? 구현 X가 Z 반복을 통한 구현 Y보다 빠르다는 것을 보여주는 것으로 충분합니까? 이것을 개선 할 방법을 생각할 수 있습니까?

편집
시간 기반 접근 방식 (반복과 반대)이 선호된다는 것은 꽤 분명합니다. 시간 확인이 성능에 영향을주지 않는 구현이있는 사람이 있습니까?



답변

수정 된 기능은 다음과 같습니다. 커뮤니티에서 권장하는대로 커뮤니티 위키로 수정하십시오.

static double Profile(string description, int iterations, Action func) {
    //Run at highest priority to minimize fluctuations caused by other processes/threads
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
    Thread.CurrentThread.Priority = ThreadPriority.Highest;

    // warm up 
    func();

    var watch = new Stopwatch();

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
    return watch.Elapsed.TotalMilliseconds;
}

당신이 확인 최적화가 활성화 된 릴리스의 컴파일 및 Visual Studio의 테스트 외부를 실행합니다 . 이 마지막 부분은 JIT가 릴리스 모드에서도 연결된 디버거로 최적화를 표시하기 때문에 중요합니다.


답변

GC.Collect반품 전에 완료가 반드시 완료되는 것은 아닙니다 . 마무리는 대기열에 추가 된 다음 별도의 스레드에서 실행됩니다. 이 스레드는 테스트 중에 여전히 활성화되어 결과에 영향을 미칠 수 있습니다.

테스트를 시작하기 전에 종료가 완료되었는지 확인하려면을 호출 할 수 있습니다. 그러면 GC.WaitForPendingFinalizers종료 대기열이 지워질 때까지 차단됩니다.

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();


답변

방정식에서 GC 상호 작용을 제거 하려면 GC.Collect 호출 전이 아닌 ‘준비’호출을 실행하는 것이 좋습니다. 이렇게하면 .NET에 함수의 작업 세트를 위해 OS에서 할당 된 충분한 메모리가 이미 있다는 것을 알 수 있습니다.

각 반복에 대해 인라인되지 않은 메서드 호출을 수행하고 있으므로 테스트중인 항목을 빈 본문과 비교해야합니다. 또한 메서드 호출보다 몇 배 더 긴 시간 만 안정적으로 측정 할 수 있다는 사실을 인정해야합니다.

또한 프로파일 링하는 항목의 종류에 따라 특정 반복 횟수가 아닌 일정 시간 동안 타이밍 기반 실행을 수행 할 수 있습니다. 최상의 구현을 위해서는 매우 짧은 실행을, 최악의 경우에는 매우 긴 실행을해야합니다.


답변

나는 대리인을 전혀 전달하지 않을 것입니다.

  1. 위임 호출은 ~ 가상 메서드 호출입니다. 저렴하지 않음 : .NET에서 가장 작은 메모리 할당량의 약 25 %. 자세한 내용은 이 링크를 참조하십시오 .
  2. 익명의 대리인은 클로저 사용으로 이어질 수 있지만 눈치 채지 못할 것입니다. 다시 말하지만, 클로저 필드에 액세스하는 것은 예를 들어 스택의 변수에 액세스하는 것보다 두드러집니다.

클로저 사용으로 이어지는 예제 코드 :

public void Test()
{
  int someNumber = 1;
  Profiler.Profile("Closure access", 1000000,
    () => someNumber + someNumber);
}

클로저에 대해 잘 모르면 .NET Reflector에서이 방법을 살펴보십시오.


답변

이와 같은 벤치마킹 방법으로 극복하기 가장 어려운 문제는 엣지 케이스와 예상치 못한 상황을 설명하는 것이라고 생각합니다. 예 : “높은 CPU 부하 / 네트워크 사용량 / 디스크 스 래싱 등에서 두 코드 조각이 어떻게 작동합니까?” 특정 알고리즘 이 다른 알고리즘 보다 훨씬 빠르게 작동하는지 확인하기위한 기본 논리 검사에 유용합니다 . 그러나 대부분의 코드 성능을 제대로 테스트하려면 특정 코드의 특정 병목 현상을 측정하는 테스트를 만들어야합니다.

작은 코드 블록을 테스트하는 것은 종종 투자 수익이 거의 없으며 단순한 유지 관리가 가능한 코드 대신 지나치게 복잡한 코드를 사용하도록 장려 할 수 있습니다. 다른 개발자 나 나 자신이 6 개월 후 빠르게 이해할 수있는 명확한 코드를 작성하면 고도로 최적화 된 코드보다 성능상의 이점이 더 많습니다.


답변

func()워밍업을 위해 한 번이 아니라 여러 번 전화를 걸었 습니다.


답변

개선을위한 제안

  1. 실행 환경이 벤치마킹에 적합한 지 감지합니다 (예 : 디버거가 연결되어 있는지 또는 잘못된 측정을 초래할 수있는 jit 최적화가 비활성화되었는지 감지).

  2. 코드의 일부를 독립적으로 측정 (병목 지점이 정확히 어디인지 확인)

  3. 다른 버전 / 구성 요소 / 코드 덩어리 비교 (첫 번째 문장에서 ‘… 어떤 구현이 가장 빠른지 확인하기 위해 작은 코드 덩어리를 벤치마킹’이라고 말합니다.)

# 1에 관하여 :

  • 디버거가 연결되어 있는지 감지하려면 속성을 읽으십시오 System.Diagnostics.Debugger.IsAttached(디버거가 처음에 연결되지 않았지만 얼마 후에 연결되는 경우도 처리해야 함).

  • jit 최적화가 비활성화되어 있는지 감지하려면 DebuggableAttribute.IsJITOptimizerDisabled관련 어셈블리의 속성 을 읽으십시오 .

    private bool IsJitOptimizerDisabled(Assembly assembly)
    {
        return assembly.GetCustomAttributes(typeof (DebuggableAttribute), false)
            .Select(customAttribute => (DebuggableAttribute) customAttribute)
            .Any(attribute => attribute.IsJITOptimizerDisabled);
    }

# 2와 관련하여 :

이것은 여러 가지 방법으로 수행 될 수 있습니다. 한 가지 방법은 여러 델리게이트를 제공 한 다음 해당 델리게이트를 개별적으로 측정하는 것입니다.

# 3에 관하여 :

이것은 또한 여러 가지 방법으로 수행 될 수 있으며 다른 사용 사례는 매우 다른 솔루션을 요구합니다. 벤치 마크를 수동으로 호출하면 콘솔에 쓰는 것이 좋습니다. 그러나 벤치 마크가 빌드 시스템에 의해 자동으로 수행되는 경우 콘솔에 쓰는 것이 좋지 않을 수 있습니다.

이를 수행하는 한 가지 방법은 벤치 마크 결과를 서로 다른 컨텍스트에서 쉽게 사용할 수있는 강력한 형식의 개체로 반환하는 것입니다.


Etimo. 벤치 마크

또 다른 접근 방식은 기존 구성 요소를 사용하여 벤치 마크를 수행하는 것입니다. 실제로 우리 회사에서는 벤치 마크 도구를 공개 도메인에 출시하기로 결정했습니다. 핵심에서 여기에 제시된 다른 답변 중 일부와 마찬가지로 가비지 수집기, 지터, 워밍업 등을 관리합니다. 또한 위에서 제안한 세 가지 기능이 있습니다. Eric Lippert 블로그 에서 논의 된 몇 가지 문제를 관리합니다 .

두 구성 요소를 비교하고 결과를 콘솔에 기록하는 예제 출력입니다. 이 경우 비교되는 두 구성 요소를 ‘KeyedCollection’및 ‘MultiplyIndexedKeyedCollection’이라고합니다.

Etimo.Benchmarks-샘플 콘솔 출력

거기에있다 NuGet 패키지 하는 샘플 NuGet 패키지 와 소스 코드에서 확인할 수 있습니다 GitHub의 . 도있다 블로그 게시물 .

급한 경우 샘플 패키지를 받고 필요에 따라 샘플 델리게이트를 수정하는 것이 좋습니다. 서두르지 않으면 블로그 게시물을 읽고 세부 사항을 이해하는 것이 좋습니다.