C # /. NET 프로그램 최적화를위한 팁 [닫힌]


78

요즘 최적화는 잃어버린 예술처럼 보입니다. 모든 프로그래머가 코드에서 최대한의 효율성을 뽑아 낸 적이 없었습니까? 눈 속에서 5 마일을 걷는 동안 자주 그렇게합니까?

잃어버린 예술을 되찾기 위해 C # /. NET 코드를 최적화하기 위해 간단하거나 복잡한 변경 사항에 대해 알고있는 몇 가지 팁은 무엇입니까? 수행하려는 작업에 따라 매우 광범위한 것이기 때문에 팁에 컨텍스트를 제공하는 데 도움이됩니다. 예를 들면 :

  • 많은 문자열을 함께 연결할 때 StringBuilder대신 사용하십시오. 이에 대한 경고는 하단의 링크를 참조하십시오.
  • string.Compare다음과 같이하는 대신 두 문자열을 비교하는 데 사용 합니다.string1.ToLower() == string2.ToLower()

지금까지의 일반적인 합의는 측정이 중요합니다. 이런 종류의 요점을 놓치게됩니다. 측정은 무엇이 잘못되었는지 또는 병목 현상이 발생한 경우 어떻게해야하는지 알려주지 않습니다. 한 번 문자열 연결 병목 현상에 부딪 쳤는데 어떻게해야할지 몰랐기 때문에이 팁이 유용합니다.

이 글을 게시하기위한 나의 요점은 일반적인 병목 현상에 대한 장소를 확보하고 문제가 발생하기 전에 피할 수있는 방법입니다. 누구나 맹목적으로 따라야하는 플러그 앤 플레이 코드에 관한 것이 아니라, 적어도 어느 정도는 성능을 고려해야하며,주의해야 할 몇 가지 일반적인 함정이 있다는 것을 이해하는 것이 더 중요합니다.

팁이 왜 유용하고 어디에 적용되어야하는지 아는 것도 유용 할 수 있습니다. 을 위해 StringBuilder팁 나는 오래전에했던 도움을 발견 존 소총의 사이트에 여기를 .


10
최적화와 가독성 사이의 경계를 걷는 것도 중요합니다.
Ryan Elkins

3
"소수의 문자열"; 수는 문제가되지 않습니다 - 그들이 하나의 복합 연결 문, 또는 여러 제표에 있는지 여부입니다.
Marc Gravell

2
StringBuilder는 종종 + 연산자보다 느립니다. C # 컴파일러는 반복되는 +를 String.Concat의 적절한 오버로드로 자동 변환합니다.
Richard Berg

2
CLR이 IL을 최적화하는 동안 CLR과 싸우는 데 어려움을 겪어야하며 컴파일 타임에 줄다리기처럼하려고했습니다. 옛날에는 기계에 대한 지침을 최적화했고 기계는 멍청하게 실행했습니다.
John K

답변:


106

요즘 최적화는 잃어버린 예술처럼 보입니다.

예를 들어 현미경의 제조가 예술로 실행되는 날이 하루에 한 번있었습니다. 광학 원리는 제대로 이해되지 않았습니다. 부품의 표준화가 없었습니다. 튜브와 기어, 렌즈는 고도로 숙련 된 작업자가 수작업으로 만들어야했습니다.

오늘날 현미경은 공학 분야로 생산됩니다. 물리학의 기본 원리는 매우 잘 이해되고 있으며, 기성 부품이 널리 사용 가능하며, 현미경 제작 엔지니어는 기기가 수행하도록 설계된 작업에 최적으로 최적화하는 방법에 대해 정보에 입각 한 선택을 할 수 있습니다.

성능 분석이 "잃어버린 예술"이라는 것은 아주 좋은 것입니다. 그 예술은 예술 로서 실행 되었습니다 . 최적화는 그것이 무엇인지에 대해 접근해야합니다 : 견고한 엔지니어링 원칙을주의 깊게 적용하여 해결할 수 있는 엔지니어링 문제 .

