Быстрый способ подсчета количества инструкций, выполненных в программе на Си
Существует ли простой способ быстро подсчитать количество выполненных инструкций (x86-инструкций - сколько и сколько каждой) при выполнении программы на C?
я использую gcc version 4.7.1 (GCC)
на x86_64 GNU/Linux
машина.
2 ответа
Linux
perf_event_open
системный вызов с
config = PERF_COUNT_HW_INSTRUCTIONS
Этот системный вызов Linux представляет собой кросс-архитектурную оболочку для событий производительности.
Вот пример, адаптированный из
man perf_event_open
страница:
perf_event_open.c
#include <asm/unistd.h>
#include <linux/perf_event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <inttypes.h>
static long
perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
int cpu, int group_fd, unsigned long flags)
{
int ret;
ret = syscall(__NR_perf_event_open, hw_event, pid, cpu,
group_fd, flags);
return ret;
}
int
main(int argc, char **argv)
{
struct perf_event_attr pe;
long long count;
int fd;
uint64_t n;
if (argc > 1) {
n = strtoll(argv[1], NULL, 0);
} else {
n = 10000;
}
memset(&pe, 0, sizeof(struct perf_event_attr));
pe.type = PERF_TYPE_HARDWARE;
pe.size = sizeof(struct perf_event_attr);
pe.config = PERF_COUNT_HW_INSTRUCTIONS;
pe.disabled = 1;
pe.exclude_kernel = 1;
// Don't count hypervisor events.
pe.exclude_hv = 1;
fd = perf_event_open(&pe, 0, -1, -1, 0);
if (fd == -1) {
fprintf(stderr, "Error opening leader %llx\n", pe.config);
exit(EXIT_FAILURE);
}
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
/* Loop n times, should be good enough for -O0. */
__asm__ (
"1:;\n"
"sub $1, %[n];\n"
"jne 1b;\n"
: [n] "+r" (n)
:
:
);
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("Used %lld instructions\n", count);
close(fd);
}
Скомпилируйте и запустите:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o perf_event_open.out perf_event_open.c
./perf_event_open.out
Вывод:
Used 20016 instructions
Итак, мы видим, что результат довольно близок к ожидаемому значению 20000: 10k * две инструкции на цикл в
__asm__
блок (sub
,
jne
).
Если я изменю аргумент, даже до низких значений, таких как
100
:
./perf_event_open.out 100
это дает:
Used 216 instructions
поддерживая эту постоянную + 16 инструкций, поэтому кажется, что точность довольно высока, эти 16 должны быть просто
ioctl
инструкции по установке после нашего маленького цикла.
Теперь вас также могут заинтересовать:
- предотвратить изменение порядка системных вызовов: Обеспечение порядка операторов в C++
- предотвратить оптимизацию цикла тестирования: как предотвратить оптимизацию GCC цикла ожидания занятости?
Другие интересные события, которые можно измерить с помощью этого системного вызова:
- количество циклов: как получить количество циклов процессора в x86_64 из C++?
Протестировано на Ubuntu 20.04 amd64, GCC 9.3.0, ядре Linux 5.4.0, процессоре Intel Core i7-7820HQ.
Вы можете использовать инструмент бинарных инструментов ' Pin' от Intel. Я бы не использовал симулятор для такой тривиальной задачи (симуляторы часто бывают очень медленными). Pin делает большую часть того, что вы можете делать с симулятором без предварительной модификации двоичного файла и с нормальным исполнением, например, со скоростью (в зависимости от используемого вами инструмента pin).
Для подсчета количества инструкций с помощью Pin:
- Загрузите последнюю версию (или 3.10, если этот ответ устареет) отсюда.
- Распакуйте все и перейдите в каталог:
cd pin-root/source/tools/ManualExample/
- Сделайте все инструменты в каталоге:
make all
- Запустите инструмент inscount0.so с помощью команды:
../../../pin -t obj-intel64/inscount0.so -- your-binary-here
- Получить количество команд в файле
inscount.out
,cat inscount.out
,
Вывод будет что-то вроде:
➜ ../../../pin -t obj-intel64/inscount0.so -- /bin/ls
buffer_linux.cpp itrace.cpp
buffer_windows.cpp little_malloc.c
countreps.cpp makefile
detach.cpp makefile.rules
divide_by_zero_unix.c malloc_mt.cpp
isampling.cpp w_malloctrace.cpp
➜ cat inscount.out
Count 716372
Вероятно, дубликат этого вопроса
Я говорю, вероятно, потому что вы просили инструкции ассемблера, но этот вопрос касается профилирования кода на уровне C.
Однако мой вопрос к вам: почему вы хотите профилировать фактические машинные инструкции? Как первая проблема, это будет отличаться между различными компиляторами и их настройками оптимизации. Как более практичный вопрос, что вы могли бы сделать с этой информацией? Если вы находитесь в процессе поиска / оптимизации узких мест, профилировщик кода - это то, что вы ищете.
Я мог бы пропустить что-то важное здесь, хотя.
Вы можете легко посчитать количество выполненных команд, используя аппаратный счетчик производительности (HPC). Для доступа к HPC вам необходим интерфейс к нему. Я рекомендовал вам использовать API производительности PAPI.
Хотя это и не "быстро" в зависимости от программы, на этот вопрос, возможно, ответили. Здесь Марк Плотник предлагает использовать gdb
чтобы посмотреть изменения регистра счетчика вашей программы:
# instructioncount.gdb
set pagination off
set $count=0
while ($pc != 0xyourstoppingaddress)
stepi
set $count++
end
print $count
quit
Затем начните gdb
в вашей программе:
gdb --batch --command instructioncount.gdb --args ./yourexecutable with its arguments
Чтобы получить конечный адрес 0xyourstoppingaddress
Вы можете использовать следующий скрипт:
# stopaddress.gdb
break main
run
info frame
quit
который ставит точку останова на функцию main
и дает:
$ gdb --batch --command stopaddress.gdb --args ./yourexecutable with its arguments
...
Stack level 0, frame at 0x7fffffffdf70:
rip = 0x40089d in main (main_aes.c:33); saved rip 0x7ffff7a66d20
source language c.
Arglist at 0x7fffffffdf60, args: argc=3, argv=0x7fffffffe048
...
Здесь важно saved rip 0x7ffff7a66d20
часть. На моем процессоре rip
указатель инструкции, а saved rip
это "обратный адрес", как указано pepero в этом ответе.
Так что в этом случае адрес остановки 0x7ffff7a66d20
, который является обратным адресом main
функция. То есть конец выполнения программы.