我刚刚编写了一些小的内联asm例程来查询x86中的时间戳计数器,这样我就可以分析一小部分代码。我真的想把这些例程放在一个标题中,以便我可以在许多不同的源文件中重用它们,所以基本上我的问题是我是应该只在宏中组织它们还是使它们成为内联函数,我对内联的疑问是它不是必然是编译器实际内联它的情况,因为它是一个性能敏感的调用,我宁愿跳过函数调用开销,另一方面用宏,整个类型的安全性消失了,我将严格需要一个32位的int为此,我假设我可以在评论中添加规范,但我仍然试图避免使用宏,因为有很多警告。这是代码:
inline void rdtsc(uint64_t* cycles)
{
uint32_t cycles_high, cycles_low;
asm volatile (
".att_syntax\n"
"CPUID\n\t" //Serialize
"RDTSC\n\t" //Read clock and cpuid
"mov %%edx, %0 \n\t"
"mov %%eax, %1 \n\t"
: "=r" (cycles_high), "=r" (cycles_low)
:: "%edx", "%eax");
*cycles = ((uint64_t) cycles_high << 32) | cycles_low;
}
欢迎提出任何建议。我只想弄清楚这种情况的首选风格是什么。
答案 0 :(得分:1)
由于您将测量部分代码的性能,而不一定是整个函数的性能,因此您不应尝试内联性能计数器。 是否存在呼叫开销并不重要。什么问题是测量是一致的,这意味着你要么总是要求呼叫开销存在,要么永远不要。 第一个比前者容易实现。
让代码的每个部分都有相同的调用开销。
答案 1 :(得分:1)
如果你真的需要在读取TSC之前进行序列化,你可以使用LFENCE指令而不是改变寄存器。
如果您决定继续使用CPUID进行序列化,则应首先设置EAX(可能为0,因为您并不真正关心输出)并注意该指令会破坏EAX,EBX,ECX和EDX寄存器,所以你的例程必须考虑到这个事实。
总而言之,我倾向于这样写:
#include <stdint.h>
#include <stdio.h>
inline uint64_t rdtsc() {
uint32_t high, low;
asm volatile (
".att_syntax\n\t"
"LFENCE\n\t"
"RDTSC\n\t"
"movl %%eax, %0\n\t"
"movl %%edx, %1\n\t"
: "=rm" (low), "=rm" (high)
:: "%edx", "%eax");
return ((uint64_t) high << 32) | low;
}
int main() {
uint64_t x, y;
x = rdtsc();
printf("%lu\n", x);
y = rdtsc();
printf("%lu\n", y);
printf("%lu\n", y-x);
}
由@Jester和@DavidWohlferd提出,可以通过将high
和low
直接分配给edx
和{{1}来消除寄存器分配寄存器。
那个版本看起来像这样:
eax
生成的代码(在运行Linux的64位计算机上使用gcc 4.8.3)使用优化-O2并包括对inline uint64_t rdtsc() {
uint32_t high, low;
asm volatile (
".att_syntax\n\t"
"LFENCE\n\t"
"RDTSC\n\t"
: "=a" (low), "=d" (high)
:: );
return ((uint64_t) high << 32) | low;
}
的调用,是:
printf
我最初发布的版本结果如下:
#APP
# 20 "rdtsc.c" 1
.att_syntax
LFENCE
RDTSC
# 0 "" 2
#NO_APP
movq %rdx, %rbx
movl %eax, %eax
movl $.LC0, %edi
salq $32, %rbx
orq %rax, %rbx
xorl %eax, %eax
movq %rbx, %rsi
call printf
该版本的代码长一指令。