나는 사람들이 vbscript / jscript / 활성 서버 페이지 / VB / C # 코드를 최적화하는 데 사용할 수있는 "팁과 트릭"목록에 대해 수년에 걸쳐 수십 번 요청 받았습니다. 나는 항상 이것을 거부합니다. "팁과 트릭"을 강조하는 것은 성능에 접근하는 잘못된 방법입니다. 그런 식으로 이해하기 어렵고, 추론하기 어렵고, 유지하기 어려운 코드로 이어지며, 일반적으로 해당하는 간단한 코드보다 눈에 띄게 빠르지 않습니다.

성능에 접근하는 올바른 방법은 다른 문제와 마찬가지로 엔지니어링 문제로 접근하는 것입니다.

  • 의미 있고 측정 가능하며 고객 중심의 목표를 설정하십시오.
  • 현실적이지만 통제되고 반복 가능한 조건에서 이러한 목표에 대한 성능을 테스트하는 테스트 스위트를 빌드하십시오.
  • 이러한 제품군에서 목표를 달성하지 못하는 것으로 나타나면 프로파일 러와 같은 도구를 사용하여 이유를 파악하십시오.
  • 프로파일 러가 가장 성능이 떨어지는 하위 시스템으로 식별하는 것을 최적화하십시오. 각각의 성능 영향을 명확하게 이해할 수 있도록 모든 변경 사항에 대해 계속 프로파일 링하십시오.
  • (1) 목표를 달성하고 소프트웨어를 배송하거나, (2) 목표를 달성 할 수있는 수준으로 수정하거나, (3) 목표를 달성 할 수 없어 프로젝트가 취소 될 때까지 반복합니다.

이는 기능 추가와 같은 다른 엔지니어링 문제를 해결하는 것과 동일합니다. 기능에 대한 고객 중심 목표 설정, 견고한 구현에 대한 진행 상황 추적, 신중한 디버깅 분석을 통해 발견 된 문제 해결, 다음까지 반복 배송 또는 실패. 성능은 기능입니다.

복잡한 현대 시스템에 대한 성능 분석에는 사소하거나 비현실적인 상황에 좁게 적용 할 수있는 트릭으로 가득 찬 가방이 아닌 견고한 엔지니어링 원칙에 대한 규율과 초점이 ​​필요합니다. 팁과 트릭을 적용하여 실제 성능 문제를 한 번도 해결 한 적이 없습니다.


1
비슷한 스크 리드를 쓰려고했지만 당신이 더 낫습니다. 브라보.
Richard Berg

7
리소스를 덜 사용하면서 동일한 작업을 수행하는 더 좋은 방법이 알려진 경우가 있습니다. 나는 당신이 어떤 목표를 달성하고 괜찮은 것처럼 보이는 한 당신이 원하는 것을 프로그래밍하는 것이 완벽하게 괜찮다고 생각하지 않습니다. 또는 프로그래밍 하고 프로파일 러 실행 한 다음 돌아가서 문제 영역을 변경 하는 것이 가장 좋습니다 . 특정 코드를 시작하기 전에 최적화하는 데 필요한 사항에 대해 잘 알고있는 사람은 어떤 문제가 있습니까?
Bob

5
@Bob : 자원 사용에 대해 현명하게 행동하는 것은 잘못된 것이 아닙니다. 일이 잘못되는 곳은 사람들이 (1) 차이를 만들지 않는 마이크로 최적화에 많은 시간 (= 돈)을 보내고, (2) 잘못된 프로그램을 작성하고 , (3) 명확하지 않은 프로그램을 작성하는 경우입니다. 최적화해야 할 것은 우선 정확성입니다. 둘째, 좋은 코딩 스타일입니다. 셋째, 성능입니다. 코드가 정확하고 우아하면 성능을 높이는 것이 훨씬 쉬워집니다.
Eric Lippert

3
괜찮습니다.하지만 여러분은 정확성을 우선으로 코딩해서는 안된다는 것을 알 수있을 것입니다. 두 번째 스타일로 코딩해서는 안됩니다. 그러나 때때로 (또는 요즘에는 많은 경우) 프로그래머가 성능이나 최적화를 전혀 고려하지 않는 것도 사실입니다. 1과 2만으로도 3의 총 무관심을 보충 할 수 있습니까? 나는 그것이 최적화에 어떤 점을 지불하고 무엇이 필요한지에 대해 한 두 가지를 배울 나쁜 생각이 볼 수 없습니다

