C++의 STL 에서는 여러가지 알고리즘을 제공한다.
그중 "임의의" 동작을 할 수 있는 알고리즘들은 그 "동작" 의 대상이 되는 인자를 받도록 되어 있는데,
그 "동작"의 조건은 ()연산자로 호출이 가능할것! 이다.
그 동작을 만족시키는것은 C에서는 함수가 있다.
또 C++에서는 STL 과 함께 소개된 함수객체(Functor)가 있으며, STL 초기부터 제공된 bind1st, bind2nd가 있고, boost에서 제공되는 mem_fn 도 있으며, 이 모든것을 통합한 범용 bind(C++11 or boost에 포함)도 존재한다.
그리고 최신의 C++11 표준에 포함된 lambda도 있고, boost의 lambda도 있지만, 이쪽은 C++11 표준에 lambda가 포함 된 이상 더이상 쓸 일은 없을테니 이번 실험에선 제외한다.
실험순서는 전역함수, bind된 전역함수, 함수객체, 람다, bind된 람다, auto를 이용해 bind된 람다 의 순이다.
실험은 Visual C++ 2010 Sp1, x86 Debug Build에서 테스트 하엿다. Release Mode 도 특성은 동일하다.
궂이 Debug Build로 테스트 한 이유는 Release모드에서 자꾸 전역함수를 inline화 시키는 바람에 =_=;
코드 나간다.
#include <cstdlib> #include <cstdio> #include <vector> #include <random> #include <cstdint> #include <algorithm> #include <functional> #include <windows.h> using namespace std; __int64 g_NumberSum; void GetSumFunction(int Num) { g_NumberSum = g_NumberSum + Num; } struct GetSumFunctor { void operator()(int Num) { g_NumberSum = g_NumberSum + Num; } }; void wmain() { const int NUMBER_COUNT = 100000000; mt19937_64 RandomEngine; uniform_int <> RandomRange(0, INT32_MAX); RandomEngine.seed(GetTickCount()); vector<int> RandumNumbers; RandumNumbers.reserve(NUMBER_COUNT); for (int i = 0; i < NUMBER_COUNT; ++i) { RandumNumbers.push_back(RandomRange(RandomEngine)); } DWORD Tick; //function g_NumberSum = 0; Tick = GetTickCount(); for_each(RandumNumbers.begin(), RandumNumbers.end(), GetSumFunction); printf("Global Function :: Sum : %I64d, Time : %dms\n", g_NumberSum, GetTickCount() - Tick); //bind function g_NumberSum = 0; function<void(int)> FunctionObject = bind(&GetSumFunction, tr1::placeholders::_1); Tick = GetTickCount(); for_each(RandumNumbers.begin(), RandumNumbers.end(), FunctionObject); printf("Bind Function :: Sum : %I64d, Time : %dms\n", g_NumberSum, GetTickCount() - Tick); //functor g_NumberSum = 0; Tick = GetTickCount(); for_each(RandumNumbers.begin(), RandumNumbers.end(), GetSumFunctor()); printf("Functor :: Sum : %I64d, Time : %dms\n", g_NumberSum, GetTickCount() - Tick); //C++ 11 Lambda g_NumberSum = 0; Tick = GetTickCount(); for_each(RandumNumbers.begin(), RandumNumbers.end(), [](int Num) { g_NumberSum = g_NumberSum + Num; }); printf("C++11 Lambda :: Sum : %I64d, Time : %dms\n", g_NumberSum, GetTickCount() - Tick); //C++11 Bind Lambda g_NumberSum = 0; Tick = GetTickCount(); function<void(int)> BindLambda = [](int Num) { g_NumberSum = g_NumberSum + Num; }; for_each(RandumNumbers.begin(), RandumNumbers.end(), BindLambda); printf("Bind Lambda :: Sum : %I64d, Time : %dms\n", g_NumberSum, GetTickCount() - Tick); //C++11 Auto bind Lambda g_NumberSum = 0; Tick = GetTickCount(); auto AutoBindLambda = [](int Num) { g_NumberSum = g_NumberSum + Num; }; for_each(RandumNumbers.begin(), RandumNumbers.end(), AutoBindLambda); printf("Auto Bind Lambda:: Sum : %I64d, Time : %dms\n", g_NumberSum, GetTickCount() - Tick); } |
아래는 결과이다.
결과를 보면 lambda 가 가장 빠른 것을 볼 수 있다. 길게 나불대지 않고 성능 이슈와 원인을 아래 간략히 적어본다.
1. 전역함수의 경우 함수 Call 오버헤드에 의해 속도가 조금 감소하엿다.
2. lambda, functor 는 사실상 오차범위 내의 차이라고 보인다.
3. bind lambda 의 경우는 std::fuction 객체가 함수호출 정보를 RTTI에 의존하는 특징때문에 속도를 왕창 까먹게 된다.
4. bind 된 전역함수의 경우는 RTTI의 오버헤드 + 함수호출로 인해 가장 느려진다.
5. auto를 사용해 변수에 저장한 lambda 는 std::fuction객체에 저장 하는 것이 아니라 일종의 inline 함수처럼 구현해준다. 그래서 오버헤드가 전혀 없이 빠른 속도를 보여준다. 즉 같은 함수객체를 2번이상 이용 할 경우 코드를 Copy&Paste 하지 않아도 auto를 이용하면 성능과 중복방지 두마리 코드를 다 잡을 수 있다.