为啥这个线性和二进制搜索的基准代码不起作用?

Posted

技术标签:

【中文标题】为啥这个线性和二进制搜索的基准代码不起作用?【英文标题】:Why is this benchmark code for linear and binary search not working?为什么这个线性和二进制搜索的基准代码不起作用? 【发布时间】:2019-05-09 17:30:24 【问题描述】:

我正在尝试将线性和二进制搜索作为作业的一部分进行基准测试。我已经编写了必要的搜索和随机函数。但是当我尝试对它们进行基准测试时,即使对于更大的数组大小,我也会得到 0 延迟。

代码:

#include<iostream>
#include <time.h>
#include <windows.h>
using namespace std;

double getTime()

    LARGE_INTEGER t, f;
    QueryPerformanceCounter(&t);
    QueryPerformanceFrequency(&f);
    return (double)t.QuadPart/(double)f.QuadPart;



int linearSearch(int arr[], int len,int target)
    int resultIndex = -1;
    for(int i = 0;i<len;i++)
        if(arr[i] == target)
           resultIndex = i;
           break;
        
    

    return resultIndex;


void badSort(int arr[],int len)
    for(int i = 0 ; i< len;i++)
        int indexToSwapWith = i;
        for(int j = i+1;j < len;j++)
            if(arr[j] < arr[indexToSwapWith] )
                indexToSwapWith = j;
        
        if(indexToSwapWith != i)
            int t = arr[i];
            arr[i] = arr[indexToSwapWith];
            arr[indexToSwapWith] = t;
        
    


int binSearch(int arr[], int len,int target)
    int resultIndex = -1;

    int first = 0;
    int last = len;
    int mid = first;

    while(first <= last)
        mid = (first + last)/2;
        if(target < arr[mid])
            last = mid-1;
        else if(target > arr[mid])
            first = mid+1;
        else
            break;
    

    if(arr[mid] == target)
        resultIndex = mid;

    return resultIndex;


void fillArrRandomly(int arr[],int len)
    srand(time(NULL));
    for(int i = 0 ; i < len ;i++)
        arr[i] = rand();
    


void benchmarkRandomly(int len)

    float startTime = getTime();

    int arr[len];
    fillArrRandomly(arr,len);
    badSort(arr,len);

    /*
    for(auto i : arr)
        cout<<i<<"\n";
    */

    float endTime = getTime();
    float timeElapsed = endTime - startTime;
    cout<< "prep took " << timeElapsed<<endl;

    int target = rand();

    startTime = getTime();
    int result = linearSearch(arr,len,target);

    endTime = getTime();
    timeElapsed = endTime - startTime;
    cout<<"linear search result for "<<target<<":"<<result<<" after "<<startTime<<" to "<<endTime <<":"<<timeElapsed<<"\n";

    startTime = getTime();
    result = binSearch(arr,len,target);
    endTime =  getTime();
    timeElapsed = endTime - startTime;
    cout<<"binary search result for "<<target<<":"<<result<<" after "<<startTime<<" to "<<endTime <<":"<<timeElapsed<<"\n";


int main()
    benchmarkRandomly(30000);

样本输出:

准备耗时 0.9375

从 701950 到 701950:0 之后的 29445:26987 的线性搜索结果

29445:26987 在 701950 到 701950:0 之后的二分搜索结果

我也尝试过使用clock_t,但结果是一样的。我是否需要更大的数组大小,还是我以错误的方式进行基准测试?

在课程中,我必须自己实现大部分内容。这就是我不使用stl的原因。我不确定是否允许使用 stl::chrono,但我想确保问题首先不在其他地方。

编辑:如果不清楚,我不能在基准测试中包含排序和随机生成的时间。

【问题讨论】:

【参考方案1】:

一个问题是您在使用随机值打包测试数组之前设置了 startTime = getTime()。如果随机数生成很慢,这可能会主导返回的结果。主要工作是对数组进行排序,与此相比,搜索时间将非常短。 正如您所建议的那样,这可能是一个间隔。对于 30k 个对象的二分搜索,我们只讨论 12 或 13 次迭代,因此在现代机器上最多 20 / 1000000000 秒。这大约是零毫秒。

