Lets run some benchmarks on the different ways to call functions. Today’s contestants are C Functions, C Function Pointers, C Blocks, C++ Virtual Methods, and Objective-C Methods.
The Code:
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
#import <CoreFoundation/CoreFoundation.h> #import <stdint.h> static uint64_t s_foo = 0; #define FUNC_BODY() s_foo += 1; static void cfunction(void* context) { FUNC_BODY(); } class Foo { public: virtual void doit() =0; }; class Bar : public Foo { public: virtual void doit(); }; void Bar::doit() { FUNC_BODY(); } @interface Baz : NSObject @end @implementation Baz -(void)doit { FUNC_BODY(); } @end typedef void (*VoidFunc)(void* context); typedef void (^VoidBlock)(void); template<class Func> void time(const char* name, uint64_t numTimes, const Func& func) { CFAbsoluteTime startTime, totalTime; startTime = CFAbsoluteTimeGetCurrent(); func(); totalTime = CFAbsoluteTimeGetCurrent() - startTime; printf("%s - useconds/call:%f\n", name, (totalTime * 1000000) / numTimes); } int main(int argc, char *argv[]) { uint64_t numTimes = 10000000LL; void* context = (void*)0xfeedface; CFAbsoluteTime startTime, totalTime; time("c function", numTimes, [&] { asm("cfunction:"); for(uint64_t i=0; i<numTimes; ++i) { cfunction(context); } }); VoidFunc voidFunc = cfunction; time("c function pointer", numTimes, [&] { asm("cfunction_pointer:"); for(uint64_t i=0; i<numTimes; ++i) { voidFunc(context); } }); VoidBlock voidBlock = ^() { s_foo += 1; }; time("c block", numTimes, [&] { asm("c_block:"); for(uint64_t i=0; i<numTimes; ++i) { voidBlock(); } }); Foo* foo = new Bar(); time("c++ virtual method", numTimes, [&] { asm("cppvirtual:"); for(uint64_t i=0; i<numTimes; ++i) { foo->doit(); } }); Baz* baz = [[Baz alloc] init]; time("objc method", numTimes, [&] { asm("objcmethod:"); for(uint64_t i=0; i<numTimes; ++i) { [baz doit]; } }); return 0; } |
The System:
Mac OS X 10.9.3 : LLVM 3.4
Generated Assembly:
c function:
1 2 3 4 |
mov rax, qword ptr [rbp - 24] ## 8-byte Reload mov rcx, qword ptr [rax + 8] mov rdi, qword ptr [rcx] call __ZL9cfunctionPv |
c function pointer:
1 2 3 4 5 6 |
mov rax, qword ptr [rbp - 24] ## 8-byte Reload mov rcx, qword ptr [rax + 8] mov rcx, qword ptr [rcx] mov rdx, qword ptr [rax + 16] mov rdi, qword ptr [rdx] call rcx |
c block:
1 2 3 4 5 6 |
mov rax, qword ptr [rbp - 24] ## 8-byte Reload mov rcx, qword ptr [rax + 8] mov rcx, qword ptr [rcx] mov rdx, rcx mov rdi, rdx call qword ptr [rcx + 16] |
c++ virtual method:
1 2 3 4 5 6 |
mov rax, qword ptr [rbp - 24] ## 8-byte Reload mov rcx, qword ptr [rax + 8] mov rcx, qword ptr [rcx] mov rdx, qword ptr [rcx] mov rdi, rcx call qword ptr [rdx] |
objc method:
1 2 3 4 5 6 |
mov rax, qword ptr [rbp - 24] ## 8-byte Reload mov rcx, qword ptr [rax + 8] mov rcx, qword ptr [rcx] mov rsi, qword ptr [rip + L_OBJC_SELECTOR_REFERENCES_10] mov rdi, rcx call _objc_msgSend |
The Results:
c function – useconds/call:0.004192
c function pointer – useconds/call:0.004705
c block – useconds/call:0.004320
c++ virtual method – useconds/call:0.004731
objc method – useconds/call:0.006549
Conclusions:
Objective-C is slow because it is performing more code inside of objc_msgSend but the others are roughly performing the same number of operations. The only differences are the number of reads from memory they have.