7
@Bob : 일부 프로그래머는 성능에 관심이 없다는 데 동의합니다. 그러나 나는 당신의 요점을 따르지 않습니다. 팁과 트릭 목록이 갑자기 성능에 관심이있는 사람들로 바뀌지는 않을 것입니다. 현재 관심없는 사람들을 관심있는 사람들로 만들 있다는 주장을 위해 팁과 요령 목록은 좋은 성과를 얻는 데 도움이되지 않습니다. 하루 종일 코드 본문에 팁과 요령을 적용 할 수 있으며 목표에 반하는 진전이 있는지 알 수 없습니다. 목표를 가지고 진행 상황을 측정해야합니다.
Eric Lippert

45

좋은 프로파일 러를 얻으십시오.

좋은 프로파일 러없이 C # (실제로 모든 코드)을 최적화하려고 시도하지 마십시오. 실제로 샘플링 및 추적 프로파일 러를 모두 보유하면 크게 도움이됩니다.

좋은 프로파일 러가 없으면 잘못된 최적화를 만들 수 있으며 가장 중요한 것은 처음에 성능 문제가 아닌 루틴을 최적화하는 것입니다.

프로파일 링의 처음 세 단계는 항상 1) 측정, 2) 측정, 3) 측정 ...입니다.


1
내가하지 말 것 측정 , 캡처 . stackoverflow.com/questions/406760/…
Mike Dunlavey

23
당신은 잊어 버렸습니다4) measure
Nifle

1
@Nifle : 코끼리를 사냥하는 경우 측정해야합니까?
Mike Dunlavey

1
@RobbieDee : Conrad Albrecht의 답변을 참조하십시오 .
Mike Dunlavey

1
죄송합니다 @MikeDunlavey, 난 그냥 당신과 함께 약간의 재미를 가지고 있었다,하지만 덕분에 ... :-)
로비 디

21

최적화 지침 :

  1. 필요한 경우가 아니면하지 마십시오
  2. 개발자 대신 새로운 하드웨어를 문제에 던지는 것이 더 저렴하다면하지 마십시오.
  3. 생산과 동등한 환경에서 변화를 측정 할 수 없다면 그렇게하지 마십시오.
  4. CPU 메모리 프로파일 러 를 사용하는 방법을 모르면하지 마십시오.
  5. 코드를 읽을 수 없거나 유지 관리 할 수 ​​없게 만들 경우에는하지 마십시오.

프로세서가 계속 빨라짐에 따라 대부분의 애플리케이션에서 주요 병목 현상은 CPU가 아니라 대역폭입니다. 즉, 오프 칩 메모리 대역폭, 디스크 대역폭, 네트워크 대역폭입니다.

맨 끝에서 시작하십시오. YSlow를 사용하여 웹 사이트가 최종 사용자에게 느린 이유를 확인한 다음 뒤로 이동하여 데이터베이스 액세스가 너무 넓지 않고 (열) 너무 깊지 않도록 (행) 수정하십시오.

CPU 사용을 최적화하기 위해 무엇이든 할 가치가있는 매우 드문 경우에는 메모리 사용에 부정적인 영향을 미치지 않도록주의하십시오. 개발자가 CPU주기를 절약하기 위해 결과를 캐시하기 위해 메모리를 사용하려는 '최적화'를 보았습니다. 순 효과는 캐시 페이지 및 데이터베이스 결과에 사용 가능한 메모리를 줄이는 것이 었으며 이로 인해 응용 프로그램이 훨씬 느려졌습니다! (측정에 관한 규칙을 참조하십시오.)

또한 최적화되지 않은 '멍청한'알고리즘이 '영리한'최적화 알고리즘을 능가하는 경우도 보았습니다. 얼마나 훌륭한 컴파일러 작성자와 칩 설계자가 '비효율적 인'루핑 코드를 파이프 라이닝을 통해 온칩 메모리에서 완전히 실행할 수있는 매우 효율적인 코드로 바꾸 었는지 과소 평가하지 마십시오. '효율적'이라고 생각했던 언 래핑 된 내부 루프가있는 '영리한'트리 기반 알고리즘은 실행 중에 온칩 메모리에 머 무르지 못했기 때문에 단순히 이길 수 있습니다. (측정에 관한 규칙을 참조하십시오.)


