예외가 발생하지 않을 때 try / catch 블록으로 인해 성능이 저하됩니까?
Microsoft 직원과의 코드 검토 과정에서 우리는 try{}
블록 내부에서 큰 코드 섹션을 발견했습니다 . 그녀와 IT 담당자는 이것이 코드 성능에 영향을 줄 수 있다고 제안했습니다. 실제로 그들은 대부분의 코드가 try / catch 블록 외부에 있어야하며 중요한 섹션 만 확인해야한다고 제안했습니다. Microsoft 직원은 다가오는 백서에서 잘못된 try / catch 블록을 경고한다고 덧붙였습니다.
주변을 둘러보고 최적화 에 영향을 줄 수는 있지만 변수가 범위간에 공유되는 경우에만 적용되는 것으로 보입니다.
코드의 유지 관리 가능성에 대해 묻거나 올바른 예외를 처리하지 않습니다 (문제의 코드는 리팩토링이 필요합니다). 또한 흐름 제어에 예외를 사용하는 것을 언급하지는 않지만 대부분의 경우 분명히 잘못되었습니다. 이것들은 중요한 문제이지만 (일부는 더 중요합니다) 여기서 초점은 아닙니다.
예외가 발생 하지 않을 때 try / catch 블록은 어떻게 성능에 영향을 줍 니까?
확인해 봐.
static public void Main(string[] args)
{
Stopwatch w = new Stopwatch();
double d = 0;
w.Start();
for (int i = 0; i < 10000000; i++)
{
try
{
d = Math.Sin(1);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
w.Stop();
Console.WriteLine(w.Elapsed);
w.Reset();
w.Start();
for (int i = 0; i < 10000000; i++)
{
d = Math.Sin(1);
}
w.Stop();
Console.WriteLine(w.Elapsed);
}
산출:
00:00:00.4269033 // with try/catch
00:00:00.4260383 // without.
밀리 초 단위 :
449
416
새로운 코드 :
for (int j = 0; j < 10; j++)
{
Stopwatch w = new Stopwatch();
double d = 0;
w.Start();
for (int i = 0; i < 10000000; i++)
{
try
{
d = Math.Sin(d);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
d = Math.Sin(d);
}
}
w.Stop();
Console.Write(" try/catch/finally: ");
Console.WriteLine(w.ElapsedMilliseconds);
w.Reset();
d = 0;
w.Start();
for (int i = 0; i < 10000000; i++)
{
d = Math.Sin(d);
d = Math.Sin(d);
}
w.Stop();
Console.Write("No try/catch/finally: ");
Console.WriteLine(w.ElapsedMilliseconds);
Console.WriteLine();
}
새로운 결과 :
try/catch/finally: 382
No try/catch/finally: 332
try/catch/finally: 375
No try/catch/finally: 332
try/catch/finally: 376
No try/catch/finally: 333
try/catch/finally: 375
No try/catch/finally: 330
try/catch/finally: 373
No try/catch/finally: 329
try/catch/finally: 373
No try/catch/finally: 330
try/catch/finally: 373
No try/catch/finally: 352
try/catch/finally: 374
No try/catch/finally: 331
try/catch/finally: 380
No try/catch/finally: 329
try/catch/finally: 374
No try/catch/finally: 334
시도 / 캐치과 시도 / 캐치하지 않고 모든 통계를 본 후, 호기심보고 저를 강제 뒤의 두 경우에 생성되는 것을 볼 수 있습니다. 코드는 다음과 같습니다.
씨#:
private static void TestWithoutTryCatch(){
Console.WriteLine("SIN(1) = {0} - No Try/Catch", Math.Sin(1));
}
MSIL :
.method private hidebysig static void TestWithoutTryCatch() cil managed
{
// Code size 32 (0x20)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "SIN(1) = {0} - No Try/Catch"
IL_0006: ldc.r8 1.
IL_000f: call float64 [mscorlib]System.Math::Sin(float64)
IL_0014: box [mscorlib]System.Double
IL_0019: call void [mscorlib]System.Console::WriteLine(string,
object)
IL_001e: nop
IL_001f: ret
} // end of method Program::TestWithoutTryCatch
씨#:
private static void TestWithTryCatch(){
try{
Console.WriteLine("SIN(1) = {0}", Math.Sin(1));
}
catch (Exception ex){
Console.WriteLine(ex);
}
}
MSIL :
.method private hidebysig static void TestWithTryCatch() cil managed
{
// Code size 49 (0x31)
.maxstack 2
.locals init ([0] class [mscorlib]System.Exception ex)
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: ldstr "SIN(1) = {0}"
IL_0007: ldc.r8 1.
IL_0010: call float64 [mscorlib]System.Math::Sin(float64)
IL_0015: box [mscorlib]System.Double
IL_001a: call void [mscorlib]System.Console::WriteLine(string,
object)
IL_001f: nop
IL_0020: nop
IL_0021: leave.s IL_002f //JUMP IF NO EXCEPTION
} // end .try
catch [mscorlib]System.Exception
{
IL_0023: stloc.0
IL_0024: nop
IL_0025: ldloc.0
IL_0026: call void [mscorlib]System.Console::WriteLine(object)
IL_002b: nop
IL_002c: nop
IL_002d: leave.s IL_002f
} // end handler
IL_002f: nop
IL_0030: ret
} // end of method Program::TestWithTryCatch
나는 IL의 전문가는 아니지만 로컬 예외 객체가 네 번째 줄에 생성 된 것을 볼 수 있습니다. 그 .locals init ([0] class [mscorlib]System.Exception ex)
후 17 줄까지 try / catch가없는 메소드와 거의 동일 IL_0021: leave.s IL_002f
합니다. 예외가 발생하면 컨트롤이 줄로 건너 IL_0025: ldloc.0
뛰고 그렇지 않으면 레이블로 건너 뛰고 IL_002d: leave.s IL_002f
함수가 반환됩니다.
예외가 발생하지 않으면 예외 객체
만
보유하고 점프 명령 을 보유하기 위해 로컬 변수를 만드는 것이 오버 헤드라고 안전하게 가정 할 수 있습니다
.
아니요. try / finally 블록이 사소한 최적화로 인해 실제로 측정에 영향을 줄 수있는 경우, 우선 .NET을 사용하지 않아야합니다.
Rico Mariani의 성능 팁 : 예외 비용 : 던질 때와 던지지 않을 때
첫 번째 종류의 비용은 코드에서 예외 처리를하는 정적 비용입니다. 관리되는 예외는 실제로 여기에서 비교적 잘 수행되므로 정적 비용이 C ++에서보다 훨씬 저렴할 수 있습니다. 왜 이런거야? 정적 비용은 실제로 두 종류의 장소에서 발생합니다. 첫째, try / finally / catch / throw의 실제 사이트는 해당 구문에 대한 코드가 있습니다. 둘째, 관리되지 않는 코드에는 예외가 발생할 경우 파괴해야하는 모든 개체를 추적하는 것과 관련된 은폐 비용이 있습니다. 상당한 정리 논리가 있어야하며 교묘 한 부분은 스스로 던지거나 잡지 않거나 예외를 지나치게 많이 사용하지 않는 코드조차도 자체 정리 방법을 아는 부담이 여전히 있다는 것입니다.
드미트리 자 슬라브스키 :
Chris Brumme의 메모에 따르면 : 캐치가있을 때 JIT가 일부 최적화를 수행하지 않는다는 사실과 관련된 비용이 있습니다.
이 예에서는 Ben M 과 구조가 다릅니다 . 내부 for
루프 내부의 오버 헤드가 확장 되어 두 경우를 잘 비교하지 못합니다.
다음은 검사 할 전체 코드 (변수 선언 포함)가 Try / Catch 블록 내에있는 경우 비교하기에 더 정확합니다.
for (int j = 0; j < 10; j++)
{
Stopwatch w = new Stopwatch();
w.Start();
try {
double d1 = 0;
for (int i = 0; i < 10000000; i++) {
d1 = Math.Sin(d1);
d1 = Math.Sin(d1);
}
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
}
finally {
//d1 = Math.Sin(d1);
}
w.Stop();
Console.Write(" try/catch/finally: ");
Console.WriteLine(w.ElapsedMilliseconds);
w.Reset();
w.Start();
double d2 = 0;
for (int i = 0; i < 10000000; i++) {
d2 = Math.Sin(d2);
d2 = Math.Sin(d2);
}
w.Stop();
Console.Write("No try/catch/finally: ");
Console.WriteLine(w.ElapsedMilliseconds);
Console.WriteLine();
}
Ben M 의 원래 테스트 코드를 실행했을 때 디버그 구성과 릴리스 구성이 모두 다릅니다.
이 버전에서는 디버그 버전 (실제로는 다른 버전보다)에 차이가 있었지만 릴리스 버전에는 차이가 없었습니다.
결론 :
이 테스트를 통해 Try / Catch 가 성능에 약간의 영향을 미친다고 말할 수 있습니다.
편집 :
루프 값을 10000000에서 1000000000으로 늘리려 고했는데 Release에서 다시 실행하여 릴리스에서 약간의 차이점을 얻었습니다. 결과는 다음과 같습니다.
try/catch/finally: 509
No try/catch/finally: 486
try/catch/finally: 479
No try/catch/finally: 511
try/catch/finally: 475
No try/catch/finally: 477
try/catch/finally: 477
No try/catch/finally: 475
try/catch/finally: 475
No try/catch/finally: 476
try/catch/finally: 477
No try/catch/finally: 474
try/catch/finally: 475
No try/catch/finally: 475
try/catch/finally: 476
No try/catch/finally: 476
try/catch/finally: 475
No try/catch/finally: 476
try/catch/finally: 475
No try/catch/finally: 474
결과가 결과가 아님을 알 수 있습니다. 경우에 따라 Try / Catch를 사용하는 버전이 실제로 더 빠릅니다!
나는 try..catch
단단한 루프에서 실제 영향을 테스트했으며 , 정상적인 상황에서 성능 문제가 되기에는 너무 작습니다.
루프가 거의 작동하지 않으면 (내 테스트에서 x++
), 예외 처리의 영향을 측정 할 수 있습니다. 예외 처리 루프는 실행하는 데 약 10 배가 더 걸렸습니다.
루프가 실제 작업을 수행하는 경우 (내 테스트에서 Int32.Parse 메서드라고 함) 예외 처리에 영향을 거의 미치지 않아 측정 할 수 없습니다. 루프 순서를 바꾸어 훨씬 더 큰 차이를 얻었습니다 ...
캐치 블록은 성능에 미미한 영향을 미치지 만 예외 처리는 상당히 큰 규모 일 수 있습니다. 동료가 혼란 스러웠을 수 있습니다.
try / catch는 성능에 영향을 미칩니다.
그러나 큰 영향은 아닙니다. try / catch의 복잡도는 일반적으로 루프에 배치 된 경우를 제외하고 간단한 할당과 마찬가지로 O (1)입니다. 따라서 현명하게 사용해야합니다.
다음 은 try / catch 성능에 대한 참조입니다 (복잡성을 설명하지는 않지만 내포되어 있음). 더 적은 예외 처리 섹션을 살펴보십시오
이론적으로 try / catch 블록은 실제로 예외가 발생하지 않는 한 코드 동작에 영향을 미치지 않습니다. 그러나 try / catch 블록의 존재가 큰 영향을 미칠 수있는 드문 상황이 있지만 그 효과가 눈에 띄는 드물지만 거의 모호하지 않은 경우도 있습니다. 그 이유는 다음과 같은 주어진 코드 때문입니다.
Action q;
double thing1()
{ double total; for (int i=0; i<1000000; i++) total+=1.0/i; return total;}
double thing2()
{ q=null; return 1.0;}
...
x=thing1(); // statement1
x=thing2(x); // statement2
doSomething(x); // statement3
컴파일러는 statement2가 statement3보다 먼저 실행된다는 사실을 기반으로 statement1을 최적화 할 수 있습니다. 컴파일러가 thing1에 부작용이없고 thing2가 실제로 x를 사용하지 않는다는 것을 인식 할 수 있으면 thing1을 완전히 생략 할 수 있습니다. [이 경우와 같이] thing1이 비싸면, 주요 최적화가 될 수 있지만, thing1이 비싼 경우도 컴파일러가 최적화 할 가능성이 가장 낮습니다. 코드가 변경되었다고 가정하십시오.
x=thing1(); // statement1
try
{ x=thing2(x); } // statement2
catch { q(); }
doSomething(x); // statement3
이제 statement3이 statement2를 실행하지 않고 실행할 수있는 일련의 이벤트가 있습니다. 의 코드에 아무것도 경우에도 thing2
예외를 던질 수 없었다, 다른 스레드가 사용할 수 있다는 가능할 것이다 Interlocked.CompareExchange
통지에 q
지워졌습니다 그것은으로 설정 Thread.ResetAbort
, 다음을 수행 할 Thread.Abort()
문장 2가 그 값을 작성하기 전에 x
. 그런 다음이 catch
실행됩니다 Thread.ResetAbort()
[위임을 통해 q
실행이 statement3을 계속 할 수 있도록]. 이러한 일련의 이벤트는 당연히 불가능할 수 있지만, 그러한 불가능한 이벤트가 발생할 때에도 사양에 따라 작동하는 코드를 생성하려면 컴파일러가 필요합니다.
일반적으로 컴파일러는 복잡한 코드보다 간단한 코드 비트를 생략 할 가능성이 훨씬 높으므로 예외가 발생하지 않는 경우 try / catch가 성능에 큰 영향을 줄 수있는 경우는 거의 없습니다. 그럼에도 불구하고 try / catch 블록이 있으면 try / catch의 경우 최적화를 방해하여 코드 실행 속도가 빨라질 수있는 상황이 있습니다.
예외가 발생하지 않을 때 try / catch 블록의 작동 방식 및 일부 구현의 오버 헤드가 높고 오버 헤드가없는 방법 에 대한 설명은 try / catch 구현에 대한 설명을 참조하십시오 . 특히 Windows 32 비트 구현은 오버 헤드가 높고 64 비트 구현은 그렇지 않다고 생각합니다.
'IT story' 카테고리의 다른 글
VIM 응용 프로그램을 종료하지 않고 파일을 닫습니까? (0) | 2020.04.04 |
---|---|
왜 0 <-0x80000000입니까? (0) | 2020.04.04 |
ASP.NET MVC에서 favicon.ico 제공 (0) | 2020.04.04 |
왜 isset ()과! empty ()를 모두 확인 (0) | 2020.04.04 |
언제 RxJava Observable을 사용해야하고 언제 Android에서 간단한 콜백을 사용해야합니까? (0) | 2020.04.04 |