직접호출, boost::bind, mem_fun의 속도비교

‘boost::bind를 맘 내키는 대로 써도 될까?’라는 의문에 각 경우의 속도를 비교해보기로 했다.

우선, 재물로 사용될 클래스를 만들고…

class test_class
{
public:
    test_class()
    : _i(10)
    {}

void    show(int i)
{
    ++_i;
}

private:
    int _i;
};// class test_class

아래는 테스트코드의 일부. TIME_DURATION은 포함되는 블록의 수행시간을 마이크로 초단위로 돌려준다.

const int   repeat_num  = 100000;
hs_int64    d;

test_class  t;

TIME_DURATION(d)
{
    for (int i = 0; i < repeat_num; ++i)
        t.show(i);
}
cout    << "direct call: " << d << endl;

TIME_DURATION(d)
{
    for (int i = 0; i < repeat_num; ++i)
        boost::bind(&test_class::show, &t, i)();
}
cout    << "boost.bind call: " << d << endl;

boost::_bi::bind_t, boost::_bi::list2, boost::arg<1> > >   t_mem_boost = boost::bind(&test_class::show, &t, _1);
TIME_DURATION(d)
{
    for (int i = 0; i < repeat_num; ++i)
        t_mem_boost(i);
}
cout    << "boost.bind call via instance: " << d << endl;

TIME_DURATION(d)
{
    for (int i = 0; i < repeat_num; ++i)
        std::mem_fun(&test_class::show)(&t, i);
}
cout    << "mem_fun call: " << d << endl;

std::mem_fun1_t  t_mem_stl   = std::mem_fun(&test_class::show);
TIME_DURATION(d)
{
    for (int i = 0; i < repeat_num; ++i)
        t_mem_stl(&t, i);
}
cout    << "mem_fun call via instance: " << d << endl;

결과

direct call: 658
boost.bind call: 9152
boost.bind call via instance: 4034
mem_fun call: 1972
mem_fun call via instance: 950

테스트를 반복해도 결과는 대동소이하다. boost::bind는 직접호출보다 10배 이상 느리다. 그나마 mem_fun이 좀 낮다.
따라서…

‘boost::bind를 맘 내키는 대로 써도 될까?’ -> 살살 쓰자

파이썬으로 소스코드(.h, .cpp) 줄(line) 수 세기

파이썬 배우는 기념으로 만든 소스(.h, .cpp)의 줄 수 세는 프로그램.
현재 디렉토리를 포함한 모든 하위디렉토리에 속한 소스의 줄 수를 보여준다.

# -*-coding:utf-8-*-
import os

def count_line(filename):
	file	= open(filename)
	line_num	= 0
	while (file.readline()):
		line_num	+= 1
	return line_num

def count_code_lines(dirname):
	file_line_list	= []
	filenames	= os.listdir(dirname)
	for filename in filenames:
		filename	= dirname + '\\' + filename
		if (os.path.isdir(filename)):
			file_line_list	+= count_code_lines(filename)
		else:
			if ((len(filename) > len('.h') and filename[-2:] == '.h') or 
				(len(filename) > len('.cpp') and filename[-4:] == '.cpp')):
				file_line_list.append((filename, count_line(filename)))
	return file_line_list

def get_dir_list(path):
	dir_list	= []
	if (os.path.isdir(path)):
		dir_list.append(path)

	header	= os.path.split(path)[0]
	if (header == ''):
		return dir_list
	else:
		dir_list	+= get_dir_list(header)

	return dir_list

file_line_list	= count_code_lines('.')

dir_line_dict	= {}

for filename, line_num in file_line_list:
	dir_list	= get_dir_list(filename)
	for dir in dir_list:
		if (not dir in dir_line_dict):
			dir_line_dict[dir]	= 0
		dir_line_dict[dir]	= dir_line_dict[dir] + line_num

dirnames	= dir_line_dict.keys()
dirnames.sort()

