为什么此线性和二进制搜索基准代码不起作用?

时间:2019-05-09 17:30:25

标签: c++ benchmarking binary-search linear-search

我正在尝试将线性和二进制搜索作为任务的一部分进行基准测试。我已经编写了必要的搜索和随机化函数。但是,当我尝试对它们进行基准测试时,即使对于更大的阵列大小,我也会得到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的线性搜索结果

701950至701950:0后对29445:26987的二进制搜索结果

我也尝试过使用clock_t,但是结果是一样的。我需要更大的阵列大小还是基准测试错误?

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

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

2 个答案:

答案 0 :(得分:2)

一个问题是,在将测试数组打包为随机值之前,需要设置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。修改代码以计算比较元素的次数,并使用该信息代替计时。

答案 1 :(得分:0)

您似乎正在使用搜索结果(通过在定时区域之外用cout *打印它就可以了)。而且data +键是随机的,因此在编译时不应对搜索进行优化。 (禁用优化的基准测试是没有意义的,因此您需要这样的技巧。)


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

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

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


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


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

但是,这样的问题是要确保没有对重复调用进行优化,并在循环内随机分配搜索键。 (否则,聪明的编译器可能会使搜索脱离循环。

诸如volatile int result = binSearch(...);之类的方法会有所帮助,因为分配(或初始化)volatile是一种显而易见的副作用,无法对其进行优化。因此,编译器实际上需要在寄存器中具体实现每个搜索结果。

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