10
마찬가지로 Big-O 분석에 집착하지 마십시오. O (nm) 순진한 문자열 검색 알고리즘은 일반적인 비즈니스 사례에서 패턴을 찾는 검색 문자열을 전처리하는 O (n + m) 알고리즘보다 수천 배 더 빠릅니다. 첫 번째 문자와 일치하는 순진한 문자열 검색은 종종 낙관적 메모리 캐시를 많이 사용하는 최신 프로세서에서 엄청나게 빠른 단일 기계 명령어로 컴파일됩니다.
Eric Lippert

16

ORM으로 작업 할 때 N + 1 Selects를 알고 있어야합니다.

List<Order> _orders = _repository.GetOrders(DateTime.Now);
foreach(var order in _orders)
{
    Print(order.Customer.Name);
}

고객이 열심히로드하지 않으면 데이터베이스로 여러 번 왕복 할 수 있습니다.


14
  • 매직 넘버를 사용하지 말고 열거를 사용하십시오.
  • 값을 하드 코딩하지 마십시오.
  • 형식이 안전하고 권투 및 개봉을 방지하므로 가능한 경우 제네릭을 사용하십시오.
  • 꼭 필요한 곳에 오류 처리기를 사용하십시오.
  • 폐기, 폐기, 폐기하십시오. CLR은 데이터베이스 연결을 닫는 방법을 알지 못하므로 사용 후 연결을 닫고 관리되지 않는 리소스를 폐기하십시오.
  • 상식을 사용하십시오!

15
내가 할 일이 좋다는 데 동의하는만큼, 여기에서 처음 두 가지는 성능에 영향을주지 않습니다. 유지 관리 만 가능합니다.
Reed Copsey

사실이지만 여전히 최적화 된 코드입니다.
SoftwareGeek

4
추가로, 3 번째 (권투)는 진짜 핀치 포인트가 아닙니다. 문제로 과장되어 있습니다. 예외와 마찬가지로 일반적 으로 문제가 되지 않습니다 .
Marc Gravell

1
"하지만 여전히 최적화 된 코드입니다"-이것은 큰 주장입니다. 중요한 문제가 될 것으로 예상되는 유일한 것은 "처분"입니다. 그리고 그것은 성능 저하가 아닌 예외 (핸들 밖 등)로 나타날 가능성이 더 높습니다.
Marc Gravell

실제로 최적화가 목표 인 경우 종료 자 패턴은 상당히 나쁩니다. 종료자가있는 개체는 자동으로 Gen-1 (또는 그 이상)으로 승격됩니다. 또한 해당 Todo 목록에 원격으로 비용이 많이 드는 항목이있는 경우 종료 자 코드를 GC 스레드에서 강제 실행하는 것은 일반적으로 최적이 아닙니다. 요점 : 그것은 원시 속도를위한 것이 아니라 편리함과 정확성을 목표로하는 기능입니다. 세부 정보 : msdn.microsoft.com/en-us/magazine/bb985010.aspx
Richard Berg

9

좋아, 나는 내가 가장 좋아하는 것을 던져야한다. 작업이 사람과의 상호 작용을 위해 충분히 길면 디버거에서 수동 중단을 사용하십시오.

Vs. 프로파일 러는 무슨 일이 일어나고 있는지 실제로 이해하는 데 사용할 수있는 호출 스택과 변수 값을 제공합니다.

이 작업을 10 ~ 20 회 수행하면 어떤 최적화가 실제로 효과를 가져올 수 있는지에 대한 좋은 아이디어를 얻을 수 있습니다.


1
++ 아멘. 프로파일 러가 존재하기 전부터 그렇게 해왔습니다. & 프로그램 DrawMusic이 멋져 보입니다!
Mike Dunlavey

6
이것은 본질적으로 프로파일 러가하는 일입니다. 단, 약 천 가지의 다른 방식 (더 빠르고 더 자주, 더 정확함 등)으로 더 잘 수행한다는 점만 제외하면됩니다. 그들은 또한 콜 스택을 제공합니다. 이것은 가난한 사람 (그리고 새로운 것을 배우는 것을 두려워하는 노인)의 해결책입니다.
BlueRaja-Danny Pflughoeft

