http://yesarang.tistory.com/364
최근 제가 진행하던 프로젝트에서 boost::shared_ptr를 상당히 많이 사용해 왔었는데, 성능이 좋게 나오질 않아서 혹시나 해서 오늘 성능을 간단하게 측정해 봤더니 속도 차이가 상당히 많이 나는군요. 성능이 좀 느리겠거니 했는데 상상 이상이었습니다.(실은 이 문제 때문에 한바탕 홍역을 치뤘죠. ㅠ.ㅠ)
다음은 테스트 프로그램입니다.
$ cat shared.cpp
#include <iostream>
#include <boost/shared_ptr.hpp>
using namespace std;
using namespace boost;
class AClass
{
public:
void Op() const
{
++i;
}
int Get() const
{
return i;
}
private:
mutable int i;
};
#ifdef SHARED_PTR
void Foo(const shared_ptr<AClass>& p)
{
p->Op();
}
#elif defined(SHARED_PTR_COPY)
void Foo(shared_ptr<AClass> p)
{
p->Op();
}
#else
void Foo(AClass* p)
{
p->Op();
}
#endif
int
main(int argc, char* argv[])
{
#if defined(SHARED_PTR) || defined(SHARED_PTR_COPY)
shared_ptr<AClass> p(new AClass());
#else
AClass* p = new AClass();
#endif
int maxCnt = 1000000000;
if (argc >= 2)
maxCnt = atoi(argv[1]);
for (int i = 0; i < maxCnt; ++i)
{
Foo(p);
}
cout << p->Get() << endl;
return 0;
}
이 테스트 프로그램은 세 가지 경우를 테스트하기 위한 것입니다.
1. raw pointer를 사용하는 경우: SHARED_PTR 또는 SHARED_PTR_COPY 가 정의되지 않은 경우입니다.
2. shared_ptr을 사용하는 경우: SHARED_PTR 이 정의된 경우입니다. Foo()를 호출할 때, const shared_ptr reference를 넘기기 때문에 복사가 일어나질 않습니다.
3. shared_ptr을 쓰면서 복사를 하는 경우: SHARED_PTR_COPY가 정의된 경우입니다. Foo()를 호출할 때, shared_ptr 객체를 넘기기 때문에 복사가 한 번 발생하고 이때, shared_ptr의 복사 생성자가 수행되면서 내부적으로 reference count를 증가시키고, 다시 함수에서 리턴될 때, reference count를 감소시키게 됩니다. 그리고 reference count 증가 및 감소는 보통 atomic operation으로 수행되기 때문에 보통의 증가/감소보다는 속도가 느린 걸로 알고 있습니다.
이 세가지에 대해 컴파일러 최적화 옵션 및 BOOST 옵션을 약간 달리 하면서 실행을 해 보았습니다. 다음은 테스트를 위한 Makefile 입니다.
$ cat Makefile
all: shared1 shared2 shared3 shared_copy1 shared_copy2 shared_copy3 plain1 plain2 plain3
#CFLAGS=-pg -g
#LDFLAGS=-pg -g
# CASE #1
CFLAGS1=
# CASE #2
CFLAGS2=-O3
# CASE #3
CFLAGS3=-O3 -DBOOST_SP_DISABLE_THREADS
LDFLAGS=
shared1: shared1.o
g++ $(LDFLAGS) -o shared1 shared1.o
shared_copy1: shared_copy1.o
g++ $(LDFLAGS) -o shared_copy1 shared_copy1.o
plain1: plain1.o
g++ $(LDFLAGS) -o plain1 plain1.o
shared1.o: shared.cpp
g++ $(CFLAGS1) -c -o shared1.o -DSHARED_PTR shared.cpp
shared_copy1.o: shared.cpp
g++ $(CFLAGS1) -c -o shared_copy1.o -DSHARED_PTR_COPY shared.cpp
plain1.o: shared.cpp
g++ $(CFLAGS1) -c -o plain1.o -DPLAIN shared.cpp
shared2: shared2.o
g++ $(LDFLAGS) -o shared2 shared2.o
shared_copy2: shared_copy2.o
g++ $(LDFLAGS) -o shared_copy2 shared_copy2.o
plain2: plain2.o
g++ $(LDFLAGS) -o plain2 plain2.o
shared2.o: shared.cpp
g++ $(CFLAGS2) -c -o shared2.o -DSHARED_PTR shared.cpp
shared_copy2.o: shared.cpp
g++ $(CFLAGS2) -c -o shared_copy2.o -DSHARED_PTR_COPY shared.cpp
plain2.o: shared.cpp
g++ $(CFLAGS2) -c -o plain2.o -DPLAIN shared.cpp
shared3: shared3.o
g++ $(LDFLAGS) -o shared3 shared3.o
shared_copy3: shared_copy3.o
g++ $(LDFLAGS) -o shared_copy3 shared_copy3.o
plain3: plain3.o
g++ $(LDFLAGS) -o plain3 plain3.o
shared3.o: shared.cpp
g++ $(CFLAGS3) -c -o shared3.o -DSHARED_PTR shared.cpp
shared_copy3.o: shared.cpp
g++ $(CFLAGS3) -c -o shared_copy3.o -DSHARED_PTR_COPY shared.cpp
plain3.o: shared.cpp
g++ $(CFLAGS3) -c -o plain3.o -DPLAIN shared.cpp
test: test1 test2 test3
test1: shared1_test shared1_copy_test plain1_test
shared1_test: shared1
time ./shared1 $(CNT)
shared1_copy_test: shared_copy1
time ./shared_copy1 $(CNT)
plain1_test: plain1
time ./plain1 $(CNT)
test2: shared2_test shared2_copy_test plain2_test
shared2_test: shared2
time ./shared2 $(CNT)
shared2_copy_test: shared_copy2
time ./shared_copy2 $(CNT)
plain2_test: plain2
time ./plain2 $(CNT)
test3: shared3_test shared3_copy_test plain3_test
shared3_test: shared3
time ./shared3 $(CNT)
shared3_copy_test: shared_copy3
time ./shared_copy3 $(CNT)
plain3_test: plain3
time ./plain3 $(CNT)
clean:
rm -rf *.o *.dSYM shared1 shared2 shared3 shared_copy1 shared_copy2 shared_copy3 plain1 plain2 plain3 core* gmon.*
1. 테스트 케이스 1: 컴파일러 옵션으로 아무것도 주지 않을 경우 --> CFLGAS1 사용
2. 테스트 케이스 2: 컴파일러 옵션으로 -O3 를 준 경우 --> CFLAGS2 사용
3. 테스트 케이스 3: 컴파일러 옵션으로 -O3 -DBOOST_SP_DISABLE_THREADS 사용. BOOST_SP_DISABLE_THREADS를 켜면 reference count 값을 변경시킬 때, atomic operation을 사용하지 않게 됩니다.
이렇게 테스트 프로그램을 작성하고, 테스트를 해 봤더니 다음과 같은 결과가 나오더군요.
$ make
$ make test
time ./shared1 1000000000
1000000000
15.66 real 15.52 user 0.04 sys
time ./shared_copy1 1000000000
1000000000
93.33 real 92.58 user 0.24 sys
time ./plain1 1000000000
1000000000
8.54 real 8.48 user 0.02 sys
time ./shared2 1000000000
1000000000
2.81 real 2.79 user 0.00 sys
time ./shared_copy2 1000000000
1000000000
37.86 real 37.62 user 0.08 sys
time ./plain2 1000000000
1000000000
0.66 real 0.66 user 0.00 sys
time ./shared3 1000000000
1000000000
2.84 real 2.79 user 0.00 sys
time ./shared_copy3 1000000000
1000000000
5.74 real 5.70 user 0.01 sys
time ./plain3 1000000000
1000000000
0.66 real 0.66 user 0.00 sys
(위 테스트는 looping overhead, function call overhead 를 정확히 측정하지 않았기 때문에 아주 정확한 수치라고는 주장할 수 없음을 미리 밝힙니다. 정확한 수치를 알아내기 위해서는 좀 더 잘 설계된 테스트 케이스를 사용해야 할 것입니다. 다만 위 테스트 결과를 사용하더라도 이 글에서 주장하고자 하는 바를 위한 충분한 근거가 되리라고 생각합니다.)
우선 테스트 케이스1와 테스트 케이스2, 테스트 케이스 3에서 공히 shared_copy > shared > plain 결과가 나온다는 걸 확인할 수 있습니다. BOOST_SP_DISABLE_THREADS 를 켜게 되면 shared_copy에만 영향을 미친다는 걸 알 수 있습니다. 이건 아까 말씀드린 대로 atomic operation이 disable되기 때문인 것으로 추측됩니다. 이상 결과로 보건대 shared_ptr을 쓰실 때는 다음과 같은 점에 유의하셔야 할 것 같습니다.
1. No free lunch. 좋은 게 있으면 그에 대한 비용이 있기 마련이라는 것이죠. shared_ptr는 일반 포인터에 비해 overhead가 있다는 점을 염두해 두셔야겠습니다.
2. 단순 접근하는 overhead도 2.81/0.66(테스트 케이스 2)으로 상당합니다.
3. 복사 생성자 overhead는 37.86/0.66(테스트 케이스 2)으로 더 크다는 것을 확인할 수 있습니다. BOOST_SP_DISABLE_THREADS를 켜더라도 5.74/0.66으로 여전히 크다는 것을 알 수 있습니다. 따라서 될 수 있으면 복사를 shared_ptr도 복사는 피하셔야 하겠습니다.
제가 테스트한 환경은 다음과 같습니다.
$ uname -a
Darwin MyMac.local 9.7.0 Darwin Kernel Version 9.7.0: Tue Mar 31 22:52:17 PDT 2009; root:xnu-1228.12.14~1/RELEASE_I386 i386
$ g++ --version
i686-apple-darwin9-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5564)
여러분 환경에서는 어떤 결과가 나오는지 실험해 보시고, 트랙백 한 번 남겨 주시면 감사하겠습니다. ^^
소스 코드 첨부합니다(looping overhead를 측정하기 위해 본문 소스에서 약간 더 수정했습니다).
'Programming' 카테고리의 다른 글
Reference counting을 사용하는 프로그래밍 언어 (0) | 2014.05.05 |
---|---|
Programming Is a Dead End Job (0) | 2014.05.04 |
C++ 이야기 세번째: new 와 delete (0) | 2014.05.03 |
(C) struct example (0) | 2014.05.01 |
[PHP] include(), require(), include_once(), require_once() (0) | 2014.04.27 |