왜 느린가 ??
시간이 710ns 증가합니다. 16 MHz 클록의 경우, 그 시간은 11 틱이다.
시간 증가가 함수 포인터에 대한 일정한 오버 헤드이므로 4X라고 말하는 것이 맞지 않습니다. 귀하의 경우 함수 본문은 작기 때문에 오버 헤드가 상대적으로 큽니다. 그러나 함수가 크고 실행에 1ms가 걸리는 경우가 있습니다. 시간 증가는 여전히 710ns이고 함수 포인터가 0.07 % 더 오래 걸리는 이유는 무엇입니까?
하나의 접근법이 다른 접근법보다 빠른 이유를 보려면 어셈블러 코드를 사용해야합니다. Eclipse와 같은 빌드 도구를 사용하면 Arduino IDE와 함께 사용할 수없는 명령 행 옵션을 추가하여 GCC 컴파일러에서 어셈블러 목록을 얻을 수 있습니다. 이것은 무슨 일이 일어나고 있는지 알아내는 데 매우 귀중합니다. 여기
당신이 무슨 생각을 보여주는 목록 어셈블러의 섹션 것은 일어나고 :
simple_call();
308: 0e 94 32 01 call 0x264 ; 0x264 <_Z11simple_callv>
simple();
30c: e0 91 0a 02 lds r30, 0x020A
310: f0 91 0b 02 lds r31, 0x020B
314: 19 95 eicall
이 명부는 컴파일러에 의해 생성 된 소스 코드와 어셈블러를 보여줍니다. 그것을 이해하고 타이밍을 파악하려면 Atmel AVR instruction reference이 필요합니다. 여기에는 모든 명령어에 대한 설명과 시계 걸린 횟수가 나와 있습니다. simple_call()은 아마 네가 예상하고 4 tick을 취한다. 간단한()는 말한다 :
LDS = load address byte - 2 ticks
LDS = load address byte - 2 ticks
EICALL = indirect call to address loaded - 4 ticks
들은 모두 전화 기능 simple_call() :
이
void simple_call(){ PORTB |= _BV(1); }
264: df 93 push r29
266: cf 93 push r28
268: cd b7 in r28, 0x3d ; 61
26a: de b7 in r29, 0x3e ; 62
26c: a5 e2 ldi r26, 0x25 ; 37
26e: b0 e0 ldi r27, 0x00 ; 0
270: e5 e2 ldi r30, 0x25 ; 37
272: f0 e0 ldi r31, 0x00 ; 0
274: 80 81 ld r24, Z
276: 82 60 ori r24, 0x02 ; 2
278: 8c 93 st X, r24
27a: cf 91 pop r28
27c: df 91 pop r29
27e: 08 95 ret
그래서 함수 포인터는 단지 4 개 틱을하고 함수의 모든 지시에 비해 작아야한다 방법.에 무슨 일이 일어나고 있는지 당신이 생각하는
위, 나는 해야 및 말했다. 나는 약간의 거짓말을했다 : 위의 어셈블러는 최적화가 없다.
-O3을 사용하면 모든 것이 변경됩니다.최적화 기능으로
는 함수 본문이 거의 아무것도 숙청됩니다 : 2 + 4 틱입니다 void simple_call(){ PORTB |= _BV(1); }
264: 29 9a sbi 0x05, 1 ; 5
266: 08 95 ret
. 컴파일러 전문가는 컴파일러를 코딩하여 C++의 한 줄을 실행하는 훨씬 더 좋은 방법을 찾아 냈습니다. 그러나 더 기다려라. 함수를 "호출"할 때 컴파일러는 "왜 그렇게합니까? 그것은 단지 하나의 어셈블러 명령어입니다"라고 말합니다. 컴파일러는 전화가 무의미 결정하고 지시 인라인을두고 :
simple();
2d8: e0 91 0a 02 lds r30, 0x020A
2dc: f0 91 0b 02 lds r31, 0x020B
2e0: 19 95 eicall
그래서 수학가 추가하는 경우 볼 수 있습니다 :
void simple_call(){ PORTB |= _BV(1); }
2d6: 29 9a sbi 0x05, 1 ; 5
하지만 최적화와
는 함수 포인터를 호출 통화 남아있다. 인라인으로 "통화"는 3 틱입니다. 간접 호출은 8 + 6 = 14입니다. 차이는 11 틱입니다! (추가 할 수 있습니다!) 그래서 ** why *입니다.
어떻게해야합니까?
당신은 할 필요가 없습니다 : 그것은 단지 4 시계가 함수 포인터 통화를 할 더 틱입니다. 가장 사소한 기능을 제외하면 중요하지 않습니다.
: 함수를 인라인하려고해도 여전히 조건부 분기가 필요합니다. 간접 호출보다로드, 비교 및 조건부 점프가 많이 필요합니다. 즉, 함수 포인터는 모든 조건부보다 더 나은 분기 방법입니다.