@ BlueRaja-DannyPflughoeft : 그들은 당신을 속입니다. 그들은 할 일이별로 없다는 것을 매우 정확하게 말해줍니다. 이 방법과 프로파일 러의 차이점은이 방법을 사용하면 간단한 통계로는 알아낼 수없는 속도 향상을 볼 수 있다는 것입니다. 대신 원시 샘플을 실제로 볼 수 있다면 문제를 일으킬 수있는 정보가 처음 10 개에서 분명 할 때 1000 개의 샘플을 취합니다. 이 게시물 을 보셨을 것 입니다.
Mike Dunlavey 2013

@ BlueRaja-DannyPflughoeft : 결과를보십시오. 프로파일 러를 사용하여 얻은 가장 큰 속도 향상 비율은 무엇입니까?
Mike Dunlavey 2013

2
@ BlueRaja-DannyPflughoeft : 당신은 그렇게하지 않을 것이라고 확신합니다. 그리고 당신이 내 나이가되면 당신은 당신과 같은 사람들을 만날 것입니다. 그러나 그것을 제쳐두 자. 여기에 몇 가지 소스 코드의 어떤 다른 방법을 사용하여, 내가 그것을 어떻게 보지 않고, 크기 3 개 주문하여 속도를 높일 수 있습니다 경우 : 자랑 권리를해야합니다
마이크 Dunlavey

9

방법을 병목 현상으로 식별했지만 어떻게해야할지 모르겠다면 본질적으로 멈춘 것입니다.

그래서 몇 가지를 나열하겠습니다. 이 모든 일들은 실버 총알하지 당신은 아직 프로필을해야합니다 코드를. 저는 여러분 있는 일에 대한 제안을 하고 있으며 때로는 도움을 줄 수 있습니다. 특히 처음 세 개가 중요합니다.

  • 저수준 유형 또는 배열을 사용하여 문제를 해결해보십시오.
  • 문제는 종종 작습니다. 영리하지만 복잡한 알고리즘을 사용한다고해서 항상이기는 ​​것은 아닙니다. 특히 저수준 유형 (배열) 만 사용하는 코드로 덜 스마트 한 알고리즘을 표현할 수있는 경우에는 더욱 그렇습니다. 예를 들어 n <= 100에 대한 InsertionSort 대 MergeSort 또는 n <= 100에 대한 문제의 데이터 흐름 형식을 순진하게 해결하기 위해 비트 벡터를 사용하는 것과 비교하여 Tarjan의 Dominator 찾기 알고리즘을 사용합니다. (물론 100은 당신에게 몇 가지 아이디어를주기위한 것입니다- 프로필 !)
  • 더 큰 문제 인스턴스를 위해 다른 코드를 유지해야하는 경우에도 저수준 유형 (종종 크기가 64 미만인 문제 인스턴스)을 사용하여 해결할 수있는 특수 사례를 작성하는 것이 좋습니다.
  • 위의 두 가지 아이디어에 도움이되는 비트 연산을 배우십시오.
  • BitArray는 Dictionary 또는 List에 비해 친구가 될 수 있습니다. 그러나 구현이 최적이 아니라는 점에 유의하십시오. 더 빠른 버전을 직접 작성할 수 있습니다. 인수가 범위를 벗어 났는지 등을 테스트하는 대신 인덱스가 범위를 벗어날 수 없도록 알고리즘을 구조화 할 수 있지만 표준 BitArray에서 검사를 제거 할 수 없으며 자유롭지 않습니다 .
  • 저수준 유형의 배열만으로 수행 할 수있는 작업의 예로 BitMatrix는 ulong 의 배열 로 구현할 수있는 다소 강력한 구조 이며 ulong을 "front"로 사용하여 탐색 할 수도 있습니다. 일정한 시간의 최하위 비트 (Breadth First Search의 큐와 비교-하지만 분명히 순서는 다르며 순전히 항목을 찾는 순서가 아니라 항목 의 인덱스 에 따라 다릅니다 ).
  • 나눗셈과 모듈로는 오른쪽이 상수가 아니면 정말 느립니다.
  • 부동 소수점 연산은 하지 더 이상 느린 정수 수학에 비해 일반적으로 (하지 "당신이 할 수있는 일", 그러나 "당신이하고 건너 뛸 수 있습니다 뭔가")
  • 분기는 무료아닙니다 . 간단한 산술 (나눗셈 또는 모듈로 제외)을 사용하여 피할 수 있다면 때때로 성능을 얻을 수 있습니다. 분기를 루프 외부로 이동하는 것은 거의 항상 좋은 생각입니다.

