[...] (마이크로 초 환경에서 부여됨) [...]
우리가 수백만에서 수십억 개의 것들을 반복한다면 마이크로 초가 더해진다. C ++의 개인 vtune / 마이크로 최적화 세션 (알고리즘 개선 사항 없음) :
T-Rex (12.3 million facets):
Initial Time: 32.2372797 seconds
Multithreading: 7.4896073 seconds
4.9201039 seconds
4.6946372 seconds
3.261677 seconds
2.6988536 seconds
SIMD: 1.7831 seconds
4-valence patch optimization: 1.25007 seconds
0.978046 seconds
0.970057 seconds
0.911041 seconds
"멀티 스레딩", "SIMD"(컴파일러를 이길 수 있도록 손으로 쓴 것) 및 4 가의 패치 최적화 이외의 모든 것은 마이크로 레벨 메모리 최적화였습니다. 또한 32 초의 초기 시간부터 시작하는 원래 코드는 이미 상당히 최적화되어 있습니다 (이론적으로 최적의 알고리즘 복잡성). 이것은 최근 세션입니다. 이 최근 세션 이전의 원래 버전은 처리하는 데 5 분이 걸렸습니다.
메모리 효율성을 최적화하면 단일 스레드 컨텍스트 및 여러 스레드 컨텍스트에서 여러 번에서 수십 배까지 도움이 될 수 있습니다.
미세 최적화의 중요성
마이크로 최적화는 시간 낭비라는이 아이디어에 약간 흥분합니다. 나는 이것이 일반적인 조언이지만, 모든 사람들이 측정보다는 허치와 미신에 기초하여 잘못하지는 않는다는 데 동의합니다. 올바르게 수행하면 반드시 미세한 영향을 줄 필요는 없습니다. 인텔 자체의 Embree (레이트 레이싱 커널)를 가져 와서 작성한 스칼라 BVH (지수 적으로 이길 수없는 레이 패킷이 아님) 만 테스트 한 다음 해당 데이터 구조의 성능을 이길 수 있다면 수십 년 동안 코드를 프로파일 링하고 튜닝하는 데 사용 된 베테랑에게도 겸손한 경험. 그리고 그것은 모두 미세 최적화가 적용 되었기 때문입니다. 산업 전문가들이 레이트 레이싱 작업을하는 것을 보았을 때 그들의 솔루션은 초당 1 억 개 이상의 광선을 처리 할 수 있습니다.
알고리즘 중심으로 BVH를 간단하게 구현하고 최적화 컴파일러 (Intel 자체 ICC)에 대해 초당 1 억 개 이상의 1 차 광선 교차점을 얻을 수있는 방법은 없습니다. 간단한 것은 종종 초당 백만 개의 광선을 얻지 못합니다. 초당 수백만 개의 광선을 얻기 위해서는 전문가 수준의 솔루션이 필요합니다. 초당 1 억 개 이상의 광선을 얻으려면 인텔 수준의 미세 최적화가 필요합니다.
알고리즘
몇 분에서 몇 초 또는 몇 분에서 몇 초 정도의 성능이 중요하지 않은 한 마이크로 최적화는 중요하지 않다고 생각합니다. 버블 정렬과 같은 끔찍한 알고리즘을 가져 와서 예를 들어 대량 입력에 사용하고 병합 정렬의 기본 구현과 비교하면 전자는 처리하는 데 몇 개월이 걸릴 수 있으며 후자는 12 분이 걸릴 수 있습니다 이차 대 선형의 복잡성.
몇 달과 몇 분의 차이는 아마도 성능이 중요한 분야에서 일하지 않는 사람들조차도 대부분의 사람들이 결과를 얻기 위해 몇 달을 기다려야하는 경우 실행 시간을 받아 들일 수없는 것으로 간주하게 될 것입니다.
한편, 마이크로 최적화되지 않은 간단한 병합 정렬을 퀵 정렬과 비교하면 (합병 정렬보다 알고리즘 적으로 우수하지 않으며, 참조 지역에 대한 마이크로 레벨 개선 만 제공), 마이크로 최적화 된 퀵 정렬은 다음과 같이 끝날 수 있습니다. 12 분이 아닌 15 초 사용자가 12 분 동안 기다리게하는 것은 완벽하게 받아 들일 수 있습니다 (커피 브레이크 시간).
12 분에서 15 초 사이에 대부분의 사람들에게이 차이는 무시할 수 있다고 생각합니다. 그래서 마이크로 최적화는 종종 몇 분과 몇 달이 아닌 몇 분과 몇 초의 차이와 비슷하기 때문에 종종 쓸모없는 것으로 간주됩니다. 그것이 쓸모없는 것으로 간주되는 다른 이유는 그것이 중요하지 않은 영역에 자주 적용되기 때문입니다 : 루프가없고 중요하지 않은 작은 영역은 의심할만한 1 % 차이를 유발합니다 (소음 일 수도 있음). 그러나 이러한 유형의 시간 차이에 관심이 있고 측정하고 올바르게 기꺼이하는 사람들에게는 적어도 메모리 계층 구조의 기본 개념 (특히 페이지 결함 및 캐시 누락과 관련된 상위 수준)에주의를 기울일 가치가 있다고 생각합니다. .
자바, 마이크로 최적화를위한 충분한 공간 확보
휴, 죄송합니다.
JVM의 "마법"이 프로그래머가 Java에서 마이크로 최적화에 미치는 영향을 방해합니까?
당신이 올바르게하면 사람들이 생각할 수있는만큼 조금은 아닙니다. 예를 들어, 필기 처리 된 SIMD, 멀티 스레딩 및 메모리 최적화 (이미지 처리 알고리즘에 따라 액세스 패턴 및 가능한 표현)를 사용하여 원시 코드에서 이미지 처리를 수행하는 경우 32- 초 동안 초당 수억 개의 픽셀을 쉽게 처리 할 수 있습니다. 비트 RGBA 픽셀 (8 비트 컬러 채널) 및 때로는 초당 수십억 개.
당신이 말하면, Pixel
객체를 만들면 Java의 어느 곳에서나 접근 할 수 없습니다 (이것만으로도 64 비트에서 픽셀 크기가 4 바이트에서 16으로 팽창합니다).
그러나 Pixel
객체 를 피하고 바이트 배열을 사용하고 Image
객체를 모델링하면 훨씬 더 가까워 질 수 있습니다 . 평범한 오래된 데이터 배열을 사용하기 시작하면 Java는 여전히 유능합니다. Java에서 이전에 이런 종류의 것들을 시도해 보았지만 평소보다 4 배 더 큰 사소한 작은 객체를 만들지 않고 (예 : 대신 사용 ) 대량 인터페이스를 모델링하기 시작 하면 상당히 감동 했습니다 . 인터페이스가 아닌 인터페이스. 심지어 객체가 아닌 평범한 오래된 데이터를 반복하는 경우 Java가 C ++ 성능과 경쟁 할 수 있다고 말하고 싶습니다 ( 예 : 의 거대한 배열 ).int
Integer
Image
Pixel
float
Float
메모리 크기보다 훨씬 더 중요한 것은 일련의 배열이 int
연속적인 표현 을 보장한다는 것입니다. Integer
하지 않는 배열 . 연속성 (contiguity)은 여러 요소 (예 : 16 ints
)가 모두 단일 캐시 라인에 적합하고 효율적인 메모리 액세스 패턴으로 제거하기 전에 잠재적으로 함께 액세스 될 수 있기 때문에 참조의 로컬성에 필수적입니다 . 한편 단일 Integer
메모리는 주변 메모리가 관련이없는 메모리 어딘가에 좌초 될 수 있으며, 16 개 정수가 아닌 제거 전에 단일 정수만 사용하기 위해 해당 메모리 영역 만 캐시 라인에로드해야합니다. 우리가 놀랍도록 운이 좋고 주변에 있어도Integers
메모리에서 서로 바로 옆에 있었으므로 Integer
4 배 더 큰 결과로 제거하기 전에 액세스 할 수있는 캐시 라인에 4 개만 넣을 수 있으며 , 이는 가장 좋은 시나리오입니다.
또한 동일한 메모리 아키텍처 / 계층 구조로 통합 된 이후 마이크로 최적화가 많이 이루어졌습니다. 메모리 액세스 패턴은 사용하는 언어에 관계없이 루프 타일링 / 블로킹과 같은 개념이 일반적으로 C 또는 C ++에서 훨씬 더 많이 적용될 수 있지만 Java에도 많은 이점이 있습니다.
나는 최근에 C ++에서 때때로 데이터 멤버의 순서가 최적화를 제공 할 수 있다고 읽었다 ...]
데이터 멤버의 순서는 일반적으로 Java에서 중요하지 않지만 대부분 좋은 것입니다. C 및 C ++에서 ABI 이유로 인해 데이터 멤버의 순서를 유지하는 것이 종종 중요하므로 컴파일러는이를 망칠 필요가 없습니다. 패딩시 메모리 낭비를 피하기 위해 데이터 멤버를 내림차순으로 정렬하는 것과 같은 작업을 수행하는 인간 개발자는 신중해야합니다. Java를 사용하면 JIT가 패딩을 최소화하면서 적절한 정렬을 보장하기 위해 즉시 멤버를 재정렬 할 수 있습니다. 따라서 일반적인 C 및 C ++ 프로그래머가 종종 열악한 메모리를 낭비하는 방식으로 자동화합니다 ( 메모리 낭비뿐만 아니라 AoS 구조 간의 보폭을 불필요하게 늘리고 더 많은 캐시 누락을 유발하여 속도를 낭비하는 경우가 많습니다). 그것' 패딩을 최소화하기 위해 필드를 재정렬하는 매우 로봇적인 일이므로 이상적으로는 인간이 다루지 않습니다. 사람이 최적의 배열을 알도록 요구하는 방식으로 필드 배열이 중요한 유일한 시점은 오브젝트가 64 바이트보다 크고 액세스 패턴 (최적의 패딩이 아님)을 기반으로 필드를 배열하는 경우입니다. 보다 인간적인 노력이 될 수 있습니다 (중요한 경로를 이해해야 함. 일부는 사용자가 소프트웨어로 무엇을할지 몰라도 컴파일러가 예측할 수없는 정보 임).
그렇지 않다면 사람들은 Java에서 사용할 수있는 트릭 (간단한 컴파일러 플래그 외에도)에 대한 예를 제공 할 수 있습니다.
Java와 C ++ 간의 최적화 정신 측면에서 가장 큰 차이점은 C ++을 사용하면 성능이 중요한 시나리오에서 Java보다 객체를 조금 더 사용할 수 있다는 것입니다. 예를 들어 C ++은 오버 헤드가없는 클래스에 정수를 랩핑 할 수 있습니다 (모든 곳에서 벤치 마크 됨). Java는 객체 당 메타 데이터 포인터 스타일 + 정렬 패딩 오버 헤드를 가져야하므로 Boolean
보다 큽니다 boolean
(그러나 반사의 균일 한 이점을 제공 final
하고 모든 단일 UDT에 대해 표시되지 않은 기능을 무시하는 기능 ).
C ++에서는 비균질 필드에서 메모리 레이아웃의 연속성을 제어하는 것이 조금 더 쉽습니다 (예 : 구조체 / 클래스를 통해 부동 소수점과 정수를 하나의 배열로 인터리빙). GC를 통해 객체를 할당 할 때 Java로
...하지만 종종 고성능 솔루션은 종종 어쨌든 솔루션을 분할하고 연속 된 일반 데이터 배열에 SoA 액세스 패턴을 사용합니다. 따라서 최고의 성능을 필요로하는 영역의 경우 Java와 C ++ 간의 메모리 레이아웃을 최적화하는 전략은 종종 동일하며, 자주 사용하는 컬렉션 스타일 인터페이스를 선호하는 작은 객체 지향 인터페이스를 철거해야합니다. 콜드 필드 분할, SoA 담당자 등. 동종이 아닌 AoSoA 담당자는 Java에서 불가능한 것처럼 보이지만 (원시 바이트 배열 또는 이와 유사한 것을 사용하지 않는 한) 두 경우 모두 드문 경우입니다.순차적 및 랜덤 액세스 패턴은 핫 필드에 대한 필드 유형의 혼합을 동시에 갖는 동시에 빠를 필요가있다. 필자가이 두 성능 간의 최적화 전략 (일반적인 수준에서)의 차이의 대부분은 최고 성능에 도달하는 경우에 약합니다.
같은 작은 물체와 많이 할 수가없는 - 당신은 단순히 "좋은"성능에 도달하는 경우의 차이는 더 상당히 달라질 Integer
대 int
특히이 제네릭과 상호 작용하는 방식과, 좀 더 피타의 수 있습니다 . 그것은 자바의 중심 최적화 대상으로 단지 구축 한 일반 데이터 구조에 조금 더 어렵다 그것을위한 작품 int
, float
등을하는 것은 그 더 크고 더 비싼 UDT 일을 피할 수 있지만, 종종 가장 성능이 중요한 영역은 자신의 데이터 구조를 손으로 롤링이 필요합니다 동안 어쨌든 매우 구체적인 목적을 위해 조정되었으므로 성능은 좋지만 성능은 향상되지 않는 코드에만 귀찮습니다.
개체 오버 헤드
Java 객체 오버 헤드 (메타 데이터 및 공간적 국소성 손실 및 초기 GC주기 후 일시적인 국소성 손실)는 수백만에 의해 일부 데이터 구조에 저장되어있는 실제 크기 (예 : int
vs. Integer
매우 연속적이고 매우 꽉 찬 루프에서 액세스됩니다. 이 주제에 대해 많은 감도가있는 것처럼 보이므로 이미지와 같은 큰 물체의 객체 오버 헤드에 대해 걱정하고 싶지 않고 실제로는 단일 픽셀과 같은 작은 물체 만 걱정하고 싶지는 않습니다.
누군가이 부분에 대해 의심이 든다면 백만 개의 무작위 ints
대 백만 개의 임의의 합계를 합산하고 Integers
이를 반복적으로 수행하는 Integers
것 ( 초기 GC주기 후 메모리의 재구성)을 벤치 마크하는 것이 좋습니다 .
최고의 트릭 : 공간을 최적화 할 수있는 인터페이스 디자인
따라서 작은 객체 (예 : a Pixel
, 4-vector, 4x4 matrix, a Particle
, 심지어 Account
작은 것만 있는 경우)에 무거운 부하를 처리하는 장소를 다루는 경우 궁극적 인 Java 트릭 필드)는 이러한 조그마한 물건에 객체를 사용하지 않고 평범한 오래된 데이터의 배열 (함께 연결되어 있음)을 사용하는 것입니다. 것처럼 다음 컬렉션 인터페이스가 개체 Image
, ParticleSystem
, Accounts
, 개인 사람은 인덱스로 액세스 할 수있는 등의 행렬이나 벡터의 컬렉션, 예를 들어,이 또한 C와 C의 궁극적 인 디자인 트릭 ++ 중 하나이며, 이후 심지어 기본적인 오브젝트 오버 헤드없이 분리 된 메모리, 단일 입자 수준에서 인터페이스를 모델링하면 가장 효율적인 솔루션을 방지 할 수 있습니다.