C++ 如何从C++;?
我在SO上看到了这篇文章,其中包含获取最新CPU周期计数的C代码: 有没有一种方法可以在C++中使用这个代码(Windows和Linux解决方案欢迎)?虽然用C语言编写(C是C++的子集),但我不太确定如果这个代码在C++项目中工作,如果不是,怎么翻译? 我正在使用x86-64 编辑2: 找到此函数,但无法让VS2010识别汇编器。我需要包括什么吗?(我相信我必须将windows的C++ 如何从C++;?,c++,c,performance,x86,rdtsc,C++,C,Performance,X86,Rdtsc,我在SO上看到了这篇文章,其中包含获取最新CPU周期计数的C代码: 有没有一种方法可以在C++中使用这个代码(Windows和Linux解决方案欢迎)?虽然用C语言编写(C是C++的子集),但我不太确定如果这个代码在C++项目中工作,如果不是,怎么翻译? 我正在使用x86-64 编辑2: 找到此函数,但无法让VS2010识别汇编器。我需要包括什么吗?(我相信我必须将windows的uint64\u t转换为long。) 编辑3: 从上面的代码中,我得到了错误: 错误C2400:“操作码”中的内
uint64\u t
转换为long
。)
编辑3:
从上面的代码中,我得到了错误:
错误C2400:“操作码”中的内联汇编程序语法错误;找到“数据”
“类型”
有人能帮忙吗?对于Windows,Visual Studio提供了一个方便的“编译器内部函数”(即编译器理解的特殊函数),它为您执行RDTSC指令并返回结果:
unsigned __int64 __rdtsc(void);
从GCC4.5及更高版本开始,MSVC和GCC现在都支持
\uuu rdtsc()
内在的
但所需的包含是不同的:
#ifdef _WIN32
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
\ifdef\u WIN32
#包括
#否则
#包括
#恩迪夫
以下是GCC 4.5之前的原始答案 直接从我的一个项目中提取:
#include <stdint.h>
// Windows
#ifdef _WIN32
#include <intrin.h>
uint64_t rdtsc(){
return __rdtsc();
}
// Linux/GCC
#else
uint64_t rdtsc(){
unsigned int lo,hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
#endif
#包括
//窗户
#ifdef_WIN32
#包括
uint64_t rdtsc(){
返回uu rdtsc();
}
//Linux/GCC
#否则
uint64_t rdtsc(){
无符号整数lo,hi;
__asm_uuuuuuvolatile_uuuuuu(“rdtsc”):“=a”(lo),“=d”(hi));
return((uint64_t)hiVC++对内联汇编使用完全不同的语法,但仅在32位版本中使用。64位编译器根本不支持内联汇编
在这种情况下,这可能也是--rdtsc
在计时代码序列时(至少)有两个主要问题它可能会无序执行,因此如果您试图对一个短的代码序列计时,那么该代码前后的rdtsc
可能都会在它之前执行,或者都在它之后执行,或者诸如此类(我相当肯定这两个代码始终会按顺序执行,因此至少不会出现负的差异)
其次,在多核(或多处理器)系统上,一个rdtsc可能在一个核/处理器上执行,另一个在不同的核/处理器上执行。在这种情况下,完全可能出现负面结果
一般来说,如果您想在Windows下使用精确的计时器,最好使用QueryPerformanceCounter
<>如果你真的坚持使用<代码> RDTSC SC/<代码>,我相信你必须在一个完全用汇编语言编写的单独的模块(或者使用编译器内部)来完成它,然后与C或C++连接。我从来没有为64位模式编写过代码,但是在32位模式下,它看起来像这样:
xor eax, eax
cpuid
xor eax, eax
cpuid
xor eax, eax
cpuid
rdtsc
; save eax, edx
; code you're going to time goes here
xor eax, eax
cpuid
rdtsc
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
我知道这看起来很奇怪,但实际上是对的。执行CPUID是因为它是一条串行化指令(不能无序执行),并且在用户模式下可用。在开始计时之前执行它三次,因为Intel记录了这样一个事实,即第一次执行可以/将以与第二次不同的速度运行(他们推荐的是三个,所以是三个)
然后执行测试中的代码、另一个cpuid以强制序列化,以及最终的rdtsc以获取代码完成后的时间
除此之外,您还需要使用操作系统提供的任何手段来强制所有这些都在一个进程/核心上运行。在大多数情况下,您还需要强制代码对齐——对齐方式的更改可能会导致执行速度的显著差异
最后,你想多次执行它,而且它总是可能在事物中间中断(例如,任务开关)。,因此,您需要为执行时间可能比其他运行时间长一些做好准备—例如,5次运行每次需要约40-43个时钟周期,第六次运行需要10000多个时钟周期。显然,在后一种情况下,您只需抛出异常值—它不是来自您的代码
小结:设法执行rdtsc指令本身(几乎)是您最不担心的事情。在从rdtsc
中获得实际意义的结果之前,您还需要做很多事情。您的内联asm在x86-64中被破坏。“=a”
在64位模式下,编译器可以选择RAX或RDX,而不是EDX:EAX。请参阅
这不需要内联asm。这没有好处;编译器内置了rdtsc
和rdtscp
,并且(至少现在)如果包含正确的头,所有编译器都定义了一个u rdtsc
内在函数。但与几乎所有其他情况不同(),asm没有严重的缺点,只要您使用像@Mysticial这样的良好安全的实现
(asm的一个次要优势是,如果您想计算一个肯定小于2^32个计数的小时间间隔,可以忽略结果的高半部。编译器可以使用uint32_t time\u low=\uu rdtsc()
内在函数为您进行优化,但在实践中,它们有时仍然会将指令浪费在shift/OR上。)
不幸的是,MSVC不同意其他人关于非SIMD内部函数使用哪个头的意见
表示\u rdtsc
(带一个下划线)在
中,但这在gcc和clang中不起作用。它们只在
中定义SIMD内部函数,因此我们只能使用
(MSVC)与
(其他一切,包括最近的ICC)。为了与MSVC和英特尔的文档兼容,gcc和clang定义了函数的单下划线和双下划线版本
有趣的事实:双下划线版本返回一个无符号的64位整数,而英特尔文档将\u rdtsc()
作为返回(有符号)\u int64
// valid C99 and C++
#include <stdint.h> // <cstdint> is preferred in C++, but stdint.h works.
#ifdef _MSC_VER
# include <intrin.h>
#else
# include <x86intrin.h>
#endif
// optional wrapper if you don't want to just use __rdtsc() everywhere
inline
uint64_t readTSC() {
// _mm_lfence(); // optionally wait for earlier insns to retire before reading the clock
uint64_t tsc = __rdtsc();
// _mm_lfence(); // optionally block later instructions until rdtsc retires
return tsc;
}
// requires a Nehalem or newer CPU. Not Core2 or earlier. IDK when AMD added it.
inline
uint64_t readTSCp() {
unsigned dummy;
return __rdtscp(&dummy); // waits for earlier insns to retire, but allows later to start
}
unsigned __int64 readTSC(void) PROC ; readTSC
rdtsc
shl rdx, 32 ; 00000020H
or rax, rdx
ret 0
; return in RAX
uint64_t time_something() {
uint64_t start = readTSC();
// even when empty, back-to-back __rdtsc() don't optimize away
return readTSC() - start;
}
# gcc8.2 -O3 -m32
time_something():
push ebx # save a call-preserved reg: 32-bit only has 3 scratch regs
rdtsc
mov ecx, eax
mov ebx, edx # start in ebx:ecx
# timed region (empty)
rdtsc
sub eax, ecx
sbb edx, ebx # edx:eax -= ebx:ecx
pop ebx
ret # return value in edx:eax
# MSVC 19 2017 -Ox
unsigned __int64 time_something(void) PROC ; time_something
rdtsc
shl rdx, 32 ; high <<= 32
or rax, rdx
mov rcx, rax ; missed optimization: lea rcx, [rdx+rax]
; rcx = start
;; timed region (empty)
rdtsc
shl rdx, 32
or rax, rdx ; rax = end
sub rax, rcx ; end -= start
ret 0
unsigned __int64 time_something(void) ENDP ; time_something
// More efficient than __rdtsc() in some case, but maybe worse in others
uint64_t rdtsc(){
// long and uintptr_t are 32-bit on the x32 ABI (32-bit pointers in 64-bit mode), so #ifdef would be better if we care about this trick there.
unsigned long lo,hi; // let the compiler know that zero-extension to 64 bits isn't required
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) + lo;
// + allows LEA or ADD instead of OR
}
#define _GNU_SOURCE
#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>
#include <sys/types.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 $1, %[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);
}