Почему этот реализованный двоичный поиск настолько медленнее, чем std:: binary search()?


Прежде чем я обнаружил std:: upper_bound, я реализовал свою собственную версию binarySearch для определения индекса искомого элемента. Реализация работает, но по сравнению с линейным поиском мой binarySearch только немного быстрее. Фактор между моей реализацией и std lib увеличивается по мере роста области поиска.

Для быстрого самотестирования я вставил полный код в конце этого поста. Для быстрого взгляда здесь моя реализация searchBinary:

template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) {
    long iteration = 0;
    size_t leftIndex = 0;
    size_t rightIndex = vectorList.size()-1;
    size_t pos;

    while (leftIndex <= rightIndex) {
        iteration++;
        pos = (leftIndex + rightIndex) / 2;

        if (compareVector < vectorList[pos]) {
            rightIndex = pos - 1;
        } else if (compareVector > vectorList[pos]) {
            leftIndex = pos + 1;
        } else {
            cout << "Match at binary search after " << iteration << " iterations.n";
            return pos;
        }
    }

    cout << "No match at binary search after " << iteration << " iterations.n";
    return -1;
}

И вот как я связываю время выполнения:

void searchBinaryOwn_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
    struct timeval begin, end;
    long seconds, useconds;

    if (gettimeofday(&begin,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    searchBinary(vectorList, compareVector);

    if (gettimeofday(&end,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    seconds = end.tv_sec - begin.tv_sec;
    useconds = end.tv_usec - begin.tv_usec;
    if(useconds < 0) {
        useconds += 1000000;
        seconds--;
    }

    printf("searchBinaryOwn(): %ld sec %ld usecnn", seconds, useconds);
    return;
}

Не вижу здесь никаких проблем. Если я запускаю программу ths с 8 000 000 элементов:

  • searchLinear () занимает ~3,7 сек
  • searchBinaryOwn () занимает ~2,8 сек
  • searchBinaryStd () занимает ~0,0007 сек

Так почему же существует такая огромная разница между обоими бинарными поисками? (составлено с помощью gcc 4.8.2) Примечание: из-за "cout..."занимает около 30 секунд, std:: binarySearch на самом деле намного быстрее, чем отображается

Вот полный код:

#include <iostream>
#include <vector>
#include <sys/time.h>
#include <algorithm>
#include <string>
#include <stdio.h>
using namespace std;


template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) {
    long iteration = 0;
    size_t leftIndex = 0;
    size_t rightIndex = vectorList.size()-1;
    size_t pos;


    while (leftIndex <= rightIndex) {
        iteration++;
        pos = (leftIndex + rightIndex) / 2;

        if (compareVector < vectorList[pos]) {
            rightIndex = pos - 1;
        } else if (compareVector > vectorList[pos]) {
            leftIndex = pos + 1;
        } else {
            cout << "Match at binary search after " << iteration << " iterations.n";
            return pos;
        }
    }

    cout << "No match at binary search after " << iteration << " iterations.n";
    return -1;
}

size_t searchLinear(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
    size_t vectorListSize = vectorList.size();
    for (size_t i = 0; i < vectorListSize; i++) {
        if (vectorList[i] == compareVector) {
            return i;
        }
    }
    return (size_t)-1;
}

void searchLinear_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
    struct timeval begin, end;
    long seconds, useconds;

    if (gettimeofday(&begin,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    //search
    cout << "nPos: " << searchLinear(vectorList, compareVector) << endl;

    if (gettimeofday(&end,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    seconds = end.tv_sec - begin.tv_sec;
    useconds = end.tv_usec - begin.tv_usec;
    if(useconds < 0) {
        useconds += 1000000;
        seconds--;
    }

    printf("searchLinear(): %ld sec %ld usecnn", seconds, useconds);
    return;
}

void searchBinaryStd_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
    struct timeval begin, end;
    long seconds, useconds;

    if (gettimeofday(&begin,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    //search
    cout << "found: " << std::binary_search(vectorList.begin(), vectorList.end(), compareVector) << endl;

    if (gettimeofday(&end,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    seconds = end.tv_sec - begin.tv_sec;
    useconds = end.tv_usec - begin.tv_usec;
    if(useconds < 0) {
        useconds += 1000000;
        seconds--;
    }

    printf("searchBinaryStd(): %ld sec %ld usecnn", seconds, useconds);
    return;
}

void searchBinaryOwn_messure(std::vector<std::vector<u_char> > vectorList, std::vector<u_char> compareVector) {
    struct timeval begin, end;
    long seconds, useconds;

    if (gettimeofday(&begin,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    searchBinary(vectorList, compareVector);

    if (gettimeofday(&end,(struct timezone *)0)) {
        fprintf(stderr, "can not get timen");
        exit(1);
    }

    seconds = end.tv_sec - begin.tv_sec;
    useconds = end.tv_usec - begin.tv_usec;
    if(useconds < 0) {
        useconds += 1000000;
        seconds--;
    }

    printf("searchBinaryOwn(): %ld sec %ld usecnn", seconds, useconds);
    return;
}


int main() {
    std::vector<u_char> compareVector;
    compareVector.clear();
    compareVector.push_back(0xF8);
    compareVector.push_back(0xD1);
    compareVector.push_back(0x11);
    compareVector.push_back(0xFF);

    std::vector<std::vector<u_char> > vectorList;
    vectorList.clear();
    std::vector<u_char> temp;
    for (unsigned int i = 0; i < ((unsigned int)-1); i++) {
        if (i == 8000000) {
//      if (i == 15000000) {
            break;
        }
        temp.clear();
        temp.push_back(0x11);
        temp.push_back(0x22);
        temp.push_back(0x33);
        temp.push_back(0x44);
        vectorList.push_back(temp);
    }

    vectorList[7999999] = compareVector;

    cout << "Elements in vectorList: " << vectorList.size() << endl;

    searchLinear_messure(vectorList, compareVector);
    searchBinaryStd_messure(vectorList, compareVector);
    searchBinaryOwn_messure(vectorList, compareVector);

    return 0;
}
3 2

3 ответа:

  1. измените свой прототип функции на

template<typename T> T searchBinary(const std::vector<std::vector<T> >& vectorList, const std::vector<T>& compareVector) {

Т. е. пройти по постоянной ссылке, а не по значению. Это позволит избежать двух векторных копий.

  1. Вы можете рефакторинговать, используя один условный тест < за итерацию. (Вам также потребуется изменить условное выражение while).

  2. Должен ли iteration быть long? Разве она не может быть короче? Каков худший вариант конвергенции?

Пункт 1 является важным. 2-это очень важно, 3 это микрооптимизация, которая может вообще не иметь значения в некоторых системах.

Векторы передаются в searchBinary по значению, следовательно, будут созданы копии, которые требуют времени.

Если вы измените подпись на

template<typename T> T searchBinary(const std::vector<std::vector<T> >& vectorList, const std::vector<T>& compareVector)

Это так же быстро, как реализация std: http://melpon.org/wandbox/permlink/qozapTfn3MrGv5JA

Ну это template<typename T> T searchBinary(const std::vector<std::vector<T> > vectorList, const std::vector<T> compareVector) делает копию входного вектора (как вы передаете его по значению), который является линейным во времени. Так что результат, который вы получите, на самом деле ожидаемый.

Кстати. однозначный ответ может заключаться в том, что стандартная библиотека написана довольно хорошими разработчиками, и ожидается, что она вряд ли будет превзойдена.