글을 쓰게 된 이유
셰이더에 특정 값들(Constants)을 전달하기 위해 struct를 사용할 경우 항상 16byte 단위로 나누어떨어지게 구성해야 한다.
sizeof(Vector4 = float * 4) = 4byte * 4
https://learn.microsoft.com/ko-kr/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules
상수 변수에 대한 압축 규칙 - Win32 apps
압축 규칙은 데이터가 저장될 때 얼마나 긴밀하게 정렬될 수 있는지를 나타냅니다.
learn.microsoft.com
따라서 일반적으로 16byte에 맞도록 float 하나가 들어가는 구조체라도 padding변수를 따로 넣어준다.
struct ConstantBuffer {
float a = 0.0f; // 4
float padding[3]; // 12
};
static_assert로 구조체의 크기가 항상 16으로 나누어 떨어지는지도 검사해야 한다.
static_assert((sizeof(BasicPixelConstantBuffer) % 16) == 0,
"Constant Buffer size must be 16-byte aligned");
그래픽스 강의에서 해당내용과 관련해 bool의 경우 1byte인데 c++의 구조체 패딩으로 인해 4byte 취급을 받게 된다고 이 강의를 듣고 bool은 그냥 구조체 안에서 적으면 4byte라고 생각했다.
Shader로 들어오는 Constant Buffer와 C/C++의 struct는 다른데 지금와서 보니까 왜 이렇게 생각하고 있었는지 모르겠지만 이번 기회에 다시 공부하게 되었다.
구조체를 아래 코드처럼 작성했는데 16byte 단위를 검사하는 static_assert가 실패했다.
4byte + 4byte + 4byte + 4byte 라고 생각했기 때문에 당황했다.
struct ConstantBuffer {
float a = 0.0f; // 4
bool b = 0; // 4
bool c = 0; // 4
float padding; // 4
};
sizeof(ConstantBuffer)를 해볼 경우 12가 나오는 것을 확인할 수 있다.
float padding을 padding[2]로 바꿔주니 이 문제는 사라졌으나 왜 이렇게 된건가 싶어서
교수님의 답변을 듣고 찾아보게 되었다.
Struct의 구조체 padding
이유는 Processor는 메모리에서 값을 읽어올 때 한번에 1byte씩 읽지 않는다.
한번에 1개의 word를 읽어오게 된다.
여기서 word의 크기는 몇 비트 프로세서인지에 따라 달라지는데
32bit라면 4byte
64bit라면 8byte
를 최대로 한번에 읽어올 수 있다.
그래서 다음 그림을 보면
32bit 아키텍쳐에서는 최대 4byte씩을 읽을 수 있는데
struct abc에서
char a와 char b의 값을 저장하면 2byte의 공간이 남는다.
int c의 값을 저장하기 위해서는 4byte가 필요하다.
그러나 첫번째 공간에는 2byte만 남았기 때문에 두번째 공간의 2byte에 추가로 접근해야한다.
4byte에 한번에 접근할 수 있는데 2byte씩 2번 나눠서 메모리에 접근하는 것은 당연히 비효율적이다.
이 문제를 해결하기 위해 struct에는 자체 padding이 적용되고 있다.
첫 번째 그림에서는 a 1번, b 1번, c 2번으로 총 4번의 memory access가 필요했는데
두 번째 그림의 경우 a 1번, b 1번, c 1번으로 3번만 access하면 되기 때문에 효율적이다.
이 때 위와 같은 abc구조체의 경우,
size = 8byte
struct B
{
char a;
int c;
char b;
};
이런식으로 char, int, char이 번갈아 나오게 되면
char a를 저장하면 3byte가 남기 때문에 padding이 적용되고
int c 는 4byte이므로 그냥 저장된다.
char b는 그다음 새로운 word이기 때문에 4byte로 취급된다.
이 구조체의 경우
size = 12byte
코드 실험
+
여기서 만약 struct B에 3byte 미만의 자료형을 끝에 추가하는 경우
똑같이 12byte로
char(1byte) + uint16(2byte) < 4byte
라서 1개의 word로 적용되는 것을 확인할 수 있다.
64bit OS struct padding?
그러면 현재 일반적으로 64bit를 쓰는데 왜 32bit의 4byte padding이 적용되는 것인가 라는 생각이 들 수도 있다.
같은 내용에 대한 질문이 stackoverflow에 있다.
https://stackoverflow.com/questions/38788137/structure-padding-on-64bit-machine
위의 답변을 정리하자면 struct내의 자료형중 가장 큰 값을 기준으로 padding을 하므로 64bit 라는 것은
최대 8byte의 자료형까지 한번에 읽을 수 있다는 것이지 무조건 8byte를 읽어와야 하는 것은 아니라는 것이다.
위의 구조체를 보면 uint16_t가 2byte로 가장 큰 자료형이다.
sizeof로 찍어보면 2byte를 기준으로 구조체의 크기를 계산했다는 것을 알 수 있다.
마찬가지로 uint64_t인 8byte 자료형을 사용하면
8byte를 기준으로 padding을 적용하는 것을 확인해볼 수 있다.
아래 유튜브 영상을 참고해서 작성했다.
https://www.youtube.com/watch?app=desktop&v=aROgtACPjjg
'C/C++' 카테고리의 다른 글
C/C++ 을 편하게 해주는 Visual Studio 단축키 (0) | 2023.08.04 |
---|---|
[C / C++] typedef와 using의 차이 (0) | 2023.03.07 |