Подтвердить что ты не робот

С++ 11 проблема с производительностью <bool> (с примером кода)

Я заметил, что вектор работает намного медленнее, чем массив bool при запуске следующего кода.

int main() 
{
    int count = 0;
    int n = 1500000;
    // slower with c++ vector<bool>
    /*vector<bool> isPrime;
    isPrime.reserve(n);
    isPrime.assign(n, true);
    */
    // faster with bool array 
    bool* isPrime = new bool[n];

    for (int i = 0; i < n; ++i)
        isPrime[i] = true;


    for (int i = 2; i< n; ++i) {
        if (isPrime[i])
            count++;
        for (int j =2; i*j < n; ++j )
            isPrime[i*j] = false;
    }

    cout <<  count << endl;
    return 0;
}

Есть ли способ, который я могу сделать, чтобы сделать vector<bool> быстрее? Btw, как std::vector::push_back, так и std::vector::emplace_back еще медленнее, чем std::vector::assign.

4b9b3361

Ответ 1

vector<bool> может иметь специализацию шаблона и может быть реализован с использованием битового массива для экономии места. Извлечение и сохранение бит и преобразование его из/в bool может привести к падению производительности, которое вы наблюдаете. Если вы используете std::vector::push_back, вы изменяете размер вектора, который приведет к еще худшей производительности. Следующий убийца производительности может быть assign (Худшая сложность: Линейный первый аргумент), вместо этого используйте operator [] (Сложность: константа).

С другой стороны, bool [] гарантированно является массивом bool.

И вы должны изменить размер до n вместо n-1, чтобы избежать поведения undefined.

Ответ 2

std::vector<bool> может иметь различные проблемы с производительностью (например, посмотрите https://isocpp.org/blog/2012/11/on-vectorbool).

В общем вы можете:

  • используйте std::vector<std::uint8_t> вместо std::vector<bool> (попробуйте std::valarray<bool> также.)

    Для этого требуется больше памяти и меньше кэш-памяти, но для доступа к одному значению нет накладных расходов (в виде манипуляций с битами), поэтому есть ситуации, в которых он работает лучше (в конце концов, это похоже на ваш массив bool, но без вреда управления памятью)

  • используйте std::bitset, если во время компиляции вы знаете, насколько велик ваш логический массив (или если вы можете хотя бы установить разумная верхняя граница)
  • Если Boost является опцией try boost::dynamic_bitset (размер может быть указан во время выполнения)

Но для оптимизации скорости вам нужно проверить...

В вашем конкретном примере я могу подтвердить разницу в производительности только тогда, когда оптимизация отключена (конечно, это не путь).

Некоторые тесты с g++ v4.8.3 и clang++ v3.4.5 в системе Intel Xeon (уровень оптимизации -O3) дают другое изображение:

                    time (ms)
                 G++      CLANG++
array of bool    3103     3010
vector<bool>     2835     2420    // not bad!
vector<char>     3136     3031    // same as array of bool
bitset           2742     2388    // marginally better

(время, прошедшее за 100 прогонов кода в ответе)

std::vector<bool> не выглядит так плохо (исходный код здесь).

Ответ 3

vector<bool> может быть высокой, но не обязательно. Для того чтобы vector<bool> был эффективным, он должен работать на многих баллах одновременно (например, isPrime.assign(n, true)), и разработчику пришлось вложить в него любящую заботу. Индексирование отдельных bools в vector<bool> происходит медленно.

Вот основной поиск, который я написал некоторое время назад, используя vector<bool> и clang + libС++ (важна часть libС++):

#include <algorithm>
#include <chrono>
#include <iostream>
#include <vector>

std::vector<bool>
init_primes()
{
    std::vector<bool> primes(0x80000000, true);
    primes[0] = false;
    primes[1] = false;
    const auto pb = primes.begin();
    const auto pe = primes.end();
    const auto sz = primes.size();
    size_t i = 2;
    while (true)
    {
        size_t j = i*i;
        if (j >= sz)
            break;
        do
        {
            primes[j] = false;
            j += i;
        } while (j < sz);
        i = std::find(pb + (i+1), pe, true) - pb;
    }
    return primes;
}

int
main()
{
    using namespace std::chrono;
    using dsec = duration<double>;
    auto t0 = steady_clock::now();
    auto p = init_primes();
    auto t1 = steady_clock::now();
    std::cout << dsec(t1-t0).count() << "\n";
}

Это выполняется для меня примерно за 28 секунд (-O3). Когда я меняю его, чтобы вернуть vector<char>, время выполнения увеличивается примерно до 44.

Если вы запустите это, используя некоторые другие std:: lib, вы, вероятно, не увидите эту тенденцию. В libС++ алгоритмы, такие как std::find, были оптимизированы для поиска слова бит за раз, а не бит за раз.

Подробнее о том, какие алгоритмы std могут быть оптимизированы вашим поставщиком, см. http://howardhinnant.github.io/onvectorbool.html.