增加数组条目的数量不会有太大帮助,但您可以尝试增加数组大小,直到接近内存限制。但现在你的问题将是准备随机数的生成和排序将花费很长时间。

我会建议:-

A.检查大量项目:-

unsigned int total;
startTime = getTime();
for (i=0; i<10000000; i++)
    total += binSearch(arr, len, rand());
endTime = getTime();

B.修改您的代码以计算您比较元素的次数并使用该信息而不是时间。

【讨论】:

A.不,这不会在循环内使用 binSearch 的结果,因此它应该优化掉。 OP 的代码没有 有这个问题:它保存搜索结果并在循环后打印。他们应该测量 something,也许浮点舍入使其为 0。(但是,是的,重复循环 with 对结果的某些优化失败使用,例如 volatile int result = binSearch(...)更好地分摊测量开销,并为函数预热代码缓存。是的,随机搜索键也应该有助于避免预热分支预测。) 好点。我已经更改了建议的代码片段以使用结果,注意到总将环绕多次。 这样更好。不过,QueryPerformanceCounter 应该仍然能够大致计时 1 次迭代。我仍然认为floatcout 上的舍入和/或格式可能是看到0 的一种解释,就像我在回答中所说的那样。对QueryPerformanceCounter 的两次调用不会返回相同的值,因此如果它们不能正确测量纳秒,那么 OP 的时间计算就会中断。 (虽然它有几十到几百个时钟周期的开销,所以它对于微基准测试并不完美,但要做得更好,你必须内联 _rdtsc() 并担心无序执行。) 我真的不需要时间进行排序/随机生成。但是平均通话时间是有效的。谢谢。【参考方案2】:

看起来您正在使用搜索结果(通过在定时区域之外使用cout * 打印它,这很好)。并且数据+键是随机的,所以搜索不应该在编译时得到优化。 (禁用优化的基准测试毫无意义,所以你需要这样的技巧。)


您是否使用调试器查看过timeElapsed?也许这是一个非常小的float,使用默认的cout 设置打印为0

或者 float endTime - float startTime 实际上等于 0.0f,因为四舍五入到最接近的 float 使它们相等。减去附近的两个大浮点数会产生“灾难性的抵消”。

请记住float only has 24 bits of significand,因此无论您除以何种频率,如果 PerformanceCounter 值在 2^24 中的差异小于 1 部分,您将得到零。 (如果该函数从 x86 rdtsc 返回原始计数,那么如果您的系统上次重新启动时间比时间间隔长 2^24 倍以上,就会发生这种情况。x86 TSC 在系统启动时从零开始,并且(在 CPU 上在过去约 10 年中)以“参考频率”计算,该“参考频率”(大约)等于您的 CPU 的额定/“标贴”频率,无论涡轮或空闲时钟速度如何。请参阅Get CPU cycle count?)


double 可能会有所帮助,但在除法之前在整数域中减去会更好。此外,重写该部分将使QueryPerformanceFrequency 超出时间间隔!


正如@Jon 建议的那样,通常最好将被测代码放入一个较长时间间隔内的重复循环中,这样(代码)缓存和分支预测可以预热。

但是你会遇到确保重复调用不会被优化掉的问题,以及在循环内随机化搜索键的问题。 (否则智能编译器可能会将搜索提升到循环之外)。

volatile int result = binSearch(...); 这样的东西会有所帮助,因为分配(或初始化)volatile 是一种无法优化的可见副作用。因此编译器需要将每个搜索结果实际具体化到一个寄存器中。

对于某些编译器,例如支持 GNU C 内联汇编的那些,您可以使用内联汇编来要求编译器在寄存器中生成一个值 而不会增加任何将其存储在任何地方的开销。 AFAIK 这对于 MSVC 内联汇编是不可能的。

【讨论】:

以上是关于为啥这个线性和二进制搜索的基准代码不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

多维动态数组,为啥不起作用?

为啥 pull-right 类在 bootstrap 版本 4.1.0 上不起作用? [复制]

为啥外部链接在构建后在 phonegap 应用程序上不起作用

为啥我的 Linux 编译的二进制文件在 Windows 上运行时不起作用?

为啥 ngfor 指令不起作用,尽管我在 Typescript 类中创建了正确的对象

Python线性搜索不起作用