'programming'에 해당되는 글 20건
- 2009.01.15 [기타팁] SEH를 C++예외 처리처럼 사용하기!!
- 2009.01.15 IOCP..
- 2008.12.11 [lua] 파일 자르기
- 2008.11.25 lua 이야기
- 2008.11.11 [erlang] 분산 프로그래밍 맛보기 1.
- 2008.11.10 erlang...
- 2008.11.07 Dijkstra Algorithm(최단거리찾기)
- 2008.10.20 boost::asio::io_service working thread 여러개 설정
- 2008.09.03 meta programming 맛보기 1
- 2008.08.25 메모리 풀링
[기타팁] SEH를 C++예외 처리처럼 사용하기!!
-----------------------------------------------------------------------------------------------------
SEH(구조적 예외 처리)는
표준 C++ 예외 처리 기법인 try, catch문에 대응해서
MS에서 비졀씨에서만 동작가능하게 만든
예외처리 기법으로..
try, catch 문 같은 경우는 어플리케이션에서
발생되는 예외(코드내에서 명시적으로 throw한 예외)
를 처리한다고 보면
SEH는 시스템에서 발생되는 예외를 처리 할수 있죠..
예를 들어.. 할당안된 포인터에 값을 쓸려고 할때나,
값을 0으로 나눌려고 했을경우등에 대한 예외를 처리할수 있죠..
__try __exception 문으로 SEH예외를 잡아 낼수 있는데요.
요거를 잘쓰면.. 뻑이나도 절대 죽지 않는 프로그램을
만들어 낼수 있습니다.
죽어도 다시 살아나는 -_-;;
문제는 try, catch문과 __try __exception문을 동시에
쓸수가 없죠...
그런데, SEH예외가 발생되는 시점을 알수 있게 해주는
_set_se_translator()을 사용해서 C++표준 예외를 올려줌으로서
SEH를 표준 예외와 같이 사용할수 있게 만들어 쓸수 있습니다
Debugging Allication이라는 책을 참조해서
제가 나름대로 거기에 대한 클래스를 만들어 봤습니다.
자료실에 소스를 올려 놨으니까..
받아서 보시면..
엄청나게 자세한 주석(-_-;;)과 함께 사용예도 있으니까
보는데는큰 무리가 없을거라고 생각됩니다.
저같은 경우에는 벡터맨(T_T)과 게임서버를 만들때
아주 유용하게 사용했습니다 ㅡ,.ㅡ;;
특별하게 설정할 필요 없이.. 프로젝트에 추가만 시켜주면
바로 사용할수 있게 만들어 뒀으니까.. 많이들 사용했으면
좋겠네요.
#define _SEH_USE_CLIPBOARD_를 추가 하면
그리고 예외가 발생한 시점의
레지스트정보도 클립보드에 저장도 되니까 유용하게 사용할수
있을거라는 생각도 되네요....
그럼 즐플들 하세요 ^^
IOCP..
2002년 8월 경에 작성한 글이니, 6년반 전에 썼었던 글이다.
다시 보니, 얼굴이 화끈 거릴정도로 허접한 글이지만, 그래도 이랬었던적도 있다는 생각에 수정없이 올려둔다.
이것도 추억이지머.... 그 카페에 적었던 글이 몇개 더 있는데, 이것들도 올려둘려고 한다.
사실, 여기 블러그에 포스팅한 글들도, 지금 다시 보면 부끄러운 글들이 많은데, 또 6년이 지난 후에 여기 글들을 읽으면, 지금과 같은 낮뜨거움이 생기겠지 :)
----------------------------------------------------------------------------------------------------
IOCP 사용 방법
IOCP의 목적
IOCP는 프로그램이 어떤 핸들( 파일, 소켓 등 )에 대해 I/O 작업을 할 때, 블록 되지 않게 함으로써 프로그램의 대기 시간을 줄일 수 있는 방법이다.
블록이 되면 생기는 문제 제시하면서, 타당성 제공.
IOCP의 사용하기 위한 절차.
마치 IOCP에게 작업을 해줘라고 신청하고 신청한 작업이 끝나면 이런 작업을 누군가 하라고 했는데 그 작업이 지금 완료 되었다고 알려주는 것과 같은 절차를 가진다.
1. IOCP 핸들을 생성한다.
2. 핸들과 키의 쌍으로 IOCP핸들에 등록을 한다.
3. 핸들에 I/O 작업을 한다.( I/O작업을 신청한다는 의미가 더 어울릴 것 같다. )
4. 어떤 핸들에 대한 I/O작업이 완료되면 IOCP가 프로그램에게 알려준다.
5. 완료된 작업에 대해 해야 할 일을 한다.
1. IOCP 핸들을 생성한다.
I/O작업을 대신 해줄 IOCP 핸들을 생성해야 한다.
다음은 IOCP 핸들을 생성하기 위한 함수의 원형이다. 뒤에 보겠지만, 핸들과 키의 쌍을 IOCP 핸들에 등록하는 것도 이 함수를 사용한다.
HANDLE CreateIoCompletionPort (
HANDLE FileHandle, // handle to file
HANDLE ExistingCompletionPort, // handle to I/O completion port
ULONG_PTR CompletionKey, // completion key
DWORD NumberOfConcurrentThreads // number of threads to execute concurrently);
인자를 보면, hFile은 IOCP에 등록하는 I/O 핸들( 소켓, 파일 등 )이다. 이렇게 함으로써 IOCP는 hFile에서 일어나는 I/O를 감시하면서 작업이 끝났을 때, 알려줄 수 있게 되는 것 이다. 다음의 hExistingPort는 새로운 포트를 생성하려면 NULL, 기존에 있는 IOCP에 연결을 하려면 CreateIoCompletionPort()함수가 이전에 반환했던 IOCP 핸들을 넘겨준다. 세 번째 CompletionKey는 위에서 말했던 키를 나타낸다. 나중에 핸들에서의 작업이 끝나면 IOCP는 어떤 핸들에서의 작업인지 알려줄 때 핸들 값 자체를 넘겨주는 것이 아니라, 이 키 값을 알려준다. 따라서, 여러 개의 핸들을 등록했다면, 등록할 때 이 키 값을 고유하게 해주므로써 각각을 구분할 수 있도록 한다. 마지막 NumberOfConcurrentThreads는 IOCP가 입출력 작업을 할 때 얼마나 많은 쓰레드를 사용하여 작업을 할 지 설정하는 것으로 특별히 정확하게 얼마나 설정해야 할 지 모를 때는 0의 값으로 설정하면 가장 최적화된 방법으로 스레드를 생성하여 사용한다. 생성이 성공하면 IOCP핸들을 반환하고 실패 하면 NULL값을 반환한다.
Tip dwCompletionKey값은 여러 가지로 사용할 수 있다. 타입이 DWORD이므로 어떤 형식의 포인터로 사용하는 것도 가능하고 정수로써도 사용할 수 있다.
HANDLE hIOCP;
hIOCP = ::CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, NULL, NULL );
그저 생성하는 것이므로 다음과 같이 생성을 한다.
2. 핸들과 키의 쌍으로 IOCP핸들에 등록을 한다.
위에서 설명한 CreateIoCompletionPort함수를 사용하여 핸들과 키를 등록할 수 있다.
HANDLE hFileHandle; //IOCP에 등록하고 싶은 핸들.
m_hIOCP = ::CreateIoCompletionPort(
hFileHandle,
hIOCP,
dwCompkey,
NumberOfCurrentThread );
이와 같이 hFileHandle-dwCompkey 쌍으로 등록하면 나중에 I/O출력함수를 hFileHandle을 인자로 호출을 하면 I/O가 끝나면 IOCP가 dwCompkey값을 주면서 I/O작업이 끝났다고 알려준다. 그러므로 dwCompkey값을 어떤 값으로 사용하냐에 따라 프로그램을 작성하는 방법이 많이 달라질 수 있다.
IOCP의 기법은 어떤 핸들( 파일, 소켓 등등 )과 특정 값을 일치 시켜서 나중에 작업을 알수 있도록 하는 것이다.
사용자는 어떤 핸들에 대한 작업이 완료 되었을 때, 어떤 값을 알려달라고 시스템에 알려준다.
그리고, 핸들에 I/O작업을 실시한다. 다른 작업을 하는 도중 핸들에 I/O작업이 완료 되었으면 시스템은 프로그램에게 처음에 등록했던 값을 알려준다.
3. 핸들에 I/O 작업을 한다.
윈도우에서는 소켓이나 파일 핸들이나 같이 사용되기 때문에 아무 I/O함수나 사용하여 I/O작업을 신청한다.
단 여기서 중요한 것은 IOCP는 중첩된(Overlapped) I/O 방식을 사용한다는 것이다. 따라서, WSARecv()/WSASend(), ReadFile()/WriteFile()을 사용할 것을 권장한다.
OVERLAPPED *pPacket = new OVERLAPPED;
if( FALSE == ReadFile( (HANDLE)hFileHandle,
szBuffer,
MAX_BUFFER,
NULL,
pPacket ) )
{
if( GetLastError() != ERROR_IO_PENDING )
{
printf("ReadFile() failed with error %d\n", GetLastError());
return 0;
}
}
위와 같이 프로그램을 작성하여 입출력을 등록한다. 보통 우리가 알고 있는 것과 차이가 있다면 OVERLAPPED 구조체를 생성하여 인자로 넘겨준다는 것이다. 이 구조체에 대한 자세한 정보는 전문서적을 참조하기 바란다. 하지만 이 구조체의 가장 큰 역할은 나중에 IOCP가 작업이 완료되었다고 알려줄 때, 읽기 작업을 끝냈는지 쓰기 작업을 끝냈는지 또는 10개의 작업을 하라고 했는데 도대체 어떤 작업을 끝냈는지 알려 줄 방법이 없으므로 이 구조체를 사용하여 작업을 신청하기 전에 나중에 끝났을 때 알아볼 수 있도록 흔적을 남기기 위한 것이다. ( 원래는 더 많은 의미를 담고 있지만, 잘 사용 안 하니까… ) 따라서, OVERLAPPED 구조체를 상속하여 흔적에 사용할 정보를 담을 수 있도록 만들어서 사용한다.
4. 어떤 핸들에 대한 I/O작업이 완료되면 IOCP가 프로그램에게 알려준다.
IOCP가 작업의 완료를 알려주는 것을 받는 방법은 여러 가지가 있으나 여기서는 GetQueuedCompletionStatus() 함수를 사용하는 법을 알아보도록 한다.
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort, // handle to completion port
LPDWORD lpNumberOfBytes, // bytes transferred
PULONG_PTR lpCompletionKey, // file completion key
LPOVERLAPPED *lpOverlapped, // buffer
DWORD dwMilliseconds // optional timeout value
);
첫 번째 CompletionPort는 이전에 생성했던 IOCP 핸들이고, lpNumberOfBytes I/O작업에서 읽거나 쓴 바이트의 수를 반환해 준다. 세 번째 인자는 핸들을 IOCP에 등록할 때 키로 등록했던 값을 반환해준다. 이 값을 가지고 어떤 핸들로 부터의 작업이 완료가 되었는지 알수 있다. 네 번째 인자는 작업을 등록할 때, 어떤 작업인지 흔적으로 남겼던 OVERLAPPED 구조체를 반환해 준다. 이 구조체에 남겨진 정보를 가지고 어떤 작업이 끝난건지 알 수 있도록 수동으로 만들어야 한다. 여섯 번째 인자는 IOCP로부터 오는 완료 메시지를 얼마나 대기 하고 있을 것인지 설정하는 값이다.
if( FALSE == GetQueuedCompletionStatus(
hIOCP,
&dwBytesTransferred,
&dwCompKey,
(LPOVERLAPPED *)&pOverlap,
INFINITE ) )
{
if( pOverlap != NULL )
{
if( 64 != GetLastError() )
{
printf( "Error Thread : GetQueueCompletionStatus( %d )\n", GetLastError() );
return 0;
}
}
}
위와 같이 사용한다. 주의 해야 할 것은 GetQueuedCompletionStatus함수가 반환하는 값이 FALSE라고 해서 에러가 아니라 pOverlap이 NULL이고 FALSE를 발생했던 에러의 값이 64가 아닐 때만 진짜로 에러가 발생한 것이라는 것이다.
5. 완료된 작업에 대해 해야 할 일을 한다.
위에 GetQueuedCompletionStatus함수에서 전달 받은 값들을 가지고 3번 과정에서 I/O작업을 신청할 때, I/O작업이 끝나면 하려고 했던 작업을 하면된다.
if( dwBytesTransferred == 0 )
{
printf("Closing socket %d\n", g_CClients[dwCompKey].GetSocket() );
if( SOCKET_ERROR == closesocket( g_CClients[dwCompKey].GetSocket() ) )
{
if(GetLastError() == 10038)
continue;
else
{
printf("closesocket() failed with error %d\n", WSAGetLastError());
return 0;
}
}
continue;
}
//recv
if( /* pOverlap에 읽기 작업이라는 흔적이 있다면 */ )
{
// 또 하나 중요한 것은 3번에서 신청한 I/O작업에 사용한 버퍼의 포인터를
// 여기서 알고있어야 한다는 것이다.
Printf( “%s\n”, szBuffer );
}
else if( /* pOverlap에 쓰기 작업이라는 흔적이 있다면 */ )
{
printf( “쓰기가 완료되었네요…\n” );
}
else
{
// 남겼던 흔적이 없거나 에러가 발생한것임.
}
위의 GetQueuedCompletionStatus함수에서 반환해준 dwBytesTransferred의 값이 0이란 의미는 해당 핸들이 끊어졌다는 것이다. ( 소켓의 경우 ) 따라서, 핸들을 닫아 주면 된다. 그 다음에는 아까 전에 말했던 OVERLAPPED구조체에 3번에서 I/O작업 신청할 때 남겼던 흔적에 따라서 나머지를 처리해 주면 된다.
IOCP를 사용할 때 주의 할 점
IOCP 기법을 사용할 때의 주의 할 점은 I/O작업에 사용할 버퍼가 I/O작업을 신청한 시점부터 IOCP로부터 완료했다는 메시지를 받기 전까지 변경이 되면 안 된다는 것이다. 따라서, I/O 작업에 사용하는 버퍼는 전역 변수이거나 동적으로 할당된 메모리 공간이어야 한다. 특히 WSASend()나 WriteFile() 작업을 할 때, 함수를 호출하는 시점에 사용했던 공간을 함수이 끝나자 마자 지워버리거나( 지역 변수 ), 다른 값으로 변경하게 되면, 나중에 IOCP가 시간이 생겨서 이 작업을 할 때, 엉뚱한 공간을 참조하거나, 원래 쓰려고 했던 데이터가 아닌 다른 데이터를 쓰게 되므로 조심해야 한다.
OVERLAPPED구조체도 마찬가지다. 작업이 끝나기 전에 지워지면 안 된다.
[lua] 파일 자르기
지하철 타고 다닐때 들을려고 산 mp3 플레이어가 d2인데, 여기에 소설 파일을 다운 받아서 음악을 들으면서 다닐때가 많다.
그런데 d2의 문제점 중의 하나가 텍스트 파일이 일정 크기 이상일때, 잘려서 그 이후를 보기 힘들다는 불편함이 있었다.
그래서 텍스트를 넣을때 항상 일정크기별로 잘라서 저장해야 되는 불편함이 있었다.
최근에는 와이프가 d2로 소설을 보는데 재미가 들려서 d2를 뺏겨 버리게 되었는데(T_T), 얼마전에 큰 파일을 잘라달라는 부탁을 받게 되었다.
일일히 잘라야 되는게 귀찮기도 하고 해서, 파일 자르는 프로그램을 하나 만들기로 해서, luaf로 간단하게 만들어 보았다.
아래가 코드..
코드가 좀 많이 허접하지만... 나중에 또 쓰일수도 있을거 같아서 저장용....
lua 이야기
클라이언트 전체와 host쪽 모듈에 lua을 적용하기를 원했지만, 결과적으로 개발은 클라이언트 사이드는 UI개발 및 config성 데이터에만 일부 적용, host모듈은 network엔진을 제외한 거의 대부분의 로직이 lua로 개발 진행이 되었다.
클라이언트 사이드야 내 업무 분야가 아니기 때문에, 딱히 말할것은 없지만, 좀더 적극적으로 활용 할수 있었을 거라는 아쉬움은 좀 남아 있다.
host쪽 모듈은 개발 당시에는 70%이상이 lua로 개발되었고, 클베시점에서, 몇가지 이유로 lua코드를 c코드로 포팅하게 되었으나, 거의 로직의 수정없이 포팅이 된거라 개발당시의 스크립트 언어의 장점은 거의 누렸다는 생각은 든다.
초기 개발시 lua을 적용하게 된 이유중의 하나가, 게임 플레이의 모드화를 포함시켜 볼려고 했었고, 이를 위해서 게임 모드쪽 로직은 100% lua로 작성이 되었으나, 이것들 역시 c코드로 포팅이 되어 버리게 되어서 아쉬움이 많이 남아 있다.
처음 프로젝트에 적용했던거라서, 설계상의 문제점도 몇가지 있었고, 삽질도 많이 했었지만, 개인적으로는 lua의 게임개발에서의 적용은 적극 권장한다.
[erlang] 분산 프로그래밍 맛보기 1.
테스트 : 두개의 system에서 3번의 ping-pong 테스트를 해 본다.(PC두개가 없어서 같은 PC에서 두개의 프로세스를 띄워서 테스트 했다)
- 설정
- ping system
- erlang 인터프리터 실행시 다음과 같은 옵션으로 실행
- werl -sname ping
- 프럼프트에 "ping@systemname>" 이 떠야 정상
- pong system
- erlang 인터프리터 실행시 다음과 같은 옵션으로 실행
- werl -sname pong
- 프럼프트에 "pong@systemname>" 이 떠야 정상
- 코드
-module(ping_pong).
-export([start_ping/1, start_pong/0, ping/2, pong/0]).
ping(0, Pong_Node) ->
{pong, Pong_Node} ! finished,
io:format("ping finished ~n", []);
ping(N, Pong_Node) ->
{pong, Pong_Node} ! {ping, self()},
receive
pong ->
io:format("Ping received Pong~n", [])
end,
ping(N-1, Pong_Node).
pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end.
start_pong() ->
register( pong, spawn(tut17, pong, [])).
start_ping(Pong_Node) ->
spawn(tut17, ping, [3, Pong_Node]).
- 실행 결과
- pong
- ping
** 간단 설명
- start_ping(Pong_Node) ->spawn(tut17, ping, [3, Pong_Node]). ping()
- function을 Thread에 spawn시킨다.
- Pong_Node는 타겟시스템 이름
- ping(0, Pong_Node) ->
{pong, Pong_Node} ! finished,
io:format("ping finished ~n", []); - pong시스템에게 finished를 전달하고 메시지 출력 후 종료
- erlang에서 ! 연산자는 send message를 의미한다.
- 첫번째 인자가 왜 0인지는 설명이 필요 없을듯..
- ping(N, Pong_Node) ->
{pong, Pong_Node} ! {ping, self()},
receive
pong ->
io:format("Ping received Pong~n", [])
end,
ping(N-1, Pong_Node). - pong 시스템에게 메시지 전달 후 응답 기다림
- 응답이 오면 메시지 출력 후 다시 ping실행 N = 0 일때 까지
- receive 명령어는 다른 프로세스로 부터 메시지가 올때까지 대기 하는 명령어다(WaitForXXX 개념)
- pong() ->
receive
finished ->
io:format("Pong finished~n", []);
{ping, Ping_PID} ->
io:format("Pong received ping~n", []),
Ping_PID ! pong,
pong()
end. - ping시스템으로 부터 메지지 대기
- finished 메시지가 오면 메시지 출력 후 종료
- ping메시지가 오면 pong 메시지 전달 후 다시 대기
끝..
erlang...
erlang이란 언어를 잠깐 보면서 느낀 것들...
첫번째로, C++에서의 meta programming이 자꾸 머리속에 그려졌다. c++에서의 흉내 흉내로 볼수 있겠지만, 나 같은 경우, 절차형 언어인 c++스타일에 익숙하고, 함수형 언어에 대한 개념이 부족했기 때문에, 함수형 언어라는 스타일이 익숙하지 않은 것이 사실이며, 실제, 그 용도에 대해서 의문을 가지고 있는 것이 사실이다.
함수형 언어에 어느 정도 익숙해 진다면, 나의 meta programming에 대한 이해도 역시 높아 지지 않을까 기대 해 보게 된다.
factorial 구하는 예제를 통해서, erlang과 meta programming의 비슷한 구조를 살펴 보자.
예1) erlang
-module(tut1).
-export([fac/1]).
fac(1) ->
1;
fac(N) ->
N * fac(N - 1).
#compile
1> c(tut1).
{ok,tut1}
#excute
2> tut1:fac(4).
24
예2) c++
template<int num>
struct fac
{
enum { result = num * fac<num-1>::result};
};
template<>
struct fac<1>
{
enum {result = 1};
};
void main()
{
cout << fac<4>::result << endl;
}
보시다 시피 아주 비슷한 구조를 가지고 있지 않은가?
erlang이라는 언어는 오늘에서야 처음 봤지만, 지금까지 본 예제들은 대부분 재귀호출과 패턴매칭(c++에서는 템플릿 특수화라고 불리울수 있는...)이 기본 골격이라고 보여진다.
두번째는 C#에서의 linq가 함수형 언어의 패러다임이 도입된 형태라는 것이다. C#이나 linq를 거의 써본적이 없기는 하지만(1주일 정도 해 본거 같다.), 생각해보면 그거 써볼때도, 람다식이니 하는 얘기들을 본거 같은데, 솔직히 함수형 언어에 대한 이해 자체가 없었기 때문에, linq의 기본 개념 조차 생각해 본적이 없었다가, 오늘 erlang를 잠깐 이나마 보고 나니, linq과 비슷한 부분이 많다는 것을 알게 되었다. 역시 아는 만큼 보인다는 말이 실감이 되는거 같다.
이제야 튜토리얼 몇개를 보고, 실행 조차 못해본(50mb 다운받는데 3시간이 넘게 걸린다, 다운 받고 있는 사이에, 튜토리얼 대충 훝어 보고, 이글 쓰고, 하드카피로 인쇄하고, 담배 두대 피고 왔는데도, 아직 한시간 넘게 남았다. T_T )처지에서 더 이상의 얘기를 하는 것은 무리...!!!
Dijkstra Algorithm(최단거리찾기)
여기에서 소스 참조
** 최단경로 : 방향그래프(Directed Graph)의 간선이 양수의 Weight를 가질 때 임의의 출발 정점에서 도착 정점까지의 경로 중 경로의 길이가 최소인 경로
Dijkstra Algorithm
1. 인접행렬 상태로 각 간선의 가중치(Weight)가 존재하도록 한다.
정점 i에서 i까지의 가중치는 0이고 정점 i에서 j까지의 간선 E(i, j)가 존재치 않으면 가중치는 무한대(∞)가 된다.
2. 집합 S와 T를 정하는데 S는 출발 정점을 초기값으로 하고 집합 T는 출발 정점을 제외한 모든 정점을 포함하도록 초기화한다.
3. 출발 정점을 제외한 모든 정점으로부터의 거리의 초기값(dist[i], 2<=i<=n)을 [1]의 인접행렬에서 취한다.
4. 집합 T의 원소 중 출발점으로부터의 거리가 최소인 정점 v를 택하여 T에서 제거하고 집합 S에 추가한다.
5. T집합내의 모든 정점 w에 대해 출발점으로부터의 거리(dist[w])와 간선 E(v, w)의 길이에 v정점의 거리(dist[v])를 합한 값 중 작은 것을 선택하여 정점 w의 거리(disw[w])로 한다.
6. 4, 5의 과정을 T집합이 공집합이 될 때까지 반복한다
수행시간은 빅오(n^2)
예제.
예제 소스의 배열대신 graph로 대체해서 만든게 있는데.... 지저분해서 못 올리것다....
boost::asio::io_service working thread 여러개 설정
한심하게도, 얼마전까지 io_service내부에서 working thread가 관리되는걸로 생각하고 있었다.
그런지만 그럴지가 없지 않은가? CompletionPort나 내부적으로 Proactor패턴을 구현하고 있다고 얼마전 포스팅에서 쓴적이 있는데, io_service내부에서 thread를 관리할 이유가 없는 것이다.
어쨋거나, working thread는 외부에서 여러개 실행해서, bind시켜 주면 된다.
const int numofthread = 5;
boost::thread_group threadg;
for( int i = 0; i < numofthread; i++ )
{
threadg.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
}
threadg.join_all();
return 0;
meta programming 맛보기 1
1. 메타데이터(meta data)
문제) 구글 입사 문제
양의 정수 n에 대해서 1과 n 사이에 1이 나오는 횟수를 나타내는 함수를 f(n)이라고 한다. 예를 들어 f(13)=6이다.
f(n)=n이 되는 첫 번째 양수는 1이다. 두 번째 양수를 구할 수 있는 코드를 구현하시오. (정답은 199981)
일반적인 해법)
메타데이터로서 접근)
일반적인 해법과 메타데이터로서의 접근과의 근본적인 차이점은, 실행시간에서의 결과값 계산과 컴파일시간에서의 결과값 계산이다.
(어떤 방법을 쓰던간에) 두 프로그램을 실행시키면, 어떤 것이 더 빠를것인가는 생각하지 않아도 후자가 빠르다. (전자의 실행시간은 O(n!), 후자는 O(1) 이다)
때문에 보통 메타프로그래밍은 컴파일시간을 희생시켜, 실행시간에서의 성능을 얻는다.
메모리 풀링
결과는 아래와 같다.
테스트 환경: |
컴파일러 : vc2008 express edition |
PC : Pentium(R)4 CPU 3.00GHz, Ram 2G |
운영체제 : XP sp2 |
테스트 내용 : |
1032BYTE 짜리 buffer, 4byte int 각각 5만개씩 생성 |
결과 : |
** message_buffer로 테스트 했을 때 |
미리 잡아 놓고 dequeue를 이용한 풀[message_buffer] : 0 |
기본 new/delete[message_buffer] : 0.156 |
boost::object_pool[message_buffer] : 142.844 |
boost::pool_allocator[message_buffer] : 141.203 |
boost::fast_pool_allocator[message_buffer] : 0.047 |
** int로 테스트 했을 때 |
미리 잡아 놓고 dequeue를 이용한 풀[int] : 0.016 |
기본 new/delete[int] : 0.032 |
boost::object_pool[int] : 3.875 |
boost::pool_allocator[int] : 3.89 |
boost::fast_pool_allocator[int] : 0 |
생각보다 object_pool 이나 pool_allocator가 성능이 신통치가 않다.(
미리 잡아 놓고 dequeue를 사용한 풀을 써볼려고 테스트를 만든건데, 위의 테스트는 버퍼를 미리 잡는 시간은 제외된 시간이다. 의외로 쓸만한거 같은데, 동기화 오브젝트를 넣고 하면, 얼마나 성능이 나올지는 좀더 테스트를 해 봐야 될거 같다.
아래는 소스 코드