for dirname in dirnames:
	print "%10d %s"%(dir_line_dict[dirname], dirname)

출력예 : 뭐, 대략 이런식

      8388 .
       409 .\MemoryPool
       136 .\MemoryPool\profiler
       862 .\PicTest
       687 .\SimpleMFC
       403 .\SimpleMFC2
      1627 .\SimpleTest
       142 .\SimpleTest2
        59 .\UDPClient
       155 .\UDPServer
       963 .\WebViewer
       112 .\cliWrap
        19 .\cppLib
      2950 .\rvo_test
       136 .\rvo_test\profiler
       472 .\rvo_test\srv_cntr
      2114 .\rvo_test\sti

C++로 만든 라이브러리를 C#에서 사용하기

내가 만든 라이브러리가 제대로 동작하는지, 그리고, 이 라이브러리는 사용하는데 있어서 문제가 없는지를 확인하기 위해서, 이를 사용한 테스트 클라이언트를 만들기로 했다. 단, 여기에 C#을 사용하고 싶었다. C#에서 간단히 처리할 수 있는 문제를 가지고 MFC와 씨름하면서 시간을 허비하기는 싫었기 때문에.

몇 가지 방법 중에 내가 선택한 방법은 C++/CLI로 C++라이브러리를 감싼 다음 이를 C#에서 사용하는 것이다. 이 방법을 선택한 이유는 순전히, 시도해본 다른 방법이 모두 실패했기 때문이다. 방법이 없다 라기보다는 내가 잘 모르고 익숙하지 않아서 그렇다. 그렇다고 C++/CLI를 사용하는데 익숙한가 하면, 그것도 아니다. 난 C++/CLI는 정말인지 필요 없다고 생각한다. 딱 한가지만 빼고. 바로 C++라이브러리를 감싸는 것. 하여튼, 난 C++/CLI를 하나도 몰랐다. 지금도 잘 모르는 상태이기 때문에, 아래에 내가 설명하는 내용에는 다소의 오류가 있을 수도 있음을 알아주기 바란다.

C++/CLI는 C++의 문법을 이해한다. 이 말은 C++/CLI에서 Unmanaged Code를 사용할 수 있다는 의미이다. 따라서 C#이 C++/CLI의 문법을 100% 이해할 수는 없다. 기본적으로 C#은 Managed Code이기 때문이다. 따라서 C++/CLI로 C++라이브러리를 잘만 감싸면, C#에서 아무런 수고 없이 이를 사용할 수 있다. 참고로 내가 사용한 Visual Studio의 버전은 2005이다.

Visual C++의 CLR타입 Class Library 프로젝트를 생성하고, C++로 만든 라이브러리를 포함시킨다. 사실, 이것은 Unmanaged C++을 사용할 때의 라이브러리 사용과정과 전혀 다르지 않다. 이제부터 할 일은 C++라이브러리 중에서 노출할 필요가 있는 인터페이스들을 Managed C++로 작성해주기만 하면 된다. 이 글의 주요 주제 되겠다.

C++의 데이터타입은 C++/CLI에서 아래의 표와 같이 바꾼다.

Unmanaged Data Type

Managed Data Type

Ref.

class

ref class

struct

value struct

enum

enum class

std::string

String^

PtrToStringAnsi()

std::map

Dictionary^

std::vector

List^

std::list

List^

function pointer

delegate

반드시 이와 같이 할 필요는 없다. std::vector를 Array로 바꾸어도 전혀 상관없다. 목표는 C++에서 데이터를 다루듯이 C#에서 이를 다룰 수 있도록 하는 것이다.

C++/CLI의 struct는 C++과는 달리, value-type으로만 사용된다. reference-type으로 사용하기위해서는 class를 사용해야 한다.

ref class로 선언되지 않은 클래스는 GC(Garbage Collection)의 관리를 받지 않으며, Unmanaged Code이다.

