데이터 지향적 사고 방식
데이터 지향 디자인이 모든 곳에 SoA를 적용한다는 의미는 아닙니다. 이는 단순히 데이터 표현에 중점을 둔 아키텍처, 특히 효율적인 메모리 레이아웃 및 메모리 액세스에 중점을 둔 아키텍처를 설계하는 것을 의미합니다.
적절한 경우 SoA 담당자가 발생할 수 있습니다.
struct BallSoa
{
vector<float> x; // size n
vector<float> y; // size n
vector<float> z; // size n
vector<float> r; // size n
};
... 이것은 구 중심 벡터 성분과 반경을 동시에 처리하지 않고 (4 개의 필드가 동시에 뜨겁지 않은) 수직 루프 논리에 적합하지만 한 번에 하나씩 (반경을 통한 루프, 다른 3 개의 루프) 구 중심의 개별 구성 요소를 통해).
다른 경우, 필드에 자주 액세스하는 경우 (루프 논리가 개별적으로가 아닌 모든 볼 필드를 반복하는 경우) 및 / 또는 볼의 임의 액세스가 필요한 경우 AoS를 사용하는 것이 더 적절할 수 있습니다.
struct BallAoS
{
float x;
float y;
float z;
float r;
};
vector<BallAoS> balls; // size n
... 다른 경우에는 두 이점의 균형을 잡는 하이브리드를 사용하는 것이 적합 할 수 있습니다.
struct BallAoSoA
{
float x[8];
float y[8];
float z[8];
float r[8];
};
vector<BallAoSoA> balls; // size n/8
... 공구를 캐시 라인 / 페이지에 맞추기 위해 반 부동을 사용하여 공의 크기를 절반으로 압축 할 수도 있습니다.
struct BallAoSoA16
{
Float16 x2[16];
Float16 y2[16];
Float16 z2[16];
Float16 r2[16];
};
vector<BallAoSoA16> balls; // size n/16
... 아마도 반경조차도 구 중심만큼 자주 접근하지는 않을 것입니다 (아마도 코드베이스는 종종 점처럼 점으로 취급하고 구처럼 거의 다루지 않습니다). 이 경우 핫 / 콜드 필드 분할 기술을 더 적용 할 수 있습니다.
struct BallAoSoA16Hot
{
Float16 x2[16];
Float16 y2[16];
Float16 z2[16];
};
vector<BallAoSoA16Hot> balls; // size n/16: hot fields
vector<Float16> ball_radiuses; // size n: cold fields
데이터 지향 설계의 핵심은 설계 결정을 내릴 때 초기에 이러한 모든 종류의 표현을 고려하여 공개 인터페이스를 사용하여 차선의 표현에 빠지지 않도록하는 것입니다.
메모리 액세스 패턴과 그에 수반되는 레이아웃에 중점을 두어 평소보다 훨씬 더 큰 관심사를 만듭니다. 어떤 의미에서는 추상화를 다소 분해 할 수도 있습니다. std::deque
예를 들어, 알고리즘 요구 사항과 관련하여 집계 된 연속 블록 표현만큼이나 메모리 수준에서 무작위 액세스가 작동하는 방식과 같이 더 이상 보지 않는이 사고 방식을 더 많이 적용 했습니다. 그것은 구현 세부 사항에 다소 초점을 맞추고 있지만 확장 성을 설명하는 알고리즘 복잡성만큼 성능에 거의 영향을 미치는 경향이있는 구현 세부 사항입니다.
조기 최적화
데이터 지향 디자인의 주요 초점은 적어도 한 눈에 조기 최적화에 매우 위험한 것으로 나타납니다. 경험에 따르면 종종 그러한 미세 최적화가 후시와 프로파일 러와 함께 적용되는 것이 가장 좋습니다.
그러나 데이터 지향 설계에서 취해야 할 강력한 메시지는 그러한 최적화를위한 여지를 남겨 두는 것입니다. 이것이 바로 데이터 지향 사고 방식이 도움이되는 것입니다.
데이터 지향 디자인은보다 효과적인 표현을 위해 호흡 공간을 떠날 수 있습니다. 한 번에 메모리 레이아웃을 완벽하게 구현할 필요는 없지만 점점 더 최적화 된 표현을 허용하기 위해 적절한 고려 사항을 미리 만드는 것에 대한 자세한 내용입니다.
세분화 된 객체 지향 디자인
많은 데이터 지향 디자인 토론은 객체 지향 프로그래밍의 고전적인 개념에 맞서 싸울 것입니다. 그러나 나는 OOP를 완전히 무시하는 것만 큼 하드 코어가 아닌 이것을 보는 방법을 제공 할 것입니다.
객체 지향 설계의 어려움은 인터페이스를 매우 세밀한 수준으로 모델링하여 종종 대량 대량 사고 방식 대신 스칼라, 한 번에 한 번의 사고 방식에 갇히게 만들도록 유혹한다는 것입니다.
과장된 예로, 이미지의 단일 픽셀에 적용되는 객체 지향 디자인 사고 방식을 상상해보십시오.
class Pixel
{
public:
// Pixel operations to blend, multiply, add, blur, etc.
private:
Image* image; // back pointer to access adjacent pixels
unsigned char rgba[4];
};
바라건대 실제로 아무도 이것을하지 않습니다. 예제를 실제로 총체적으로 만들기 위해 픽셀이 포함 된 이미지에 백 포인터를 저장하여 블러와 같은 이미지 처리 알고리즘을 위해 인접 픽셀에 액세스 할 수 있습니다.
이미지 백 포인터는 즉시 눈부신 오버 헤드를 추가하지만, 그것을 배제하더라도 (픽셀의 공용 인터페이스 만 단일 픽셀에 적용되는 작업을 제공) 픽셀을 나타내는 클래스로 끝납니다.
이제이 백 포인터 외에도 C ++ 컨텍스트에서 즉각적인 오버 헤드 의미의 클래스에는 아무런 문제가 없습니다. C ++ 컴파일러 최적화는 우리가 구축 한 모든 구조를 가져 와서 대장간으로 제거하는 데 탁월합니다.
여기서 어려운 점은 캡슐화 된 인터페이스를 너무 세분화 된 픽셀 수준으로 모델링한다는 것입니다. 따라서 우리는 이러한 종류의 세부적인 디자인과 데이터에 갇히게되며이 Pixel
인터페이스에 수많은 클라이언트 종속성이 결합 될 수 있습니다.
해결 방법 : 세분화 된 픽셀의 객체 지향 구조를 없애고 대량의 픽셀 (이미지 수준에서)을 다루는 더 거친 수준에서 인터페이스 모델링을 시작하십시오.
벌크 이미지 레벨에서 모델링함으로써 최적화 할 공간이 크게 늘어났습니다. 예를 들어 64 바이트 캐시 라인에 완벽하게 맞지만 일반적으로 작은 보폭으로 픽셀에 효율적으로 인접 수직 액세스 할 수있는 16x16 픽셀의 통합 타일로 큰 이미지를 표현할 수 있습니다 (여러 개의 이미지 처리 알고리즘이있는 경우 하드 코어 데이터 중심의 예제로 세로 방향으로 주변 픽셀에 액세스해야합니다.
더 거친 레벨에서 디자인
이미지 레벨에서의 인터페이스 모델링의 위의 예는 이미지 처리가 연구되고 죽음에 최적화 된 매우 성숙한 분야이기 때문에 일종의 당연한 예입니다. 그러나 입자 방출기, 스프라이트 대 스프라이트 모음, 가장자리 그래프의 모서리, 또는 사람 대 사람 모음에 포함 된 입자가 덜 분명 할 수 있습니다.
데이터 지향 최적화 (예측 또는 후시)를 가능하게하는 핵심은 종종 더 거칠게 인터페이스를 대량으로 설계하는 것입니다. 단일 엔터티에 대한 인터페이스 디자인이라는 개념은 대량으로 처리하는 큰 작업으로 엔터티 모음을 디자인하는 것으로 대체됩니다. 이것은 특히 모든 것에 액세스해야하며 선형 복잡성을 가질 수없는 순차적 액세스 루프를 대상으로합니다.
데이터 지향 설계는 종종 데이터를 통합하여 집계 모델링 데이터를 대량으로 형성하는 아이디어로 시작됩니다. 비슷한 사고 방식이 수반되는 인터페이스 설계에 반영됩니다.
이것은 데이터 지향 디자인에서 얻은 가장 귀중한 교훈입니다. 컴퓨터 아키텍처에 익숙하지 않아서 처음 시도 할 때 가장 최적의 메모리 레이아웃을 찾을 수 없기 때문입니다. 그것은 손에 프로파일 러로 반복되는 무언가가됩니다 (때로는 속도를 올리지 못한 방식으로 몇 번의 실수로). 그러나 데이터 지향 디자인의 인터페이스 디자인 측면에서보다 효율적인 데이터 표현을 찾을 수있는 여지가 있습니다.
핵심은 우리가 일반적으로하고 싶은 것보다 더 거친 레벨에서 인터페이스를 디자인하는 것입니다. 또한 가상 함수, 함수 포인터 호출, dylib 호출과 관련된 동적 디스패치 오버 헤드를 완화하고 인라인 할 수없는 등의 부수적 인 이점도 있습니다. 이 모든 것을 제거하는 주요 아이디어는 대량 처리 (해당되는 경우)로 처리하는 것입니다.
ball->do_something();
vsball_table.do_something(ball)
)(&ball_table, index)
.