为什么 std::chrono 说我的函数执行时间为零纳秒?

Why does std::chrono say my function took zero nanoseconds to execute?

我正在做一个项目,我在这个项目中用 C++ 实现了两种流行的 MST 算法,然后打印出每个算法的执行时间。请忽略实际的算法,我已经测试过它们并且只对准确测量它们需要多长时间感兴趣。

void Graph::krushkalMST(bool e){
    size_t s2 = size * size;
    typedef struct{uint loc; uint val;} wcType; //struct used for storing a copy of the weights values to be sorted, with original locations
    wcType* weightsCopy = new wcType[s2];       //copy of the weights which will be sorted.
    for(int i = 0; i < s2; i++){
        weightsCopy[i].loc = i;
        weightsCopy[i].val = weights[i];
    }
    std::vector<uint> T(0);                                     //List of edges in the MST
    auto start = std::chrono::high_resolution_clock::now();     //time the program was started
    typedef int (*cmpType)(const void*, const void*);           //comparison function type
    static cmpType cmp = [](const void* ua, const void* ub){    //Compare function used by the sort as a C++ lambda
        uint a = ((wcType*)ua)->val, b = ((wcType*)ub)->val;
        return (a == b) ? 0 : (a == NULLEDGE) ? 1 : (b == NULLEDGE) ? -1 : (a < b) ? -1 : 1;
    };
    std::qsort((void*)weightsCopy, s2, sizeof(wcType), cmp);    //sort edges into ascending order using a quick sort (supposedly quick sort)
    uint* componentRefs = new uint[size];                       //maps nodes to what component they currently belong to
    std::vector<std::vector<uint>> components(size);            //vector of components, each component is a vector of nodes;
    for(int i = 0; i < size; i++){
        //unOptimize(components);
        components[i] = std::vector<uint>({(uint)i});
        componentRefs[i] = i;
    }
    for(int wcIndex = 0; components.size() >= 2 ; wcIndex++){
        uint i = getI(weightsCopy[wcIndex].loc), j = getJ(weightsCopy[wcIndex].loc); //get pair of nodes with the smallest edge
        uint ci = componentRefs[i], cj = componentRefs[j];      //locations of nodes i and j
        if(ci != cj){
            T.push_back(weightsCopy[wcIndex].loc);              //push the edge into T
            for(int k = 0; k < components[cj].size(); k++)      //move each member in j's component to i's component
                components[ci].push_back(components[cj][k]);
            for(int k = 0; k < components[cj].size(); k++)      //copy this change into the reference locations
                componentRefs[components[cj][k]] = ci;
            components.erase(components.begin() + cj);          //delete j's component
            for(int k = 0; k < size; k++)                                   
                if(componentRefs[k] >= cj)
                    componentRefs[k]--;
        }
    }
    auto end = std::chrono::high_resolution_clock::now();
    uint time = std::chrono::duration_cast<std::chrono::nanoseconds>(end-start).count();
    std::cout<<"\nMST found my krushkal's Algorithm:\n";
    printData(time, T, e);
    delete[] weightsCopy;
    delete[] componentRefs;
}

void Graph::primMST(bool e){
    std::vector<uint> T(0);                                     //List of edges in the MST
    auto start = std::chrono::high_resolution_clock::now();     //Start calculating the time the algorithm takes
    bool* visited = new bool[size];                             //Maps each node to a visited value
    visited[0] = true;
    for(int i = 1; i < size; i++)
        visited[i] = false;
    for(uint numVisited = 1; numVisited < size; numVisited++){
        uint index = 0;                                         //index of the smallest cost edge to unvisited node
        uint minCost = std::numeric_limits<uint>::max();                //cost of the smallest edge filling those conditions
        for(int i = 0; i < size; i++){
            if(visited[i]){
                for(int j = 0; j < size; j++){
                    if(!visited[j]){
                        uint curIndex = i * size + j, weight = dweights[curIndex];
                        if(weight != NULLEDGE && weight < minCost){
                            index = curIndex;
                            minCost = weight;
                        }
                    }
                }
            }
        }
        T.push_back(index);
        visited[getI(index)] = true;
    }
    auto end = std::chrono::high_resolution_clock::now();
    uint time = std::chrono::duration_cast<std::chrono::microseconds>(end-start).count();
    std::cout<<"\nMST found my Prim's Algorithm:\n";
    printData(time, T, e);
    delete[] visited;
}