C++/CLI에서의 ^는 C++의 *에 해당한다. 따라서 ^형으로 선언된 변수는 gcnew키워드로 생성해야 한다. gcnew로 할당된 메모리는 GC가 자동으로 해제하므로, 그에 대해 신경 쓸 필요 없다.

Unmanaged String은 Managed String에 직접 대입될 수 없으므로, PtrToStringAnsi()함수로 사전에 변환되어야 한다. 다음은 Managed String과 Unmanaged String사이에 사용되는 매크로이다.

C++라이브러리에서 사용되는 함수 포인터는 C++/CLI에서 적절한 델리게이트로 바뀌어야한다. GetFunctionPointerForDelegate()함수를 사용할 수도 있지만, 나의 경우는 좀 더 간단하게, Unmanaged Code로 작성한 콜백함수에서 Managed Code로 작성한 콜백함수(델리게이트)를 호출했다. 가장 까다롭게 생각했고, 실제로 시간도 많이 잡아먹은 부분이 콜백함수의 처리였지만, 의외로 싱겁게 끝나버렸다.

이렇게 만들어진 라이브러리는 C#에서 바로 사용할 수 있다. 모든 인터페이스를 Managed Code로 재 작성 해야 한다는 번거로움이 있긴 하지만, 적어도 내 생각에는 이것이 가장 무난하고 또, 만만한 방법이라고 생각된다.


2012.01.31 추가.

위의 매크로는 VS2008에서는 동작하지 않는다. 참조


2012.12.04 추가.

예제

Critical Section Block

다음은 멀티쓰레드 프로그래밍에서 부지런한 프로그래머가 흔히 사용하는 코드의 일부이다. 

알다시피 이와 같은 방법은 코드의 수정을 복잡하게 만들고, 데드락을 피하기 위해서 락의 잠금과 해제 쌍을 완벽히 유지해야 하는 부담도 가지고 있다. Exception이나 갑작스런 흐름의 변화는 이러한 부담을 가중시킨다. 결정적으로…, 귀찮다.

그래서 조금 더 게으른 프로그래머는 auto_ptr을 흉내 낸 클래스를 만들어 사용한다.

크리티컬섹션의 범위가 좁은 경우에는 – 그렇다! 보호해야 하는 것은 데이터이지 함수가 아니다 – AutoCriticalSection타입의 변수의 범위를 제한해 주어야 한다.

위 코드는, 그것을 아름답지 못하게 만드는 두 가지 문제점 있다. 첫 번째는, AutoCriticalSection의 변수 명을 사용자가 선택해야 한다는 것이고, 두 번째는 블럭이 의미하는 바를 직관적으로 알아차리기 어렵다는 것이다.

아래는 이를 해결하기위한, 좀 더 많이 게으른 프로그래머의 코드이다.

두 가지 문제점을 개선한 이 코드에는 두 가지 아이디어가 녹아있다.

첫 번째는, 가독성있는 블록을 사용하기 위해서는 기존의 문법에서 방법을 빌린 것이다. 처음에는 이를 위해서 for문을 사용했다. 그러나 loop를 한번만 돌게 하기 위해서 CriticalSectionContainer클래스에 추가적인 변수가 필요했고, 블록 내에서 break와 continue를 사용할 수 있는 어색함이 남게 된다. 사소한 문제지만, 이는 변수 하나에 해당하는 양의 메모리와 한번의 점프 오퍼레이션이 소모된다. 그래서 if문을 사용했고, if문에서 참, 거짓을 판단하기 위해서 bool 연산자 오버로딩을 사용한 것이 두 번째 아이디어다.

다음은 이를 어떻게 사용하는지 보여준다.

블록이 의미하는 바가 무엇인지 분명히 나타나며, 사용자는 새로운 변수에 대해서 고민하지 않아도 되며, 이를 위해서 희생해야 하는 성능상의 손실은 if문 내에서 발생하는 한 번의 비교뿐이다.

부라보~