Почему это медленнее ??
Вы видите увеличение на 710 нс. Для часов 16 МГц это время составляет 11 тиков.
Это не очень справедливо сказать 4X, потому что увеличение времени является постоянным накладным для функции указателя. В вашем случае тело функции крошечное, поэтому накладные расходы относительно велики. Но если бы у вас был случай, когда функция была большой и заняла 1 мс для выполнения, увеличение времени все равно было бы 710 нс, и вы спрашивали, почему указатель функции занимает более 0,07%?
Чтобы понять, почему один подход быстрее, чем другой, вам нужно получить код ассемблера. Используя инструменты построения, такие как Eclipse, вы можете получить список ассемблеров из компилятора GCC, добавив параметры командной строки, недоступные с помощью Arduino IDE. Это бесценно, чтобы выяснить, что происходит.
Вот часть ассемблере листинг показывает, что вы думаете, что происходит:
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, который содержит описания каждой инструкции и количество тактов, которые они принимают. Простой_call(), возможно, то, что вы ожидаете, и занимает 4 тика. Простой() говорит:
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, которая меняет все.
С оптимизаций, функция получает тело сжато почти ничего:
void simple_call(){ PORTB |= _BV(1); }
264: 29 9a sbi 0x05, 1 ; 5
266: 08 95 ret
То есть 2 + 4 тиков. Гуру-компилятор закодировал компилятор, чтобы выяснить гораздо лучший способ выполнить одну строку C++. Но ждать больше. Когда вы «вызываете» свою функцию, компилятор говорит «зачем это? Это всего лишь одна инструкция ассемблера». Компилятор решает ваш вызов бессмысленно и ставит инструкции инлайн:
void simple_call(){ PORTB |= _BV(1); }
2d6: 29 9a sbi 0x05, 1 ; 5
Но с оптимизациями, вызов указатель функции остается вызов:
simple();
2d8: e0 91 0a 02 lds r30, 0x020A
2dc: f0 91 0b 02 lds r31, 0x020B
2e0: 19 95 eicall
Так что давайте посмотрим, если математика складывает. С встроенным, «звонок» - 3 тика. Косвенный вызов равен 8 + 6 = 14. Разница - 11 тиков! (Могу добавить!)
Так что это ** почему *.
Как ускорить его?
Вам не нужно: Только 4 тиков больше, чтобы сделать вызов указателя функции. За исключением самых тривиальных функций, это не имеет значения.
Вы не можете: Даже если вы попытаетесь встроить функции, вам все равно нужна условная ветвь. Букет нагрузок, сравнения и условных прыжков займет больше, чем косвенный вызов. Другими словами, указатель функции - лучший метод ветвления, чем любой условный.