簡體   English   中英

為什么自制的二進制搜索算法比std :: binary_search慢?

[英]why homemade binary search algorithm is slower than std::binary_search?

一個簡單的自制二進制搜索算法被std::binary_search (再次)擊敗:

// gcc version 4.8.2 X86_64

#ifndef EXAMPLE_COMPARE_VERSION
# define EXAMPLE_COMPARE_VERSION 0
#endif

static const long long LOOPS = 0x1fffffff;

#include <cassert>
#include <cstdlib>
#include <ctime>
#include <cstdio>

#if EXAMPLE_COMPARE_VERSION
#include <algorithm>

inline bool stl_compare(const int l, const int r) {
  return l < r;
}

#else

inline bool compare(const int *beg, const int *end, int v) {
  for (const int *p; beg <= end;) {
    p = beg + (end - beg) / 2;
    if (*p < v) beg = p + 1;
    else if (*p > v) end = p - 1;
    else return true;
  }
  return false;
}
#endif

int main() {
  const int arr[] = {
    1784, 1785, 1787, 1789, 1794, 1796, 1797, 1801, 
    1802, 1805, 1808, 1809, 1912, 1916, 1918, 1919, 
    1920, 1924, 1925, 1926, 1929, 1930, 2040, 2044, 
    2047, 2055, 2057, 2058, 2060, 2061, 2064, 2168, 
    2172, 2189, 2193, 2300, 2307, 2309, 2310, 2314, 
    2315, 2316, 2424, 2429, 2432, 2433, 2438, 2441, 
    2448, 2552, 2555, 2563, 2565, 2572, 2573, 2680, 
    2684, 2688, 2694, 2697, 2699, 2700, 2704, 2705, 
    2808, 2811, 2813, 2814, 2816, 2818, 2822, 2826, 
    2827, 2828, 2936, 2957, 3064, 3070, 3072, 3073, 
    3074, 3075, 3076, 3077, 3078, 3081, 3082, 3084, 
    3085, 3086, 3088, 3192, 3196, 3198, 3200, 3205, 
    3206, 3211, 3212, 3213, 3326, 3327, 3328, 3330, 
    3331, 3333, 3337, 3338, 3339, 3344, 3448, 3449, 
    3451, 3452, 3454, 3459, 3461, 3462, 3465, 3469, 
    3472, 3578, 3585, 3588, 3593, 3594, 3704, 3712, 
    3715, 3722, 3723, 3852, 3972, 3973, 3974, 3980, 
    3982, 4088, 4090, 4091, 4092, 4094, 4096, 4098, 
    4099, 4100, 4101, 4102, 4103, 4105, 4106, 4107, 
    4108, 4109, 4110, 4216, 4220, 4222, 4223, 4224, 
    4226, 4227, 4229, 4230, 4233, 4234, 4235, 4238, 
    4240, 4350, 4354, 4361, 4369, 4476, 4480, 4486, 
    4600, 4614, 4735, 4864, 4870, 4984, 4991, 5004, 
  };
  clock_t t = clock();
  const size_t len = sizeof(arr) / sizeof(arr[0]);
  for (long long i = 0; i < LOOPS; i++) {
    int v = arr[rand() % len];
#if EXAMPLE_COMPARE_VERSION >= 2
    assert(std::binary_search(arr, arr + len, v, stl_compare));
#elif EXAMPLE_COMPARE_VERSION
    assert(std::binary_search(arr, arr + len, v));
#else 
    assert(compare(arr, arr + len, v));
#endif
  }
  printf("compare version: %d\ttime: %zu\n",
      EXAMPLE_COMPARE_VERSION, (clock() - t) / 10000);
}

