포인터에 대한 고찰

jundols의 이미지

포인터의 강력함을 부정하는 것은 아니지만, 과연 포인터의 성능은 어떨까?

다음과 같은 상황이 고민이 되는 경우가 있겠습니다.

struct buf {
int data[1024];
}

struct sendStruct{
struct buf sendData;
}

struct sendStruct temp;

위 같은 경우 temp의 크기는 4096 bytes 가 되겠지요.(뭐 더 될수도 있겠고...)

temp를 다른 Task에게 메세지 큐로 보낸다고 하면, 4096을 보내야 되니 매우 오버해드가 큽니다..

해결책으로, 다음과 같이 하면 되겠지요.

struct buf {
int data[1024];
}

struct sendStruct{
struct buf *sendData;
}

struct sendStruct temp;
temp.sendData = malloc( sizeof( struct buf ) );

위와 같이 한다면, 다른 Task에게 메세지 큐로 temp 변수를 보낸다고 해도 겨우 4byte만 보내도 됩니다.

효율적이지요

그렇기 때문에, 현재 나는 구조체의 멤버들을 아주 많이 포인터로 선언해 놓고 쓰고 있습니다.

다음과 같은 코드를 생각해 볼 수 있습니다.

appList->pOBEApp->pParam->u.integer = NULL;

위 코드는 NULL이란 것을 넣기 위해 소위 '땡겨오기'를 4번이나 하고 있습니다.

'땡겨오기' 라고 표현하는게 맞는지 모르겠지만,

이걸 하게되면 컴파일 시점에는 u.integer의 주소를 알 수 없으므로

실행시마다 메모리로 부터 땡겨오게 될 것입니다. (아직 확인 안해봤지만, 그럴것이라 생각됨)

즉, u.integer 의 주소를 알기 위해 cpu는 메모리 access를 4번이나 더 해야합니다.

그리고 그렇게 해서 얻어낸 u.integer의 주소에 NULL이란 값을 적는 일을 수행합니다.

appList.pOBEapp.pParam.u.integer = NULL

위의 코드에 비해 4배 이상 느리게 될 것입니다. for문을 돌리게 되면 문제는 더 심각해 집니다.

디어셈블을 하면 알겠지만...

이 내용이 사실이라면, 포인터를 너무 남용하는것은 성능상으로도 좋지 않은것 같습니다.

익명 사용자의 이미지

appList.pOBEapp.pParam.u.integer = NULL
appList->pOBEApp->pParam->u.integer = NULL 이 두 문장의 차이점이 어떤건가요?

처음 문장도 다이렉트로 u.integer의 주소를 받아오는게 아니고 역시 밑의 포인터 처럼 타고가서 받아오는거 아닌가요?

gamdora의 이미지

처음 문장은 컴파일 시간에 u.integer의 주소를 알 수 있습니다.

익명 사용자의 이미지

그럼 전역변수로 선언을 하면 1번만 갔다오면 되지 않나요 ㅋ

매우 크리티컬한 상황이 아니라면 큰 상관이 없을듯

저러한 속도만을 생각한다면 함수, 객체지향 다 버리고 절차적 프로그램밍을 -_-;

ㅡ,.ㅡ;;의 이미지


무엇이 절대적이다? 그렇다면.. 애초부터 그것만존재하게 해야할것이고 따라서 다른것은 존재할필요도 없죠.

왜 배열로 선언할수도 있고 포인터로 선언할수도 있게했을까요..

그것은.. 상황에따라 적절히 사용하란뜻이겠죠..

위상황에서도 성능은 감수하고 편리성을 추구하겠다는생각이 있으시다면.. 그렇게 해도 무방하다면.. 그렇게 하는것이 옳은것이고..

성능이 더중요하게 요구되는 상황이라면.. 님말씀데로 안할수도 있겠죠..

또한 어떤부분에서는 포인터가 더빨라지는경우도 있으니 상황에 맞게 적절히..

물론 객체지향이 절대적이다 라고 생각하는것도 큰오산이이라 봅니다.
----------------------------------------------------------------------------
C Library Development Project


----------------------------------------------------------------------------

익명 사용자의 이미지

말씀하신 내용은 (그 옳고 그름이나 유용성 등은 논외로 하더라도) 포인터와는 아무 상관이 없습니다.

동적 할당이냐 정적 할당이냐와 관련된 것일 뿐입니다.

daybreak의 이미지

Quote:

그렇기 때문에, 현재 나는 구조체의 멤버들을 아주 많이 포인터로 선언해 놓고 쓰고 있습니다.

아직 우리 사회에서 '나는'이라는 말은 공손한 표현이 아니라고 생각합니다.
이 말은 '저는'이 쓰일 자리에 '나는'이 쓰이면 일반적인 예의에 어긋나는 표현이 될 수 있음을 뜻합니다.

확인되지 않은 사실을 적은 것은 아이디어라고 쳐도
제가 늙어서 그런지 요즘 '나는' '내가' 이러는 표현이 상당히 보기 좋지 않네요.

'나는' '내가'에 대한 제 생각이 마음에 들지 않으시면 가볍게 무시해 주시기 바랍니다.

revoman의 이미지

Quote:
temp를 다른 Task에게 메세지 큐로 보낸다고 하면, 4096을 보내야 되니 매우 오버해드가 큽니다..

궁금한 점이 있어서 댓글 올립니다.

여기서 말하는 Task란게 쓰레드를 의미하는 건가요? 그리고 메시지 큐란건 IPC Message Queue를 뜻하는 건가요? 그렇다면 쓰레드간 통신에서 메시지큐를 사용한다는게 잘 이해가 안됩니다.

고수님들의 명쾌한 설명 부탁드립니다.

nako의 이미지

appList->pOBEApp->pParam->u.integer = NULL 

이런 류의 코드는 cache fail 이 날 가능성이 매우 높습니다.
메모리 참조는 횟수는 어떻게 할 도리가 없는 것이지만 cache hit이냐 fail이냐는 차이가 큽니 다.
중요한 loop 내에서 cache fail이 두어번 연속으로 일어난다면 성능상 문제를 간과할 수 없겠죠.

컴파일러 지시자나 링커 스크립트를 통해서 연속된 메모리 공간에 주소를 위치시켜야 할 겁니다.

물론 프로파일링상 비중있는 코드일 경우에 그렇다는 이야기지 대부분은 문제가 없겠고요.

간접 참조 횟수도 줄이고, 가독성도 높이는 아래와 같은 방법은 어떨까요?

function(){
    struct somestruct *local_u = appList->pOBEApp->pParam->u;
    ...
    { // loop
        local_u->integer = NULL;
        local_u->float = NULL;
        local_u->something = do_someting();
        ...
    } // end of loop
    ...
}

얼마만큼 효과가 있는지 재어보지는 못했습니다만 저는 DSP application이나 device driver 처럼 속도에 민감하거나 구조체가 심하게 중복되어 있는 프로그램을 작성할 때 습관적으로 위와 같이 합니다.

박민권의 이미지

저런 문제 정도는 컴파일러가 알아서 최적화 해주지 않을까요?
위의 nako님이 풀이하신 방식처럼요.