2009 년 11 월 20 일 수정 :
관리 코드 성능 향상에 대한 MSDN 기사를 방금 읽고 있었는데이 부분에서이 질문이 떠 올랐습니다.
예외 발생의 성능 비용은 상당합니다. 구조적 예외 처리가 오류 조건을 처리하는 데 권장되는 방법이지만 오류 조건이 발생하는 예외적 인 상황에서만 예외를 사용해야합니다. 일반 제어 흐름에 예외를 사용하지 마십시오.
물론 이것은 .NET만을위한 것이며, 저와 같은 고성능 애플리케이션을 개발하는 사람들을위한 것이기도합니다. 그래서 그것은 분명히 보편적 인 진실이 아닙니다. 그래도 .NET 개발자가 많기 때문에 주목할 가치가 있다고 느꼈습니다.
편집 :
좋아, 우선, 한 가지를 똑바로하자. 나는 성능 문제에 대해 누구와도 싸울 의도가 없다. 일반적으로, 저는 조기 최적화가 죄라고 믿는 사람들의 의견에 동의하는 경향이 있습니다. 그러나 두 가지 사항 만 말씀 드리겠습니다.
포스터는 예외를 아껴서 사용해야한다는 기존의 통념 뒤에 객관적인 근거를 요구하고 있습니다. 우리가 원하는 모든 가독성과 적절한 디자인에 대해 논의 할 수 있습니다. 그러나 이것은 양쪽에서 논쟁 할 준비가 된 사람들과 주관적인 문제입니다. 나는 포스터가 이것을 알고 있다고 생각한다. 사실은 프로그램 흐름을 제어하기 위해 예외를 사용하는 것은 종종 일을하는 비효율적 인 방법이라는 것입니다. 아니요, 항상 그런 것은 아니지만 자주 . 그렇기 때문에 붉은 고기를 먹거나 와인을 아껴 마시는 것이 좋은 조언처럼 예외를 아껴 사용하는 것이 합리적인 조언입니다.
정당한 이유없이 최적화하는 것과 효율적인 코드를 작성하는 것에는 차이가 있습니다. 이에 따른 결과는 최적화되지는 않더라도 강력한 것을 작성하는 것과 비효율적 인 것 사이에 차이가 있다는 것입니다. 때때로 사람들이 예외 처리와 같은 것에 대해 논쟁 할 때 그들은 근본적으로 다른 것에 대해 논의하고 있기 때문에 서로 과거에 대해 이야기하고 있다고 생각합니다.
내 요점을 설명하기 위해 다음 C # 코드 예제를 고려하십시오.
예 1 : 잘못된 사용자 입력 감지
이것은 제가 예외 남용 이라고 부르는 예입니다 .
int value = -1;
string input = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
value = int.Parse(input);
inputChecksOut = true;
} catch (FormatException) {
input = GetInput();
}
}
이 코드는 저에게 말도 안됩니다. 물론 작동합니다 . 아무도 그것에 대해 논쟁하지 않습니다. 그러나 그것은 해야 같은 수 :
int value = -1;
string input = GetInput();
while (!int.TryParse(input, out value)) {
input = GetInput();
}
예 2 : 파일 존재 확인
이 시나리오는 실제로 매우 일반적이라고 생각합니다. 파일 I / O를 다루기 때문에 많은 사람들에게 확실히 훨씬 더 "허용되는" 것처럼 보입니다 .
string text = null;
string path = GetInput();
bool inputChecksOut = false;
while (!inputChecksOut) {
try {
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
inputChecksOut = true;
} catch (FileNotFoundException) {
path = GetInput();
}
}
이건 충분히 합리적 이죠? 파일을 열려고합니다. 만약 거기에 없다면, 우리는 그 예외를 잡아서 다른 파일을 열려고합니다. 그게 무슨 문제입니까?
정말 없어요. 그러나 예외를 발생 시키지 않는 다음 대안을 고려하십시오 .
string text = null;
string path = GetInput();
while (!File.Exists(path)) path = GetInput();
using (FileStream fs = new FileStream(path, FileMode.Open)) {
using (StreamReader sr = new StreamReader(fs)) {
text = sr.ReadToEnd();
}
}
물론,이 두 접근법의 성능이 실제로 동일하다면 이것은 순전히 교리 적 문제 일 것입니다. 자, 한번 살펴 보겠습니다. 첫 번째 코드 예제에서는 10000 개의 임의 문자열 목록을 만들었는데, 그중 어느 것도 적절한 정수를 나타내지 않았고 맨 끝에 유효한 정수 문자열을 추가했습니다. 위의 두 가지 방법을 모두 사용한 결과는 다음과 같습니다.
사용 try
/ catch
블록 : 25.455 초
사용 int.TryParse
: 1.637 밀리 초
두 번째 예제에서는 기본적으로 동일한 작업을 수행했습니다. 10000 개의 임의 문자열 목록을 만들고 유효한 경로가 아닌 마지막에 유효한 경로를 추가했습니다. 결과는 다음과 같습니다.
사용 try
/ catch
블록 : 29.989 초
사용 File.Exists
: 22.820 밀리 초
많은 사람들이 "예, 10,000 개의 예외를 던지고 잡는 것은 매우 비현실적입니다. 이것은 결과를 과장합니다."라고 대답합니다. 물론 그렇습니다. 하나의 예외를 던지고 잘못된 입력을 직접 처리하는 것의 차이는 사용자에게 눈에 띄지 않습니다. 이 두 가지 경우 예외를 사용 하는 것은 읽을 수있는 다른 접근 방식보다 1,000 배에서 10,000 배 이상 느립니다 .
그래서 GetNine()
아래 방법 의 예를 포함 시켰습니다 . 참을 수 없을 정도로 느리 거나 용납 할 수 없을 정도로 느린 것이 아닙니다 . 그것은 것보다 느리게 있다는입니다 해야 할 ... 어떤 좋은 이유에 대해 .
다시 말하지만, 이것들은 단지 두 가지 예일뿐입니다. 중 물론 예외를 사용하여 성능 저하가없는이 심한 (; 결국, 그 구현에 의존 않습니다 파벨의 오른쪽) 때가있을 것입니다. 내가 말하는 것은 : 사실을 직시합시다. 위와 같은 경우 예외를 던지고 잡는 것은 다음과 유사합니다 GetNine()
. 그것은 쉽게 더 잘 할 수있는 일을하는 비효율적 인 방법 일뿐 입니다.
당신은 이것이 모든 사람들이 이유를 모른 채 악 대차에 뛰어 드는 상황 중 하나 인 것처럼 근거를 요구하고 있습니다. 하지만 사실 답은 분명하고 이미 알고 계신 것 같습니다. 예외 처리에는 엄청난 성능이 있습니다.
좋습니다. 특히 비즈니스 시나리오에는 문제가 없지만 상대적으로 말해서 예외를 던지거나 잡으면 많은 경우에 필요한 것보다 더 많은 오버 헤드가 발생합니다. 알고 있습니다. 대부분의 경우 예외를 사용하여 프로그램 흐름을 제어하는 경우 느린 코드를 작성하는 것입니다.
이 코드가 왜 나쁜가요?
private int GetNine() {
for (int i = 0; i < 10; i++) {
if (i == 9) return i;
}
}
이 기능을 프로파일 링하면 일반적인 비즈니스 응용 프로그램에서 상당히 빠르게 수행된다는 것을 알 수있을 것입니다. 그것은 훨씬 더 잘 할 수있는 것을 성취하는 끔찍하게 비효율적 인 방법이라는 사실을 바꾸지 않습니다.
이것이 사람들이 예외 "남용"에 대해 이야기 할 때 의미하는 것입니다.