編譯文件(如果保存為t.cc

g++ t.cc -O3 -DEXAMPLE_COMPARE_VERSION=0 -o t0
g++ t.cc -O3 -DEXAMPLE_COMPARE_VERSION=1 -o t1
g++ t.cc -O3 -DEXAMPLE_COMPARE_VERSION=2 -o t2

去測試

./t2 ; ./t0 ; ./t1

在我的機器上輸出(時間越短越快):

compare version: 2      time: 3533
compare version: 0      time: 4074
compare version: 1      time: 3968

EXAMPLE_COMPARE_VERSION設置為0我們使用自制的二進制搜索算法。

inline bool compare(const int *beg, const int *end, int v) {
  for (const int *p; beg <= end;) {
    p = beg + (end - beg) / 2;
    if (*p < v) beg = p + 1;
    else if (*p > v) end = p - 1;
    else return true;
  }
  return false;
}

EXAMPLE_COMPARE_VERSION設置為1我們使用:

template <class ForwardIterator, class T>
  bool binary_search (ForwardIterator first, ForwardIterator last,
                      const T& val);

EXAMPLE_COMPARE_VERSION設置為2我們使用:

template <class ForwardIterator, class T, class Compare>
  bool binary_search (ForwardIterator first, ForwardIterator last,
                      const T& val, Compare comp);

// the Compare function:
inline bool stl_compare(const int l, const int r) {
  return l < r;
}

兩個std::binary_search函數在gcc頭文件目錄的bits/stl_algo.h中定義。

問題

  1. 為什么使用比較函數(t2)的std::binary_search比沒有它的版本(t1)要快得多?
  2. 有時甚至t1比自制的二進制搜索程序(t0)更快。 為什么t0這么慢以及如何加快速度呢?

更新:

rand()替換random() ,另請參閱rand()和random()函數之間的區別?

因為基准是有缺陷的。

  1. 你在循環(和定時)區域內random調用:它的運行時不僅有問題(並影響基准),但它也意味着你可能沒有在基准測試中測量相同的運行
  2. 由於花費的時間取決於隨機輸出,您使用哪種統計方法盡可能公平? 平均超過5次交錯運行? 最好的5次交錯運行? ...

現在,即使清除了廢墟,您最終也可能會遇到標准算法比您自己的自制解決方案更快的情況。 在這一點上,思考一下C ++的哲學: 你不需要付出你不需要的東西 因此,如果不進行優化,標准實現很可能至少足夠精確到和天真的方法一樣快:如果不是這樣的話,它們很久以前就被修補了!

所以,最后,你要留意檢查差異。 此時,您需要深入研究代碼並了解它的映射方式。 我建議使用源代碼,LLVM IR或匯編進行此探索(如果您不理解某些轉換,請隨時提出問題)。

也許還有一些展開? 也許測試更好暗示? 誰知道,經過幾十年的存在,你可能會發現一顆珍珠。

注意:要在http://coliru.stacked-crooked.com上獲取LLVM IR,請使用以下命令行clang -O3 -S -emit-llvm -o main.ll main.cpp && cat main.ll

對此沒有明確的答案,但我可以嘗試給出一些觀點。

  1. 如果指定stl_compare,則首先std :: binary_search會調用stl_compare,然后調用實際運算符<導致額外調用。 否則它可以簡單地調用operator <。

  2. 你的算法有機會改進。 例如,您在比較時取消引用p 2次。 您可以將* p保存為const或寄存器或const寄存器類型以加快速度。

你可以修改你的比較功能,如下所示,試一試

inline bool compare(const int *beg, const int *end, int v) {
  while (beg <= end) {
    const int *const p = beg + ((end - beg) >> 1);
    const int z = *p;
    if (z < v) beg = p + 1;
    else if (z > v) end = p - 1;
    else return true;
  }
  return false;
}

它在我的機器上顯示比stl二進制搜索更好的結果。 (gcc 4.6.3)


編輯

完全尊重Matthieu M.的評論。我試圖重新搜索您的搜索食譜。 在我的設置中,我仍然得到與stl相當的結果。

$ ./t0
compare version: 0      time: 3088
$ ./t1
compare version: 1      time: 3113
$ ./t2
compare version: 2      time: 3115
$ ./t0;./t1;./t2
compare version: 0      time: 3082
compare version: 1      time: 3116
compare version: 2      time: 3042

遵循我使用的修改功能

inline bool compare(const int *beg, const int *end, int v) {
  if(end <= beg) return false; // Comment this line if you are sure end is always greater than beg
  int count = end - beg;
  while (count > 0) {
    const int half = count >> 1;
    const int *const p = beg + half;
    if (*p < v) {
      beg = p + 1;
      count -= half + 1;
    } else {
      count = half;
    }
  }
  return *beg == v;
}

編譯器無論如何都會內聯比較函數,而STL實現通常由大師編寫。

我修改了Mohit Jain的第一個答案。 我有2個版本:

版本1

inline bool compare(const int *beg, const int *end, int v) {
  while (beg <= end) {
    const int* const p = beg + ((end - beg) >> 1);
    const int z = *p;
    if(z != v){
        beg = z > v ? beg : p + 1;
        end = z < v ? end : p - 1;
    }
    else {
        return true;
    }
  }
  return false;
}

版本2

inline bool compare(const int *beg, const int *end, int v) {
  while (beg <= end) {
    const int* const p = beg + ((end - beg) >> 1);
    const int z = *p;
    if(z != v){
        beg = z > v ? beg : p;
        end = z < v ? end : p;
    }
    else {
        return true;
    }
  }
  return false;
}

運行時間

原始版本:2642

Mohits版本:2435

我的版本1:2413

我的第2版:2366

t1:2606

t2:2508

我使用的gcc版本是4.7.2。

t0,t1和t2的結果與OPs結果一致。

對我來說太多了,我的版本2是最快的。 這可能是由於過度擬合(即優化此特定測試集的代碼)而偶然發生的。 另外,我不確定為什么我的版本1比Mohits版本更快。

我測試了各種各樣的東西,並認為我應該發布快速的版本。 要確定某些版本更快的原因,應檢查匯編代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM