A recent comment to an answer of mine here on Code Review brought up an interesting point. The comment was that one should use std::vector<char>
over std::vector<bool>
in most cases because the standard requires std::vector<bool>
to actually pack bits. I replied that for small vector sizes, the speed wouldn't matter much and for large ones, cache locality would give the advantage to bool
vectors.
However somewhere in between "small" and "large" is a lot of numbers! I wanted to do a test to characterize any std::vector<bool>
advantage. The method I used is fairly simple. I wrote a program that takes 3 arguments:
- minsize = the smallest vector size to test
- maxsize = the largest vector size to test
- steps = the number of steps between those two
Since I wanted to test a large range quickly, and guessing how the vectors would scale, I chose to step through the range logarithmically. Using the output from the test, and normalizing the speed advantage of std::vector<bool>
over std::vector<char>
by subtracting the two times and dividing by the size of the vector yielded this chart on my machine (an older 64-bit Linux machine, using g++ version 5.3.1 for x86_64).
It seems that for a range of sizes in the 2000 to 75000 range, the advantage is with std::vector<char>
but for all other ranges, the two were either identical (to the resolution of my timer) or the std::vector<bool>
had the advantage.
I'm interested in comments on the code or the test method.
booltest.cpp
#include <iostream>
#include <vector>
#include <cstdlib>
#include <cmath>
#include "stopwatch.h"
template <typename F>
struct testfunc {
F *fn;
const char *name;
};
#define TEST(x) { x, #x }
template <typename T>
void vectest(unsigned n)
{
std::vector<T> arr(n, false);
unsigned remaining = n;
static constexpr unsigned incr = 13;
for (unsigned j = 0; j < incr; ++j) {
for (unsigned i = j; i < n && remaining; i += incr) {
if (!arr[i]) {
arr[i] = true;
--remaining;
}
}
}
}
#define SHOW(x) std::cerr << #x << " = " << x << "\n"
int main(int argc, char *argv[])
{
const testfunc<decltype(vectest<bool>)> test[]{
TEST(vectest<char>),
TEST(vectest<bool>),
};
if (argc < 4) {
std::cerr << "Usage: booltest minsize maxsize steps\n";
return 0;
}
unsigned min = std::stod(argv[1]);
unsigned max = std::stod(argv[2]);
unsigned steps = std::stod(argv[3]);
double logmin = std::log10(min);
double logmax = std::log10(max);
double step = (logmax - logmin)/steps;
SHOW(min);
SHOW(max);
SHOW(steps);
// print header data
std::cout << "\"n\"";
for (const auto t : test) {
std::cout << ", \"" << t.name << "\"";
}
std::cout << "\n";
for (unsigned i = 0; i < steps; ++i) {
unsigned val = std::pow(10, logmin + i * step);
std::cout << val;
for (const auto t : test) {
std::cout << ", " << timeit<>(t.fn, val);
}
std::cout << std::endl;
}
}
x[2] = true;
is guaranteed to work because operator[]
returns areference
subclass that does this. See en.cppreference.com/w/cpp/container/vector_bool/reference \$\endgroup\$