我最初使用 <ctime> 中的 clock() 来尝试准确测量这需要多长时间,我最大的测试文件有一个包含 40 个节点和 780 条边的图表(足够大以保证一些计算时间),即使在使用 g++ 和 -O0 的慢速计算机上,我也会得到 0 或 1 毫秒。在我的桌面上,我只能得到 0 毫秒,但是因为我需要一种更准确的方法来区分测试用例之间的时间,所以我决定尝试使用 <chrono> 库提供的 high_resolution_clock

这才是真正的麻烦开始的地方,我会(并且仍然)始终如一地得到程序执行时间为 0 纳秒。 在寻找解决方案的过程中,我遇到了多个处理类似问题的问题,其中大部分都指出 <chrono> 是系统相关的,您实际上不太可能获得纳秒甚至微秒的值。尽管如此,我尝试使用 std::chrono::microsecond 仍然始终如一地得到 0。最终我发现我认为有人和我有同样的问题:

然而,这显然是一个过度活跃的优化器的问题,它删除了一段不必要的代码,而在我的情况下,最终结果总是取决于必须完全执行的一系列复杂循环的结果。我在 Windows 10,使用 -O0 通过 GCC 进行编译。

我最好的假设是我做错了什么,或者 windows 不支持任何小于毫秒的东西,而使用 std::chrono 和 std::chrono::nanoseconds 实际上只是用 0 填充的毫秒最后(正如我在算法中放入 system("pause") 并在任意时间取消暂停时观察到的那样)。如果您发现了这个问题,或者是否有任何其他方法可以实现更高分辨率,请告诉我。


应@Ulrich Eckhardt 的要求,我提供了最小的可重现示例以及我使用它进行的测试的结果,我必须说它非常有见地。

#include<iostream>
#include<chrono>
#include<cmath>

int main()
{
    double c = 1; 

    for(int itter = 1; itter < 10000000; itter *= 10){
        auto start = std::chrono::high_resolution_clock::now();
        for(int i = 0; i < itter; i++)
            c += sqrt(c) + log(c);
        auto end = std::chrono::high_resolution_clock::now();
        int time = std::chrono::duration_cast<std::chrono::nanoseconds>(end-start).count();
        std::cout<<"calculated: "<<c<<". "<<itter<<" iterations took "<<time<<"ns\n";
    }
    system("pause");
}

对于我的循环,我选择了一个随机的任意数学公式,并确保使用循环的结果,这样它就不会被优化到不存在的地步。在我的桌面上用各种迭代测试它会产生:
这似乎意味着在它开始计算时间之前需要一个特定的阈值,因为将产生非零时间的第一个结果所花费的时间除以 10,我们得到另一个非零时间,这不是结果所说的尽管假设整个循环需要 O(n) 时间和 n 次迭代,这就是它应该如何工作。如果说这个小例子有什么让我更加困惑的话。

切换到 steady_clock,您将获得 MSVC 和 MinGW GCC 的正确结果。

您应该避免使用 high_resolution_clock,因为它只是 steady_clocksystem_clock 的别名。要像时尚一样用秒表测量经过的时间,您 总是 想要 steady_clockhigh_resolution_clock 是一件不幸的事情,应该避免。

我刚刚查了一下,MSVC 有以下内容:

using high_resolution_clock = steady_clock;

而 MinGW GCC 有:

/**
 *  @brief Highest-resolution clock
 *
 *  This is the clock "with the shortest tick period." Alias to
 *  std::system_clock until higher-than-nanosecond definitions
 *  become feasible.
*/
using high_resolution_clock = system_clock;