"좋은"객체 지향 코드를 작성하는 것과 매우 빠른 저 지연 코드를 작성하는 것 사이에 상충 관계가 있다고 생각하십니까? 예를 들어, C ++에서 가상 함수를 피하거나 다형성의 오버 헤드를 피하는 등 불쾌한 것처럼 보이지만 매우 빠른 코드를 다시 작성 하는가?
지연 시간보다 처리량에 조금 더 초점을 맞춘 분야에서 일하지만 성능이 매우 중요하며 "sorta" 라고 말합니다 .
그러나 문제는 너무 많은 사람들이 성능에 대한 개념이 완전히 잘못되었다는 것입니다. 초보자는 종종 모든 것을 잘못 이해하고 있으며 "계산 비용"이라는 전체 개념 모델을 다시 작업해야합니다. 알고리즘 복잡성 만 있으면 얻을 수있는 유일한 것이됩니다. 중간체는 많은 일을 잘못합니다. 전문가들은 문제가 있습니다.
캐시 미스 및 분기 오판과 같은 메트릭을 제공 할 수있는 정확한 도구를 사용하여 측정하면 해당 분야의 모든 수준의 전문 지식을 유지할 수 있습니다.
측정은 또한 최적화하지 않아야 할 사항을 지적합니다 . 전문가들은 종종 측정 된 핫스팟을 최적화하고 느려질 수있는 것에 대한 직감을 기반으로 어둠 속에서 야생 찌르기를 최적화하지 않기 때문에 초보자보다 최적화에 더 적은 시간을 소비 합니다. 코드베이스의 다른 모든 줄에 대해).
성능을위한 설계
이를 제외하고, 인터페이스 디자인 에서처럼 성능을위한 디자인의 핵심은 디자인 부분 에서 나옵니다 . 경험이 부족한 문제 중 하나는 일반화 된 컨텍스트에서 간접 함수 호출 비용과 같은 절대 구현 메트릭이 조기에 전환되는 경향이 있다는 것입니다. 분기 관점이 아니라 관점)은 전체 코드베이스에서이를 피해야하는 이유입니다.
비용은 상대적 입니다. 간접 함수 호출에는 비용이 있지만 모든 비용은 상대적입니다. 수백만 개의 요소를 반복하는 함수를 호출하기 위해 한 번 비용을 지불하는 경우이 비용에 대해 걱정하는 것은 10 억 달러짜리 제품을 구매하기 위해 돈을 낭비하는 데 시간이 걸리는 것입니다. 1 페니가 너무 비쌌습니다.
거친 인터페이스 디자인
성능 의 인터페이스 디자인 측면은 종종 이러한 비용을보다 거친 수준으로 밀어 넣기 위해 종종 추구합니다. 예를 들어, 단일 입자에 대한 런타임 추상화 비용을 지불하는 대신, 그 비용을 입자 시스템 / 이미 터 레벨로 밀어 넣어 효과적으로 입자를 구현 세부 사항 및 / 또는 단순히이 입자 수집의 원시 데이터로 렌더링 할 수 있습니다.
따라서 객체 지향 디자인은 성능 설계 (지연 시간 또는 처리량 등)와 호환되지 않아도되지만 점점 더 세분화 된 객체를 모델링하는 데 중점을 둔 언어로 된 유혹이있을 수 있으며 최신 옵티마이 저는 도움. 소프트웨어의 메모리 액세스 패턴에 대해 효율적인 SoA 표현을 생성하는 방식으로 단일 지점을 나타내는 클래스를 통합하는 등의 작업을 수행 할 수 없습니다. 조잡한 수준으로 모델링 된 인터페이스 디자인을 가진 포인트 모음은 그러한 기회를 제공하며 필요에 따라 점점 더 최적의 솔루션으로 반복 할 수 있습니다. 이러한 설계는 대용량 메모리 용으로 설계되었습니다 *.
* 성능이 중요한 영역에서 오랫동안 작업하면 데이터 유형 및 데이터 구조에 대한 관점이 바뀌고 메모리에 연결되는 방식을 보는 경향이 있으므로 데이터가 아닌 메모리에 중점을 둡니다 . 이진 검색 트리는 더 이상 고정 할당자가 지원하지 않는 한 트리 노드에 대해 불분명하고 캐시에 친숙하지 않은 메모리 청크와 같은 경우 로그 복잡성에만 더 이상 영향을 미치지 않습니다. 뷰는 알고리즘 복잡성을 무시하지 않지만 더 이상 메모리 레이아웃과 독립적으로 보이지 않습니다. 또한 작업 반복이 메모리 액세스 반복에 대한 것으로 간주되기 시작합니다. *
많은 성능이 중요한 디자인은 실제로 사람이 이해하고 사용하기 쉬운 고급 인터페이스 디자인의 개념과 매우 호환 될 수 있습니다. 차이점은 이 맥락에서 "높은 수준" 은 메모리 대량 수집, 잠재적으로 대규모 데이터 수집을 위해 모델링 된 인터페이스 및 상당히 낮은 수준 일 수있는 실제 구현에 관한 것입니다. 시각적 비유는 소리의 속도로 진행하는 동안 정말 편안하고 운전하기 쉽고 다루기 쉽고 매우 안전한 자동차 일 수 있지만 후드를 터뜨리면 내부에 불을 내뿜는 악마가 거의 없습니다.
더 거칠게 디자인하면 코드에서 더 효율적인 잠금 패턴을 제공하고 병렬 처리를 쉽게 이용할 수있는 더 쉬운 방법을 찾는 경향이 있습니다 (멀티 스레딩은 여기서 건너 뛸 수있는 철저한 주제입니다).
메모리 풀
지연 시간이 짧은 프로그래밍의 중요한 측면은 메모리의 할당 및 할당 해제의 일반적인 속도뿐만 아니라 참조의 지역성을 향상시키기 위해 메모리를 매우 명시 적으로 제어하는 것입니다. 커스텀 할당 자 풀링 메모리는 실제로 우리가 설명한 것과 같은 종류의 디자인 사고 방식을 반영합니다. 대량으로 설계되었습니다 . 거친 수준으로 설계되었습니다. 메모리를 큰 블록으로 미리 할당하고 이미 작은 할당 단위로 할당 된 메모리를 풀링합니다.
이 아이디어는 비용이 많이 드는 것들 (예를 들어 범용 할당 자에 대한 메모리 청크 할당)을 더 거칠고 더 거친 수준으로 푸시하는 것과 동일합니다. 메모리 풀은 대량 으로 메모리를 처리하도록 설계되었습니다 .
타입 시스템 분리 메모리
모든 언어에서 세분화 된 객체 지향 디자인의 어려움 중 하나는 종종 많은 사용자 정의 유형 및 데이터 구조를 도입하려고한다는 것입니다. 이러한 유형은 동적으로 할당 된 경우 작은 청크 단위로 할당 할 수 있습니다.
C ++의 일반적인 예는 다형성이 필요한 경우이며, 일반적인 유혹은 범용 메모리 할당 자에 대해 서브 클래스의 각 인스턴스를 할당하는 것입니다.
결과적으로 연속적인 메모리 레이아웃을 약간의 비트 비트와 어드레싱 범위에 흩어져있는 조각으로 분리하여 더 많은 페이지 오류와 캐시 누락을 초래합니다.
지연 시간이 가장 짧고 끊김없는 결정적인 응답이 필요한 필드는 핫스팟이 항상 단일 병목 현상으로 끝나지 않는 곳일 수 있습니다. 작은 비 효율성이 실제로 실제로 "누적"일 수 있습니다 (많은 사람들이 상상하는 것) 프로파일 러에서 잘못 확인하여 검사를 유지하지만 대기 시간 기반 필드에서는 실제로 작은 비효율이 누적되는 드문 경우가 있습니다. 그리고 그러한 축적의 가장 일반적인 이유는 다음과 같습니다. 처음에 작은 덩어리의 메모리를 과도하게 할당합니다.
Java와 같은 언어에서는 가능하지 않은 배열 int
(그러나 여전히 큰 고수준 인터페이스 뒤에 있음 )과 같은 병목 현상이 많은 영역 (긴밀한 루프로 처리됨)에 대해 더 많은 일반 오래된 데이터 유형의 배열을 사용하는 것이 도움이 될 수 있습니다 . , ArrayList
사용자 정의의 Integer
개체. 이것은 일반적으로 후자의 메모리 분리를 피합니다. C ++에서는 메모리 정의 패턴이 효율적인 경우 사용자 정의 유형을 연속적으로 거기에서 일반 컨테이너의 컨텍스트로 할당 할 수 있으므로 구조를 크게 저하시키지 않아도됩니다.
메모리를 다시 융합
여기서 해결책은 동종 데이터 유형에 대한, 그리고 심지어 동종 데이터 유형에 대한 사용자 지정 할당기에 도달하는 것입니다. 작은 데이터 유형과 데이터 구조가 메모리의 비트와 바이트로 평탄화 될 때, 동종의 특성을 갖습니다 (다양한 정렬 요구 사항이 있음에도 불구하고). 메모리 중심 사고 방식에서 그것들을 보지 않으면, 프로그래밍 언어의 타입 시스템은 잠재적으로 인접한 메모리 영역을 작은 작은 산란 덩어리로 분리 / 분리하기 위해 "원한다".
스택은이 메모리 중심 초점을 사용하여이를 피하고 사용자 정의 유형 인스턴스의 가능한 혼합 조합을 잠재적으로 저장할 수 있습니다. 스택을 최대한 활용하는 것이 가능할 때 최상의 아이디어는 거의 항상 캐시 라인에 있기 때문에 LIFO 패턴없이 이러한 특성 중 일부를 모방하는 메모리 할당자를 설계 할 수 있습니다. 더 복잡한 메모리 할당 및 할당 해제 패턴을위한 청크
최신 하드웨어는 인접한 메모리 블록을 처리 할 때 최고점에 있도록 설계되었습니다 (예 : 동일한 캐시 라인, 같은 페이지에 반복적으로 액세스). 키워드는 연속성이 있습니다. 관심있는 주변 데이터가있는 경우에만 유용합니다. 따라서 성능의 많은 핵심 (아직 어려움)은 분리 된 메모리 덩어리를 다시 제거하기 전에 전체 (모든 주변 데이터가 관련됨)로 액세스되는 연속 블록으로 다시 통합하는 것입니다. 프로그래밍 언어에서 특히 사용자 정의 유형의 풍부한 유형 시스템이 여기에서 가장 큰 장애물이 될 수 있지만 적절한 경우 사용자 지정 할당 기 및 / 또는 부피가 큰 디자인을 통해 항상 문제를 해결하고 해결할 수 있습니다.
추한
"추악한"은 말하기 어렵다. 그것은 주관적인 지표이며, 성능이 매우 중요한 분야에서 일하는 사람은 "아름다움"에 대한 아이디어를 훨씬 더 데이터 지향적이고 대량으로 처리하는 인터페이스에 중점을 둔 아이디어로 변경하기 시작할 것입니다.
위험한
"위험"이 더 쉬울 수 있습니다. 일반적으로 성능은 하위 수준 코드에 도달하려는 경향이 있습니다. 예를 들어, 메모리 할당자를 구현하는 것은 데이터 유형 아래에 도달하지 않고 위험한 수준의 원시 비트 및 바이트에서 작업하지 않으면 불가능합니다. 결과적으로 이러한 성능이 중요한 하위 시스템에서 신중한 테스트 절차에 중점을 두어 최적화 수준을 적용하여 테스트의 철저 성을 확장 할 수 있습니다.
아름다움
그러나이 모든 것은 구현 세부 사항 수준에 있습니다. 베테랑 대규모의 성능 중심적인 사고 방식에서 "아름다움"은 구현 세부 사항이 아닌 인터페이스 디자인으로 이동하는 경향이 있습니다. 인터페이스 설계 변경시 발생할 수있는 결합 및 계단식 손상으로 인해 구현보다 "아름답고"사용 가능하며 안전하며 효율적인 인터페이스를 찾는 것이 기하 급수적으로 높은 우선 순위가됩니다. 구현은 언제든지 교체 할 수 있습니다. 우리는 일반적으로 필요에 따라 측정에서 지적한대로 성능을 반복합니다. 인터페이스 디자인의 핵심은 전체 시스템을 손상시키지 않고 이러한 반복을위한 공간을 확보 할 수있을 정도로 거칠게 모델링하는 것입니다.
실제로, 성능이 중요한 개발에 대한 베테랑의 초점은 종종 안전, 테스트, 유지 관리 성, SE의 제자에 주로 초점을 두는 경향이 있습니다. 중요한 서브 시스템 (입자 시스템, 이미지 처리 알고리즘, 비디오 처리, 오디오 피드백, 레이 트레이서, 메시 엔진 등)은 유지 관리의 악몽에 빠지지 않도록 소프트웨어 엔지니어링에 세심한주의를 기울여야합니다. 우연히도 가장 놀랍도록 효율적인 제품은 버그 수가 가장 적을 수 있습니다.
TL; DR
어쨌든, 그것은 진정으로 성능에 중요한 분야의 우선 순위, 대기 시간을 줄이고 작은 비 효율성을 축적시킬 수있는 것, 실제로 "아름다움"을 구성하는 것 (가장 생산적으로 보는 것)에 이르기까지 주제에 대한 나의 취재입니다.