Следующий фрагмент кода создает функцию (забаву) только с одной инструкцией RET.
Цикл повторно вызывает функцию и перезаписывает содержимое инструкции RET после возврата.
#include <sys/mman.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>
typedef void (*foo)();
#define RET (0xC3)
int main(){
// Allocate an executable page
char * ins = (char *) mmap(0, 4096, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, 0, 0);
// Just write a RET instruction
*ins = RET;
// make fun point to the function with just RET instruction
foo fun = (foo)(ins);
// Repeat 0xfffffff times
for(long i = 0; i < 0xfffffff; i++){
fun();
*ins = RET;
}
return 0;
}
Linux perf на X86 Broadwell имеет следующую статистику icache и iTLB:
perf stat -e L1-icache-load-misses -e iTLB-load-misses ./a.out
Статистика счетчика производительности для './a.out':
805,516,067 L1-icache-load-misses
4,857 iTLB-load-misses
32.052301220 seconds time elapsed
Теперь посмотрите на тот же код, не переписывая инструкцию RET.
#include <sys/mman.h>
#include<stdlib.h>
#include<unistd.h>
#include <string.h>
typedef void (*foo)();
#define RET (0xC3)
int main(){
// Allocate an executable page
char * ins = (char *) mmap(0, 4096, PROT_EXEC|PROT_READ|PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, 0, 0);
// Just write a RET instruction
*ins = RET;
// make fun point to the function with just RET instruction
foo fun = (foo)(ins);
// Repeat 0xfffffff times
for(long i = 0; i < 0xfffffff; i++){
fun();
// Commented *ins = RET;
}
return 0;
}
А вот статистика перфорации на той же машине.
perf stat -e L1-icache-load-misses -e iTLB-load-misses ./a.out
Статистика счетчика производительности для './a.out':
11,738 L1-icache-load-misses
425 iTLB-load-misses
0.773433500 seconds time elapsed
Обратите внимание, что перезапись инструкции приводит к увеличению L1-icache-load-misses с 11 738 до 80 5 51 0 067 - многократный рост.
Также обратите внимание, что количество пропущенных загрузок iTLB увеличилось с 425 до 4857 - довольно много, но меньше по сравнению с пропусками L1-icache-load-misses.
Продолжительность увеличивается с 0,773433500 секунд до 32,052301220 секунд - рост в 41 раз!
Неясно, почему ЦП должен вызывать промахи в i-cache, если занимаемая площадь слишком мала. Единственная разница в двух примерах заключается в том, что инструкция модифицирована. Конечно, L1 iCache и dCache разделены. Разве нет способа установить код в iCache, чтобы избежать ошибок в i-cache кеша?
Кроме того, почему 10-кратный рост числа пропусков iTLB?