2012-07-13 3 views
7

여기에 따라 시작 시간이 달라진다는 것을 알았습니다. 초기화 코드를 배치했습니다. 나는 이것이 정말로 이상하다고 생각 했으므로 나는 작은 기준을 적어 내 의혹을 확인했다. main 메소드가 호출되기 전에 실행되는 코드는 정상보다 느린 것 같습니다.정적 생성자의 코드가 느리게 실행 됨

Benchmark();이 일반 코드 경로 전후에 호출되는지에 따라 다른 속도로 실행되는 이유는 무엇입니까?

// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

반복의 수를 두배로 :

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

결과는 static Program program 속성에 대한 정적 생성자와 생성자 모두 그 Benchmark() 실행 거의 2 배 느리게 보여 여기

벤치 마크 코드입니다 벤치 마크 루프는 모든 시간을 두 배로 만들므로 발생하는 성능 패널티가 일정하지는 않지만 한 가지 요인이 있음을 나타냅니다.

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

왜 그렇습니까? 초기화가 속한 곳에서 완료되면 초기화가 빠르게 수행되는 것이 합리적입니다. 테스트는 .NET 4, 릴리스 모드, 최적화에서 수행되었습니다.

+2

정확히 무엇이 문제입니까? – jcolebrand

+0

설정을 컴파일 하시겠습니까? 프레임 워크 버전? – user7116

+0

내 편집 내용이 마음에 들지 않는지 확인하십시오 (이유는 모르겠 음). NET 4/릴리스와 비슷한 결과를 얻었습니다. –

답변

3

이것은 매우 흥미로운 문제입니다. 나는 당신의 프로그램의 변종을 실험하는데 약간의 시간을 보냈다. 여기에 몇 가지 관찰은 다음과 같습니다 다른 클래스로 벤치 마크() 정적 메소드를 이동하는 경우

  1. 는, 정적 생성자의 성능 저하가 사라집니다.

  2. Benchmark() 메서드를 인스턴스 메서드로 만들면 성능 저하가 사라집니다.

  3. 빠른 케이스 (1, 2)와 느린 케이스 (3, 4)를 프로파일 링 할 때 느린 케이스는 CLR 도우미 메서드에서 특히 JIT_GetSharedNonGCStaticBase_Helper라는 추가 시간을 보냈습니다.

이 정보를 바탕으로, 나는 무슨 일이 일어나고 있는지 추측 할 수 있습니다. CLR에서는 모든 정적 생성자가 한 번만 실행되도록해야합니다. 복잡성은 정적 생성자가주기를 형성 할 수 있다는 것입니다 (예 : 클래스 A에 B 유형의 정적 필드가 있고 B 클래스에 A 유형의 정적 필드가 포함 된 경우).

정적 생성자 내에서 실행될 때 JIT 컴파일러 삽입은 순환 클래스 종속성으로 인해 잠재적 인 무한 사이클을 방지하기 위해 일부 정적 메서드 호출을 검사합니다. 정적 생성자 외부에서 정적 메서드가 호출되면 CLR은 메서드를 다시 컴파일하여 검사를 제거합니다.

이것은 매우 가깝습니다.

+0

문제, 특히 프로파일 링을 찾아 주셔서 감사합니다. 그래도 당신의 분석에 대해서는 잘 모르겠습니다. 그것은 내 테스트에서 일정한 오버 헤드를 암시합니다. 나는 성능상의 불이익이 요인에 의한 것임을 보여주기 위해 나의 질문을 업데이트했다. – Zong

+0

각 IsPrime 호출은 검사로 둘러싸여 야하기 때문에 각 IsPrime() 호출 **에 대한 추가 오버 헤드 **가 있습니다. 예를 들어 IsPrime을 더 비싸게 만들면 (예 : IsPrime (1000000000 + 2 * i)를 호출) 네 가지 경우의 차이가 사라집니다. –

+0

즉, 반복 횟수를 두 배로 늘리면 IsPrime 호출 수가 두 배로 늘어나며 결과적으로 수행 된 검사 수가 두 배가됩니다. –

3

매우 잘 설명 된 사실입니다.

정적 생성자가 느립니다. .NET 런타임은이를 최적화 할만큼 똑똑하지 않습니다.

은 참조 : 그들은 런타임 클래스의 구성원에 액세스하기 전에 값이 정확히 설정되어 있는지 보장 할 것을 요구 때문에 Performance penalty of static constructors

명시 적으로 정적 생성자가 비싸다. 정확한 비용은 이지만 시나리오에 따라 다르지만 경우에 따라 상당히 두드러 질 수 있습니다.

+0

링크 된 기사가 적용되지 않는다고 생각합니다. 이 기사에서는 정적 생성자가있는 유형이 beforeFieldInit로 표시되어 해당 유형으로 작업하면 추가 런타임 비용이 발생한다고 설명합니다. 그러나 OP는 정적 생성자와없는 생성자 두 가지 유형을 비교하지 않습니다. 전체 벤치 마크에는 단 하나의 유형 만 있으며이 유형에는 정적 생성자가 있습니다. 그래서 다른 일이 벌어지고 있습니다. –

+0

네 말이 맞아. 내 벤치 마크는 실제로 기사의 두 경우의 실적이 다소 차이가 있음을 나타냅니다. – Zong

관련 문제