我希望在用C++实现的程序的微秒内获得准确的执行时间.我试图用clock_t获得执行时间,但这不准确.
我在编写测试代码时注意到的一点是,长时间运行的操作在程序运行第一次运行时比在后续运行时运行的时间要长得多,有时甚至会超过10倍.显然这里有一些冷缓存/暖缓存问题,但我似乎无法弄清楚它是什么.
它不是CPU缓存,因为这些长时间运行的操作往往是我提供大量数据的循环,并且它们应该在第一次迭代后完全加载.(另外,卸载和重新加载程序应该清除缓存.)
此外,它不是光盘缓存.我已经通过预先从光盘加载所有数据并在之后处理它来排除这种情况,而实际的CPU绑定数据处理正在缓慢进行.
那么什么可能导致我的程序在第一次运行时运行缓慢,但是如果我关闭它并再次运行它,它运行得更快?我已经在几个不同的程序中看到了这些,这些程序做了很多不同的事情,所以它似乎是一个普遍的问题.
编辑:为了澄清,我在Delphi写作,虽然我不认为这是特定于Delphi的问题.但这意味着无论问题是什么,它都与JIT问题,垃圾收集问题或托管代码带来的任何其他包袱无关.我不是在处理网络连接.这是纯CPU绑定处理.
一个例子:脚本编译器.它运行如下:
如果我将光盘中的所有内容从光盘加载到内存后将一个巨大的脚本文件(~100k行)提供给它,则lex步骤在我第一次运行时大约需要15秒,在后续运行时大约需要2秒.(是的,我知道这还有很长一段时间.我正在努力......)我想知道这种减速的来源以及我能做些什么.
我在SO上看到这篇文章,其中包含C代码以获取最新的CPU周期数:
基于CPU周期计算的C/C++ Linux x86_64中的分析
有没有办法在C++中使用这个代码(欢迎使用windows和linux解决方案)?虽然用C语言编写(而C是C++的一个子集)但我不太确定这段代码是否适用于C++项目,如果没有,如何翻译呢?
我使用的是x86-64
EDIT2:
找到此功能但无法让VS2010识别汇编程序.我需要包含任何内容吗?(我相信我必须换uint64_t到long long窗户......?)
static inline uint64_t get_cycles()
{
uint64_t t;
__asm volatile ("rdtsc" : "=A"(t));
return t;
}
Run Code Online (Sandbox Code Playgroud)
EDIT3:
从上面的代码我得到错误:
"错误C2400:'操作码'中的内联汇编语法错误;找到'数据类型'"
有人可以帮忙吗?
在Chandler Carruth的CppCon 2015演讲中,他介绍了两种神奇的功能,可以在没有任何额外性能损失的情况下击败优化器.
作为参考,这里是函数(使用GNU样式的内联汇编):
void escape(void* p)
{
asm volatile("" : : "g"(p) : "memory");
}
void clobber()
{
asm volatile("" : : : "memory");
}
Run Code Online (Sandbox Code Playgroud)
它适用于任何支持GNU样式内联汇编的编译器(GCC,Clang,Intel编译器,可能还有其他编译器).但是,他提到它在MSVC中不起作用.
检查Google Benchmark的实现,似乎他们使用重新解释转换为a volatile const char&并将其传递给隐藏在非gcc/clang编译器上的不同翻译单元中的函数.
template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) {
internal::UseCharPointer(&reinterpret_cast<char const volatile&>(value));
}
// some other translation unit
void UseCharPointer(char const volatile*) {}
Run Code Online (Sandbox Code Playgroud)
但是,我对此有两个顾虑:
MSVC中是否有与GNU样式的汇编函数相同的低级别?或者这是MSVC上最好的?
对不起,这是一个很长的问题,但我只是在分析这个问题时解释我的思路.最后的问题.
我已经了解了测量代码运行时间的方法.它运行多次以获得平均运行时间来计算每次运行的差异,并获得更好地利用缓存的时间.
为了测量某人的运行时间,我在多次修改后想出了这段代码.
最后,我最终得到了这个代码,它产生了我打算捕获的结果,而没有给出误导性的数字:
// implementation C
static void Test<T>(string testName, Func<T> test, int iterations = 1000000)
{
Console.WriteLine(testName);
Console.WriteLine("Iterations: {0}", iterations);
var results = Enumerable.Repeat(0, iterations).Select(i => new System.Diagnostics.Stopwatch()).ToList();
var timer = System.Diagnostics.Stopwatch.StartNew();
for (int i = 0; i < results.Count; i++)
{
results[i].Start();
test();
results[i].Stop();
}
timer.Stop();
Console.WriteLine("Time(ms): {0,3}/{1,10}/{2,8} ({3,10})", results.Min(t => t.ElapsedMilliseconds), results.Average(t => t.ElapsedMilliseconds), results.Max(t => t.ElapsedMilliseconds), timer.ElapsedMilliseconds);
Console.WriteLine("Ticks: {0,3}/{1,10}/{2,8} ({3,10})", results.Min(t => t.ElapsedTicks), results.Average(t => t.ElapsedTicks), results.Max(t => t.ElapsedTicks), timer.ElapsedTicks);
Console.WriteLine();
}
Run Code Online (Sandbox Code Playgroud)
在我看到的测量运行时间的所有代码中,它们通常采用以下形式: …
当我尝试测量算术运算的执行时间时,我遇到了非常奇怪的行为。包含for循环体中具有一个算术运算的循环的代码块总是比相同的代码块执行得慢,但在for循环体中具有两个算术运算。这是我最终测试的代码:
#include <iostream>
#include <chrono>
#define NUM_ITERATIONS 100000000
int main()
{
// Block 1: one operation in loop body
{
int64_t x = 0, y = 0;
auto start = std::chrono::high_resolution_clock::now();
for (long i = 0; i < NUM_ITERATIONS; i++) {x+=31;}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end-start;
std::cout << diff.count() << " seconds. x,y = " << x << "," << y << std::endl;
}
// Block 2: two operations in loop …Run Code Online (Sandbox Code Playgroud) 我目前正在尝试了解 x86_64(特别是我的 Intel(R) Core(TM) i3-8145U CPU @ 2.10GHz 处理器)上某些循环的性能属性。具体来说,在循环体内部添加一个额外的读取内存指令几乎可以使性能翻倍,而细节并不是特别重要。
我一直在使用一个测试程序,它由两个主要部分组成:一个测试循环和一个被测函数。测试循环运行被测函数 2 32次,一次将每个有符号的 32 位整数作为参数(按从INT_MIN到 的顺序INT_MAX)。被测函数(名为body)是一个小函数,用于检查是否使用预期参数调用它,否则将错误记录在全局变量中。测试程序涉及的内存量足够小,所有东西都可能适合 L1 缓存。
为了消除可能由编译器行为引起的任何速度差异,我用汇编语言编写了两个有问题的函数(我clang用作汇编程序),并被迫从固定地址(这种测试循环的性能通常受与对齐或缓存相关的影响所支配,因此使用固定地址将消除任何与更改无关的对齐效果或缓存效果)。
这是反汇编的测试循环(它需要函数的地址在 中循环%rdi):
401300: 53 push %rbx
401301: 55 push %rbp
401302: 51 push %rcx
401303: 48 89 fd mov %rdi,%rbp
401306: bb 00 00 00 80 mov $0x80000000,%ebx
loop:
40130b: 89 df mov %ebx,%edi
40130d: ff d5 callq *%rbp
40130f: 83 c3 01 add $0x1,%ebx
401312: 71 f7 jno 40130b <loop> …Run Code Online (Sandbox Code Playgroud) 通过制作四个4x4矩阵并转置每个矩阵,可以实现8x8矩阵的转置.这不是我想要的.
在另一个问题中,一个答案提供了一个解决方案,只需要24个8x8矩阵指令.但是,这不适用于花车.
由于AVX2包含256位寄存器,因此每个寄存器适合8个32位整数(浮点数).但问题是:
如何使用AVX/AVX2转换8x8浮点矩阵,尽可能使用最小的指令?
我有简单的C代码(psuedo代码):
#define N 100000000
int *DataSrc = (int *) malloc(N);
int *DataDest = (int *) malloc(N);
memset(DataSrc, 0, N);
for (int i = 0 ; i < 4 ; i++) {
StartTimer();
memcpy(DataDest, DataSrc, N);
StopTimer();
}
printf("%d\n", DataDest[RandomInteger]);
Run Code Online (Sandbox Code Playgroud)
我的电脑:英特尔酷睿i7-3930,配备4x4GB DDR3 1600内存,运行RedHat 6.1 64位.
第一个memcpy()以1.9 GB /秒的速度发生,而接下来的三个以6.2 GB /秒的速度发生.缓冲区大小(N)太大,不能由缓存效果引起.所以,我的第一个问题:为什么第一个memcpy()这么慢?也许malloc()在你使用之前不会完全分配内存?
如果我删除了memset(),那么第一个memcpy()以大约1.5 GB /秒的速度运行,但接下来的三个以11.8 GB /秒的速度运行.几乎是加速的2倍.我的第二个问题:如果我不调用memset(),为什么memcpy()会快2倍?
有没有办法删除数组边界检查C#?
这是我想要实现的目标:
public static int F(int[] M, int i)
{
return M[i]; // I can guarantee that [i] will never be outside of [0, M.Length]
}
Run Code Online (Sandbox Code Playgroud)
在这个函数调用之前,我有一个逻辑已经检查了边界(其中有一些额外的逻辑)。我要删除的内容如下:
Program.F(Int32[], Int32)
L0000: sub rsp, 0x28
L0004: cmp edx, [rcx+8] ; I don't need this line
L0007: jae short L0015 ; I don't need this line
L0009: movsxd rax, edx
L000c: mov eax, [rcx+rax*4+0x10]
L0010: add rsp, 0x28
L0014: ret
L0015: call 0x00007ffc8877bc70 ; I don't need this line
L001a: int3 …Run Code Online (Sandbox Code Playgroud)