2021. 11. 6. 16:54ㆍc,c++ 기본/next step
안녕하세요 대학생 개발자입니다
오늘은 바이트 패딩이 왜 그 타입의 사이즈의 경계에 맞게 나오게 되는지 이유를 좀 알아보려고합니다.
네 먼저 저번 내용을 간단히 요약해본다면
어떠한 구조체가 있을때
char의 경우에는 1바이트의 경계에 세우면 반으로 잘리지 않기 때문에 사실상 어디에 있어도 상관이 없습니다.
short, int, double등과 같이 1바이트가 아닌 변수들은 각자의 사이즈의 경계에 서야 된다는것도 알았습니다.
그리고 구조체 자체의 패딩또한 가장 큰 변수에 맞춰서 패딩이 된다는 것도 알았습니다.
그럼 여기서 한단계만 더 들어가보겠습니다 왜 이렇게 맞춰야하는가입니다.
이유는 단순한데요 캐시라인이 64바이트이기 때문입니다. 물론 어느 아키텍쳐냐에 따라 달라지겠지만 요새 cpu들은 대부분 64byte를 국룰처럼 따르고있습니다. 사실상 캐시라인이 64바이트라고 보셔도 된다 이거죠.
일단 캐시라인이 64바이트라는 점에서 우리는 하나를 알 수있습니다.
과연 int type의 변수가 64바이트중 62번째에 있게 된다면 어떻게될까??
뭐... 답은 간단하죠 두개의 캐시라인에 걸쳐서 있는 변수가 되는겁니다. 그러면 운이 안좋게도 0xffff0000 과 0x0000ffff 두개를 읽어서 하나로 합치는 과정이 필요해지겠는데요... 이런경우에는 뭐... 당장 떠오르는 방법은 둘의 하위비트와 상위비트를 합치면 된다고 생각하시겠지만 그뒤에 또 다른 변수가 이어져 있다고한다면... 그것까지 해결하려면 어질어질합니다 정말...
그래서 우리가 메모리풀을 설계할때에는 캐시라인에 걸리지 않게 고려를 잘 해야됩니다. 사실 멀티스레드 환경에서도 각자 바이트수에 맞는 경계에만 잘 세워놔도 큰 문제거리가 생기지 않습니다.. (즉 코딩할때 그냥 경계에만 잘 맞춰세워도 문제가안된다는의미)
캐시가 메모리를 수정후 저장하는 방법에는 두가지 방법이 있습니다. 첫째로 write back이고, 두번째로 write through입니다.
둘을 간단히 비교해보자면 write back의 경우에는 cpu 내부에서만 행동하는 겁니다. 만약 2개의 코어에서 동일한 캐시라인을 가지고 있다면 내가 수정한 캐시라인을 다른 코어에서 무효화 시키는 시그널을 쏜다는거죠.
반대로 write through 같은 경우는 endpoint인 메인 메모리까지 가서 덮어쓰고 올라와버립니다. 이렇게 되면 매우느려지겠죠.. 그래서 우리가 사용가능한 캐시는 write back 의 형태로 사용되고 있습니다.. 물론 캐시를 전부 write back으로만 쓰는건아닙니다. 데이터가 저장되는 캐시를 제외한 다른 메모리는 write through를 쓰는 곳도 있지만 그건 오늘할 내용은 아니니 나중에 정리하도록 하겠습니다.
그럼 실제 메모리에 저장은 언제하는가입니다.
간단합니다. 캐시에서 메모리가 빠질때 변화가 있으면 반영해버립니다.
아까의 write back방법이 되는 이유에 대한 내용을 간단히 말씀드릴게요.
우선 우리의 코어는 자신의 캐시에 없다면 주변 캐시들이 가지고 있는지 물어봅니다.
그리고나서 그 후에도 없으면 메모리까지 가서 긁어오기 시작하는데요... 그렇게 까지 하는 이유는... 메모리까지 가는 시간이 너무 느리기 때문에 운좋게 다른 캐시에 있게된다면 더 빨리 처리할 수 있기 때문인데요...
그렇기 때문에 만약 1번 코어의 캐시에서 한 캐시라인의 값을 바꿔도 동기화에 문제가 없다는 의미입니다.
왜냐면 내 코어에서 없는건 주변 캐시한테 먼저 물어보는게 우선이 되니까요.
그럼 단 몇번의 클럭이라도 줄이는게 중요한 서버 개발자 같은 경우에는 이런 캐시 미스 한건한건이 매우 크게 작용할텐데요... 일단 구글링을 했을때 가장 많이 나오는 방법은 자주 쓰는것들을 배열로 묶어서 관리하는겁니다. 배열로 묶으면 실제로 가까이 있기때문에 실제로 캐시 히트율이 높아진다는 의미죠...
사실 이건 반쯤 맞고 반쯤 틀린말입니다.
예를들어 이런 클래스가 있다고 하겠습니다.
xPos와 yPos는 계속 수정되는 값입니다. 반대로 이름같은 경우는 거의 변하지 않는 값이죠.
이 클래스를 배열로 묶게되면 어떻게 될까요.
당연히 name(20) xPos(4) yPos(4) , name(20) xPos(4) yPos(4)... 이렇게 붙게되겠죠
클래스는 28바이트니 2개의 객체가 같은 캐시라인에 포함이 될겁니다... 즉 이말은... 어차피 인덱스를 2번 지날때마다 캐시라인에 존재하지 않는다는 의미가됩니다. 이게 과연 큰 의미가 있을까요? 어차피 2번할때마다 한번은 캐시라인에 존재하지 않는거고 다시 캐시를 뒤져서 가져와야되는건데 말이죠.
심지어 이런 경우도 발생할 수 있습니다.
A플레이어를 움직이고 나서 B플레이어를 움직이려고 봤더니 내 캐시라인에서 drop 되어버린겁니다. A플레이어를 움직이면서 값을 변경시켰기 때문에 그 여파로 write back이 되어서 다른 코어의 캐시라인에서 그게 사라져 버린겁니다... 이런경우엔 캐시미스죠 당연히...
여기서 제가 드리고자 하는 말은 읽기만 하는 변수와 쓰기만 하는 변수를 구분지어서 나열한다면 캐시히트를 높히는데 의미가 있습니다. cpu에 friendly한 data oriented programming이죠.
하지만 게임을 만들면서 누가 이름만 쭉 나열해놓고... 누가 x포지션만 쭉 나열해놓고 그러겠나요... 코딩 자체가 힘들어질텐데... 아마 대부분 저렇게 하나의 클래스로 묶어서 관리할겁니다. 심지어 그편이 관리하기도 편하구요
그럼 여기서 캐시히트를 높히려면 어떻게 해야되는가에 대한 의문이 드는데요...
name은 거의 변하지 않는 변수라고 했습니다.
그에 비해 xPos와 yPos는 거의 매프레임 마다 변하는 변수라고 했을때...
둘을 의도적으로 다른 캐시라인으로 벌려놓는다면..? 내가 xPos,yPos값을 변경해도 name이 들어간 캐시라인은 다른 코어의 캐시에 유효하게 보존될 수 있지 않을까? 라는겁니다.
그럼 이런 방법을 선택할 수 있겠죠.
캐시라인을 띄워놓게 유도를 하는겁니다. 어떻게요? 우리가 고의로 중간에 패딩을 넣는겁니다. 뒤의 xpos,ypos가 64바이트의 경계에서서 다른 캐시라인으로 떨어지도록말이죠.
c++ 11이전까지는 vs에서만 __decltype(align()) <- 키워드로 캐시라인의 어디에 세워달라고 지정하는 키워드가 있었습니다만
c++ 11이후로부터는 c++에서 지원하는 alignas()라는 키워드가 있습니다.
alignas(n)는 이 변수는 꼭 캐시라인의 n의 경계에 세워줘 라고 우리가 명시하는 겁니다
그럼 이렇게 되는겁니다... 우리가 고의로 xPos라는 녀석을 64의 경계에 세워버림으로써 name과 다른 캐시라인으로 띄워놓는겁니다.
이렇게 된다면 xPos의 값이 아무리 바뀐들... yPos의 값이 아무리 바뀐들 캐시라인이 떨어져 있기때문에 write back을 하더라도 지금 Test객체의 xPos와 yPos가 담겨있는 그 캐시라인만 무효화가 될겁니다.
그렇다면 원래 하나의 캐시라인에 2개의 변수가 들어가있었는데 이제 2개의 캐시라인에 걸쳐서 2개의 변수가 들어가는셈인데요... 공간이 너무 비효율적으로 많이 쓰는것 아니냐 싶을 수도있습니다...
근데 님들 생각해보세요 저거 해봐야 128바이트입니다... 저걸로 x86 기준 어플리케이션에서 사용가능한 메모리 2기가 채우는데 얼마나 걸릴 것 같으세요? 큰 의미 없다는겁니다... 물론 요새는 x64개발이 기본이고 다들 기본으로 램도 16이상씩 끼고 다니니까요... 큰 의미가 없다는 뜻입니다.
그럼 대충 조금 정리해 보겠습니다.
한 클래스에 정보가 변하지 않는것과 꿎누히 변하는 무언가가 있으면. 계속 변하는 무언가 때문에 변하지 않는 값이 다른 코어의 캐시에서 지워져 버려서 캐시 미스가 나게된다는 상황에서 고의로 캐시라인을 띄워놓는 방법으로 캐시 히트를 높힌다 라는게 되겠습니다.
그럼 반대의 케이스도 있지 않겠습니까?
고의로 같은 캐시라인으로 붙이는겁니다.
만약 x와 y가 무조건 같이 쓰이는 변수라고 했을때 x와 y는 같은 캐시라인에 있는게 좋겠죠? 두개의 캐시라인에 분리되어있다면... 결국 두번 찾아내서 써야되니까요... 늦어지잖아요 그러니까 이런 방법을 쓸 수 있습니다.
alignas(64)를 먼저 써놓고... x, y 선언을 하는겁니다. 그러면 x가 64의 경계에 걸치게 되니까 그다음에 오는 변수는 무조건 같은 캐시라인에 있다고 볼 수있죠... 이런방법으로 캐시히트를 높힌다면... 좋지 않을까 하고있습니다.
하지만... 문제는 이건데요... 다른 캐시라인이라면 안사라질수 있으니까 이런 방법을 고안하는데 하지만 alignas(64)를 쓴다고해도 결국 ebp가 캐시라인에 걸쳐져 있지 않다면... 64번째 라인에 그 변수가 걸친다는 보장을 100%해주지는 못합니다...(하지만 요새 컴파일러는 똑똑합니다... 믿어보시죠 거의 맞으니까)
아맞다... c인데 왜 c++내용이 들어간거지......
'c,c++ 기본 > next step' 카테고리의 다른 글
문자열과 해시에 대한 얘기 (0) | 2021.11.07 |
---|---|
메모리에 대한 얘기 (0) | 2021.11.07 |
전처리기, 바이트패딩룰에대한 얘기 (0) | 2021.11.01 |
함수에 대한 얘기 (0) | 2021.11.01 |
연산자와 조건문에 대한 얘기 (4) | 2021.10.29 |