포인터 연산을 아직 잘 이해못하고 계신듯한데요.
list[x[5]].value->x->z 같은경우 포인터 디레퍼런스 연산이 4번 일어나죠.
디레퍼런스하는 속도는 대부분의 기계에서 메모리 참조 연산이기 때문에 굉장히 느린편에 속합니다
대부분의 컴파일러가 이런부분에서 최적화를 기대하긴 힘들다고 보구요~~. 최적화 된다고 해도 속도를 고려한다면, 좋지못한 방법이고, 가독성도 좋지 못합니다.
list[x[5]].value->x->z 의 주소를 0x1000 이라고 가정하면
전자의 경우 int *p = 0x1000 ; 형태가 되어서 참조시에 *p 형태의 디레퍼런스 연산이 딱한번만 일어나기때문에 , 속도/가독성면에서 월등이 이득입니다.
대신에 스택에 포인터 자료형 크기만큼 메모리를 잡아 먹겠죠(32비트에선 달랑 4바이트, 것도 동적사용임으로 메모리를 쓴다고 보기힘들죠)
_
이렇게 생각해보세요.
x = a + b + c + d;라는 문장이 있다면 먼저 a+b를 계산한 다음, 거기에 c를 더한 값을 계산한 다음, 마지막으로 d를 더해서 x에 대입해야 합니다.
a+b를 계산한 값은 어디에 저장해야 할까요? c를 더한 값은?
결국 컴파일러는 코드를 풀어놓는 작업을 해야 합니다.
tmp1 = a + b;
tmp2 = tmp1 + c;
x = tmp2 + d;
...
C에서는 포인터라는 놈 때문에 어느 변수/함수가 어느 주소를 건드릴지 컴파일러가 미리 알기 힘듭니다. 그래서 동작이 달라지면 안되니까 최적화를 보수적으로 할 수밖에 없습니다. 예를 들어
이 경우 사람은 f에서 포인터가 바뀌지 않으니 마지막 줄을 q = p로 바꿀 수 있다는 걸 알아도, 컴파일러가 이를 알기는 매우 어렵습니다. 그래서 이런 경우에는 미리 대입해놓고 쓰면 도움이 됩니다.
하지만 최적화란 게 그렇듯이 컴파일러의 똑똑함에 따라 결과가 어떻게 달라질지 모르므로 직접 짜서 벤치마크를 돌려보는 게 가장 확실합니다.
list[x[5]].value->x->z 가
list[x[5]].value->x->z 가 int * 라는 의미 같습니다.
다른 분들이 설명해 주셨지만 이 경우 컴파일러가 전혀 최적화해주지 않는 다는 가정을 하면
이 값을 사용할 때마다
x의 값 read
x[5]값 read
list의 값 read
list[x[5]].value의 값 read
list[x[5]].value->x 값 read
list[x[5]].value->x->z 값 read
위와 같은 절차를 밟아서 int * 값을 얻게 됩니다.
int *p = list[x[5]].value->x->z;
는 write 한번 하는 것으로 지속적으로 사용되는 위의 6번의 read를 1번의 read로 대체하는 역할을 합니다.
실제적으로 compiler가 많은 부분을 최적화 해주는데 간혹 안되는 부분들이 있어서 그냥 습관적으로 눈에 보이는
부분은 손으로 직접 대체해버립니다.
일반적으로 포인터로
일반적으로 포인터로 할당하는 게 더 좋다고 보이네요.
매번 인덱스를 보게 되면 메모리 로드 연산을 자주하게 됩니다.
포인터로 쓰면 레지스터에서 처리가 가능한 경우가 많죠.
rommance.net
rommance.net
최적화가 어떻게 되어도 100% 전자의 방법이 같거나 월등히 빠릅니다.
포인터 연산을 아직 잘 이해못하고 계신듯한데요.
list[x[5]].value->x->z 같은경우 포인터 디레퍼런스 연산이 4번 일어나죠.
디레퍼런스하는 속도는 대부분의 기계에서 메모리 참조 연산이기 때문에 굉장히 느린편에 속합니다
대부분의 컴파일러가 이런부분에서 최적화를 기대하긴 힘들다고 보구요~~. 최적화 된다고 해도 속도를 고려한다면, 좋지못한 방법이고, 가독성도 좋지 못합니다.
list[x[5]].value->x->z 의 주소를 0x1000 이라고 가정하면
전자의 경우 int *p = 0x1000 ; 형태가 되어서 참조시에 *p 형태의 디레퍼런스 연산이 딱한번만 일어나기때문에 , 속도/가독성면에서 월등이 이득입니다.
대신에 스택에 포인터 자료형 크기만큼 메모리를 잡아 먹겠죠(32비트에선 달랑 4바이트, 것도 동적사용임으로 메모리를 쓴다고 보기힘들죠)
댓글 달기