Я пытаюсь посчитать количество операций с плавающей запятой в одной из моих программ, и я думаю, perf
может быть инструмент, который я ищу (есть ли альтернативы?), но у меня есть проблемы с ограничением его до определенной функции / блока кода. Давайте возьмем следующий пример:
#include <complex>
#include <cstdlib>
#include <iostream>
#include <type_traits>
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type myrand()
{
return static_cast <T> (std::rand()) / static_cast <T> (RAND_MAX);
}
template <typename T>
typename std::enable_if<!std::is_floating_point<T>::value, std::complex<typename T::value_type>>::type myrand()
{
typedef typename T::value_type S;
return std::complex<S>(
static_cast <S> (std::rand()) / static_cast <S> (RAND_MAX),
static_cast <S> (std::rand()) / static_cast <S> (RAND_MAX)
);
}
int main()
{
auto const a = myrand<Type>();
auto const b = myrand<Type>();
// count here
auto const c = a * b;
// stop counting here
// prevent compiler from optimizing away c
std::cout << c << "\n";
return 0;
}
myrand()
Функция просто возвращает случайное число, если тип T является комплексным, то случайное комплексное число. Я не жестко запрограммировал дубликаты в программе, потому что они были бы оптимизированы компилятором.
Вы можете скомпилировать файл (давайте назовем его bench.cpp
) с c++ -std=c++0x -DType=double bench.cpp
,
Теперь я хотел бы подсчитать количество операций с плавающей запятой, которые можно выполнить на моем процессоре (архитектура Nehalem, x86_64, где с плавающей запятой выполняется с помощью скалярного SSE) с событием r8010
(см. руководство Intel 3B, раздел 19.5). Это может быть сделано с
perf stat -e r8010 ./a.out
и работает как положено; однако он считает общее количество мопов (есть ли таблица, показывающая, сколько моп movsd
например является?) а также Меня интересует только число для умножения (см. пример выше).
Как это может быть сделано?
Я наконец нашел способ сделать это, хотя не используя perf
но вместо соответствующего перф API. Сначала нужно определить perf_event_open
функция, которая на самом деле является системным вызовом:
#include <cstdlib> // stdlib.h for C
#include <cstdio> // stdio.h for C
#include <cstring> // string.h for C
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/perf_event.h>
#include <asm/unistd.h>
long perf_event_open(
perf_event_attr* hw_event,
pid_t pid,
int cpu,
int group_fd,
unsigned long flags
) {
int ret = syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
return ret;
}
Далее выбираются события, которые нужно посчитать:
perf_event_attr attr;
// select what we want to count
std::memset(&attr, 0, sizeof(perf_event_attr));
attr.size = sizeof(perf_event_attr);
attr.type = PERF_TYPE_HARDWARE;
attr.config = PERF_COUNT_HW_INSTRUCTIONS;
attr.disabled = 1;
attr.exclude_kernel = 1; // do not count the instruction the kernel executes
attr.exclude_hv = 1;
// open a file descriptor
int fd = perf_event_open(&attr, 0, -1, -1, 0);
if (fd == -1)
{
// handle error
}
В этом случае я хочу просто посчитать количество инструкций. Инструкции с плавающей точкой можно посчитать на моем процессоре (Nehalem), заменив соответствующие строки на
attr.type = PERF_TYPE_RAW;
attr.config = 0x8010; // Event Number = 10H, Umask Value = 80H
Установив тип в RAW, можно практически посчитать каждое событие, которое предлагает процессор; число 0x8010
указывает, какой. Обратите внимание, что это число сильно зависит от процессора! Правильные числа можно найти в Руководстве Intel 3B, часть 2, глава 19, выбрав правильный подраздел.
Затем можно измерить код, заключив его в
// reset and enable the counter
ioctl(fd, PERF_EVENT_IOC_RESET, 0);
ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
// perform computation that should be measured here
// disable and read out the counter
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
long long count;
read(fd, &count, sizeof(long long));
// count now has the (approximated) result
// close the file descriptor
close(fd);