测量我的 C 程序循环次数的最可靠方法是什么?
What is the most reliable way to measure the number of cycles of my program in C?
我熟悉两种方法,但它们都有其局限性。
第一个是使用指令RDTSC
。但是,问题是它不单独计算我的程序的周期数,因此对并发进程引起的噪声很敏感。
第二种选择是使用clock
库函数。我认为这种方法是可靠的,因为我希望它只计算我的程序的周期数(我打算实现的)。然而,事实证明,在我的例子中,它测量了经过的时间,然后将它乘以 CLOCKS_PER_SEC
。这不仅不可靠,而且是错误的,因为 CLOCKS_PER_SEC
设置为 1,000,000
,这与我处理器的实际频率不符。
鉴于所提出方法的局限性,是否有更好、更可靠的替代方法来产生一致的结果?
RDTSC 是计算程序执行周期的最准确方法。如果您希望在线程被抢占的情况下随时间尺度测量执行性能,那么使用分析器(例如 VTune)可能会更好。
与几乎没有开销的 RDTSC 相比,CLOCKS_PER_SECOND/clock() 几乎是一种非常糟糕的(低性能)获取时间的方式。
如果您有 RDTSC 的具体问题,我可以提供帮助。
回复:评论
Intel Performance Counter Monitor:这个主要是用来测量处理器以外的指标,比如Memory bandwidth, power usage, PCIe utilization。它也恰好测量 CPU 频率,但它通常对处理器绑定的应用程序性能没有用。
RDTSC 可移植性:RDTSC 是所有现代英特尔 CPU 支持的英特尔 CPU 指令。在现代 CPU 上,它基于 CPU 的非核心频率并且在 CPU 核心之间有些相似,但如果您的应用程序经常被抢占到不同的核心(尤其是不同的插座)。如果是这样的话,你真的想看看分析器。
乱序执行: 是的,事情会乱序执行,所以这会稍微影响性能,但执行指令仍然需要时间,RDTSC 是最好的方式测量那个时间。它在同一内核上执行非 IO 绑定指令的正常用例中表现出色,而这正是它的用途所在。如果您有更复杂的用例,您确实应该使用不同的工具,但这并不能否定 rdtsc() 在分析程序执行方面非常有用。
这里很大程度上取决于您尝试测量的时间量。
如果正确使用,RDTSC 可以(几乎)100% 可靠。然而,它主要用于测量真正微观的代码片段。如果你想测量两个序列,比如说,每个序列大约几十条指令,可能没有其他方法可以完成这项工作。
虽然正确使用它有点挑战。一般来说,要获得良好的测量结果,您至少需要执行以下操作:
- 将代码设置为仅在一个特定核心上 运行。
- 将代码设置为以最高优先级执行,这样就没有任何东西可以抢占它。
- 自由使用 CPUID 以确保在需要时进行序列化。
另一方面,如果您要测量的时间从 100 毫秒开始,那么 RDTSC
毫无意义。这就像试图用千分尺测量城市之间的距离。为此,通常最好确保所讨论的代码(至少)花费一秒钟左右的时间。 clock
不是特别精确,但在这个一般顺序的一段时间内,它可能只精确到 10 毫秒左右这一事实或多或少是无关紧要的。
Linux perf_event_open
系统调用 config = PERF_COUNT_HW_CPU_CYCLES
这个系统调用有明确的控制:
- 处理 PID 选择
- 是否考虑kernel/hypervisor说明
因此,即使多个进程同时 运行,它也会正确计算周期数。
有关详细信息,请参阅此答案:How to get the CPU cycle count in x86_64 from C++?
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_CPU_CYCLES;
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 , %[n];\n"
"jne 1b;\n"
: [n] "+r" (n)
:
:
);
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("%lld\n", count);
close(fd);
}
我熟悉两种方法,但它们都有其局限性。
第一个是使用指令RDTSC
。但是,问题是它不单独计算我的程序的周期数,因此对并发进程引起的噪声很敏感。
第二种选择是使用clock
库函数。我认为这种方法是可靠的,因为我希望它只计算我的程序的周期数(我打算实现的)。然而,事实证明,在我的例子中,它测量了经过的时间,然后将它乘以 CLOCKS_PER_SEC
。这不仅不可靠,而且是错误的,因为 CLOCKS_PER_SEC
设置为 1,000,000
,这与我处理器的实际频率不符。
鉴于所提出方法的局限性,是否有更好、更可靠的替代方法来产生一致的结果?
RDTSC 是计算程序执行周期的最准确方法。如果您希望在线程被抢占的情况下随时间尺度测量执行性能,那么使用分析器(例如 VTune)可能会更好。
与几乎没有开销的 RDTSC 相比,CLOCKS_PER_SECOND/clock() 几乎是一种非常糟糕的(低性能)获取时间的方式。
如果您有 RDTSC 的具体问题,我可以提供帮助。
回复:评论
Intel Performance Counter Monitor:这个主要是用来测量处理器以外的指标,比如Memory bandwidth, power usage, PCIe utilization。它也恰好测量 CPU 频率,但它通常对处理器绑定的应用程序性能没有用。
RDTSC 可移植性:RDTSC 是所有现代英特尔 CPU 支持的英特尔 CPU 指令。在现代 CPU 上,它基于 CPU 的非核心频率并且在 CPU 核心之间有些相似,但如果您的应用程序经常被抢占到不同的核心(尤其是不同的插座)。如果是这样的话,你真的想看看分析器。
乱序执行: 是的,事情会乱序执行,所以这会稍微影响性能,但执行指令仍然需要时间,RDTSC 是最好的方式测量那个时间。它在同一内核上执行非 IO 绑定指令的正常用例中表现出色,而这正是它的用途所在。如果您有更复杂的用例,您确实应该使用不同的工具,但这并不能否定 rdtsc() 在分析程序执行方面非常有用。
这里很大程度上取决于您尝试测量的时间量。
如果正确使用,RDTSC 可以(几乎)100% 可靠。然而,它主要用于测量真正微观的代码片段。如果你想测量两个序列,比如说,每个序列大约几十条指令,可能没有其他方法可以完成这项工作。
虽然正确使用它有点挑战。一般来说,要获得良好的测量结果,您至少需要执行以下操作:
- 将代码设置为仅在一个特定核心上 运行。
- 将代码设置为以最高优先级执行,这样就没有任何东西可以抢占它。
- 自由使用 CPUID 以确保在需要时进行序列化。
另一方面,如果您要测量的时间从 100 毫秒开始,那么 RDTSC
毫无意义。这就像试图用千分尺测量城市之间的距离。为此,通常最好确保所讨论的代码(至少)花费一秒钟左右的时间。 clock
不是特别精确,但在这个一般顺序的一段时间内,它可能只精确到 10 毫秒左右这一事实或多或少是无关紧要的。
Linux perf_event_open
系统调用 config = PERF_COUNT_HW_CPU_CYCLES
这个系统调用有明确的控制:
- 处理 PID 选择
- 是否考虑kernel/hypervisor说明
因此,即使多个进程同时 运行,它也会正确计算周期数。
有关详细信息,请参阅此答案:How to get the CPU cycle count in x86_64 from C++?
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_CPU_CYCLES;
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 , %[n];\n"
"jne 1b;\n"
: [n] "+r" (n)
:
:
);
ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
read(fd, &count, sizeof(long long));
printf("%lld\n", count);
close(fd);
}