함수 객체(Functor)
함수 객체(Functor)는 함수처럼 동작하는 객체로 Function Object라고도 불린다. 함수 객체는 함수 호출 연산자인 ()를 class 또는 struct내부에서 오버로딩 한것이다.
함수 객체의 장점
함수 객체의 장점은 아래의 두가지로 보인다.
- 인라인 함수로 만들 수 있다.
- 상태를 가질 수 있다. 인라인 함수에 대해서는 따로 언급을 하지는 않겠다.
‘상태를 가질 수 있다’는 함수포인터와 함수 객체의 가장 큰 차이점이라고 생각된다. 함수포인터의 함수 실행 결과는 전달받는 파라미터에 의존한다. 반면, 함수 객체는 함수 실행결과가 전달받는 파라미터는 매번 동일하더라도 결과가 다를 수 있다. 바로 함수 객체는 상태를 가질 수 있기 때문이다. 왜 그럴까?
함수 객체는 class 또는 struct의 함수 호출 연산자 ()를 오버로딩 한것이라고 말했다. 함수 객체 자체가 class또는 struct 내부의 오버로딩 함수이기 때문에 멤버 변수를 오버로딩 함수 내에서 쓸 수 있다. 만약 함수 객체가 호출 될때 마다 멤버변수의 값이 변경된다면, 그 함수 결과는 매번 다르게 되는 것이다.
함수 객체는 어디에, 왜 쓰이는가?
우리는 라이브러리를 만들 때 범용성과 효율성을 고려해야 한다. 범용성은 사용성이 용이해야 하며, 효율성은 최적화가 잘 되어있어야 한다. 라이브러리에서 범용성을 높이기위한 한가지 방법으로 함수 포인터를 사용한다. 대표적인 예로 STL의 sort함수에서 함수 포인터를 사용할 수 있다. 하지만 함수 포인터를 사용하게 되면, 함수 포인터를 호출 할때 Jumping(Jmp) 오버헤드가 발생하게 된다.
아래는 함수 포인터를 사용한 코드를 Disassemlby한 결과이다.
함수포인터 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool cmpfunc(int a, int b)
{
return a < b;
}
template<typename T>
bool docomp(int a, int b, T func)
{
return func(a, b);
}
void main()
{
docomp(1, 2, cmpfunc);
}
함수포인터 Disassembly
1
2
3
4
5
6
7
8
9
10
11
??$docomp@P6A_NHH@Z@@YA_NHHP6A_NHH@Z@Z (bool __cdecl docomp<bool (__cdecl*)(int,int)>(int,int,bool (__cdecl*)(int,int))):
0000000000000000: 49 FF E0 jmp r8
?cmpfunc@@YA_NHH@Z (bool __cdecl cmpfunc(int,int)):
0000000000000000: 3B CA cmp ecx,edx
0000000000000002: 0F 9C C0 setl al
0000000000000005: C3 ret
main:
0000000000000000: 33 C0 xor eax,eax
0000000000000002: C3 ret
위 코드에서 보는것과 같이 docomp함수에서 func함수를 호출 할 때, Jumping(jmp) 오버헤드가 발생되는 것을 확인 할 수 있다. 만약 docomp 함수를 인라인 함수로 만들면 docomp 함수 내용이 caller 함수 본체로 대체되기 때문에 Jumping 오버헤드를 피할 수 있다.
아래는 docomp 함수를 inline 함수로 변경하고 disassembly한 결과이다. Jumping 오버헤드를 피할 수 있는 것이 확인된다.
inline 함수 구현
1
2
3
4
5
6
7
8
9
...
template<typename T>
inline bool docomp(int a, int b, T func)
{
return func(a, b);
}
...
inline 함수 Disassembly
1
2
3
4
5
6
7
8
?cmpfunc@@YA_NHH@Z (bool __cdecl cmpfunc(int,int)):
0000000000000000: 3B CA cmp ecx,edx
0000000000000002: 0F 9C C0 setl al
0000000000000005: C3 ret
main:
0000000000000000: 33 C0 xor eax,eax
0000000000000002: C3 ret
하지만 우리가 주로 사용하는 STL의 함수 중 함수포인터를 인자로 받는 함수의 대부분 inline 함수로 되어있지 않다. 이럴 경우에 함수 포인터를 사용하면 오버헤드를 피할수가 없는데, 이때 함수 객체를 사용하면 오버헤드를 피할 수 있다. STL의 함수들은 대부분 함수 포인터와 함수 객체를 둘 다 사용할 수 있다. (다 확인 안해봐서 아닐 수도 있다.)
그러면 STL의 sort함수에 함수포인터와 함수객체를 사용해서 sorting을 해보고 실행 시간을 비교해 보자.
함수포인터와 함수객체 비교
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <algorithm>
#include <Windows.h>
using namespace std;
class cmpclass
{
public:
inline bool operator()(int a, int b)
{
return a < b;
}
};
bool cmpfunc(int a, int b)
{
return a < b;
}
int main()
{
const int SIZE = 50000000;
int *num1 = new int[SIZE];
int *num2 = new int[SIZE];
for(int i = 0; i < SIZE; i++)
{
num1[i] = rand();
num2[i] = num1[i];
}
cmpclass cmpc;
int time = GetTickCount64();
sort(num1, num1 + SIZE, cmpc); // Use Functor.
time = GetTickCount64() - time;
cout << "Functor : " << time << endl;
time = GetTickCount64();
sort(num2, num2 + SIZE, cmpfunc); // Use function pointer.
time = GetTickCount64() - time;
cout << "Function Pointer : " << time << endl;
return 0;
}
함수포인터와 함수객체 비교 결과
1
2
Functor : 3526
Function Pointer : 5116
위 결과값을 보면 함수 포인터보다 함수 객체를 사용했을 때 더 빠른 sort결과를 얻는 것을 확인 할 수 있다. 바로 함수 객체는 오버헤드가 발생되지 않기 때문이다.
마지막으로 함수 객체를 사용 하였을 때, Jumping 오버헤드가 발생되지 않는지 확인해 보자. 아래는 함수 객체를 사용한 코드를 Disassembly한 결과 이다.
함수객체 Jumping오버헤드 확인
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class cmpclass
{
public:
inline bool operator()(int a, int b)
{
return a < b;
}
};
template<typename T>
bool docomp(int a, int b, T func)
{
return func(a, b);
}
void main()
{
cmpclass cmpc;
docomp(1, 2, cmpc);
}
Disassembly
1
2
3
4
5
6
7
8
??$docomp@Vcmpclass@@@@YA_NHHVcmpclass@@@Z (bool __cdecl docomp<class cmpclass>(int,int,class cmpclass)):
0000000000000000: 3B CA cmp ecx,edx
0000000000000002: 0F 9C C0 setl al
0000000000000005: C3 ret
main:
0000000000000000: 33 C0 xor eax,eax
0000000000000002: C3 ret
함수객체는 Jumping 오버헤드가 발생되지 않는 것으로 보인다.