저에게 큰 도움이 된 좋은 것들-감사합니다!
Robbie Dee

8

사람들은 실제로 중요한 것에 대해 재미있는 아이디어를 가지고 있습니다. 예를 들면, 대한 스택 오버플로가 질문으로 가득 ++i보다 "확대됨에" i++. 다음은 실제 성능 조정의 예이며 기본적으로 모든 언어에 대해 동일한 절차입니다. 코드가 단순히 "빠르기 때문에"특정한 방식으로 작성된다면 그것은 추측입니다.

물론 의도적으로 멍청한 코드를 작성하지는 않지만 추측이 효과가 있다면 프로파일 러와 프로파일 링 기술이 필요하지 않을 것입니다.


6

진실은 완벽하게 최적화 된 코드가 없다는 것입니다. 그러나 알려진 CPU 유형 (및 개수), 알려진 플랫폼 (Microsoft? Mono ?), 알려진 프레임 워크 / BCL 버전 의 알려진 시스템 (또는 시스템 집합)에서 코드 의 특정 부분 에 대해 최적화 할 수 있습니다 . 알려진 CLI 버전, 알려진 컴파일러 버전 (버그, 사양 변경, 조정), 알려진 총 및 사용 가능한 메모리 양, 알려진 어셈블리 출처 ( GAC ? 디스크? 원격?), 다른 프로세스의 알려진 백그라운드 시스템 활동 포함.

실제 세계에서는 프로파일 러를 사용하고 중요한 부분을 살펴보십시오. 일반적으로 명백한 것은 I / O와 관련된 모든 것, 스레딩과 관련된 모든 것 (다시 말하지만 이는 버전간에 크게 변경됨), 루프 및 조회와 관련된 모든 것입니다.하지만 "분명히 나쁜"코드가 실제로 문제가되지 않는 것에 놀랄 수도 있습니다. "분명히 좋은"코드가 큰 범인입니다.


5

컴파일러에게 무엇을 하지, 할 어떻게 그것을 할 수 있습니다. 예를 들어, foreach (var item in list)보다 더 나은 for (int i = 0; i < list.Count; i++)m = list.Max(i => i.value);더 나은보다 list.Sort(i => i.value); m = list[list.Count - 1];.

시스템에 원하는 작업을 알려 주면 최상의 방법을 알아낼 수 있습니다. LINQ는 결과가 필요할 때까지 계산되지 않기 때문에 좋습니다. 첫 번째 결과 만 사용한다면 나머지는 계산할 필요가 없습니다.

궁극적으로 (그리고 이것은 모든 프로그래밍에 적용됨) 루프를 최소화하고 루프에서 수행하는 작업을 최소화합니다. 더 중요한 것은 루프 내부의 루프 수를 최소화하는 것입니다. O (n) 알고리즘과 O (n ^ 2) 알고리즘의 차이점은 무엇입니까? O (n ^ 2) 알고리즘에는 루프 내부에 루프가 있습니다.


ironacly LINQ는 추가 소시지를 추가하므로 소시지가없는 솔루션이 있는지 궁금합니다.
user3800527

2

나는 정말로 내 코드를 최적화하려고 시도하지는 않지만 때때로 나는 내 프로그램을 소스로 되돌리기 위해 리플렉터와 같은 것을 사용하고 사용할 것이다. 반사경이 출력하는 것과 내가 잘못한 것을 비교하는 것은 흥미 롭습니다. 때때로 나는 더 복잡한 형태로 한 일이 단순화 된 것을 발견합니다. 최적화되지는 않지만 문제에 대한 더 간단한 해결책을 찾는 데 도움이됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.