在思考这个问题时,我开始怀疑std::copy()
和/或std::fill
是否针对std::vector<bool>
进行了专门化(我的意思是优化)。
这是C++标准所要求的,还是C++标准库供应商的常见方法?
简单来说,我想知道是否有以下代码:
std::vector<bool> v(10, false);
std::fill(v.begin(), v.end(), true);
在任何方面都比这更好/不同:
std::vector<bool> v(10, false);
for (auto it = v.begin(); it != v.end(); ++it) *it = true;
非常严格地说,可以说:std::fill<std::vector<bool>::iterator>()
进入std::vector<bool>
的内部表示,并设置它们的整个字节而不是单个比特吗?我想让std::fill
成为std::vector<bool>
的朋友对图书馆供应商来说不是什么大问题吧?
[更新]
下一个相关问题:如果还没有专门化的话,我(或其他任何人:)可以专门化std::vector<bool>
这样的算法吗?C++标准允许这样做吗?我知道这将是不可移植的,但只是为一个选定的std C++库?假设我(或其他任何人)找到了一种方法来获得std::vector<bool>
私有部分。
STD是仅头文件库,它与编译器一起提供。你可以自己查看这些标题。对于GCC的CCD_ 10,阻抗在CCD_ 11中。对于其他编译器来说,它可能也是相同的文件。是的,有专门的fill
(看看__fill_bvector
附近)。
标准中没有强制要求优化。如果可以应用优化,则认为这是一个"实施质量"问题。然而,大多数算法的渐近复杂性是有限的。
只要一个正确的程序按照标准的要求运行,就允许进行优化。您询问的示例,即涉及在std::vector<bool>
上使用迭代器的标准算法的优化,几乎可以以实现认为合适的任何方式实现其目标,因为无法监控它们是如何实现的。也就是说,我非常怀疑是否有任何标准的库实现优化std::vector<bool>
上的操作。大多数人似乎认为这种专业化首先是令人憎恶的,应该取消。
只有当专门化涉及至少一个用户定义的类型时,才允许用户创建库类型的专门化。我认为根本不允许用户在名称空间std
中提供任何函数:没有任何需求,因为所有这些函数都涉及用户定义的类型,因此可以在用户的名称空间中找到。公式不同:我认为你暂时在为std::vector<bool>
优化算法方面运气不佳。然而,您可能会考虑为开源实现贡献优化版本(例如,libstdc++
和libc++
)。
它没有专门化,但你仍然可以使用它。(即使它很慢)
但我发现了一个技巧,它使用代理类std::_Vbase
在std::vector<bool>
上启用std::fill
。
(警告:我只为MSVC2013测试过它,所以它可能无法在其他编译器上运行。)
int num_bits = 100000;
std::vector<bool> bit_set(num_bits , true);
int bitsize_elem = sizeof(std::_Vbase) * 8; // 1byte = 8bits
int num_elems = static_cast<int>(std::ceil(num_bits / static_cast<double>(bitsize_elem)));
这里,由于如果使用元素的任意位,则需要元素的整位,因此元素的数量必须四舍五入。
使用这些信息,我们将构建一个指针向量,该向量指向位下面的原始元素。
std::vector<std::_Vbase*> elem_ptrs(num_elems, nullptr);
std::vector<bool>::iterator bitset_iter = bit_set.begin();
for (int i = 0; i < num_elems; ++i)
{
std::_Vbase* elem_ptr = const_cast<std::_Vbase*>((*bitset_iter)._Myptr);
elem_ptrs[i] = elem_ptr;
std::advance(bitset_iter, bitsize_elem);
}
(*bitset_iter)._Myptr
:通过取消引用std::vector<bool>
的迭代器,可以访问代理类reference
及其成员_Myptr
。
由于std::vector<bool>::iterator::operator*()
的返回类型为const std::_Vbase*
,通过const_cast
移除其常量。
现在我们得到一个指针,它指向那些位下面的原始元素std::_Vbase* elem_ptr
。
elem_ptrs[i] = elem_ptr
:记录此指针,。。。
std::advance(bitset_iter, bitsize_elem)
:。。。继续我们的旅程,通过跳跃前一个元素所持有的比特来寻找下一个元素。
std::fill(elem_ptrs[0], elem_ptrs[0] + num_elems, 0); // fill every bits "false"
std::fill(elem_ptrs[0], elem_ptrs[0] + num_elems, -1); // fill every bits "true"
现在,我们可以在指针的矢量上使用std::fill
,而不是比特的矢量。
也许有些人可能会对外部使用代理类感到不舒服,甚至会消除它的不确定性
但如果你不在乎这一点,想要快速的东西,这是最快的方法。
我在下面做了一些比较。(制作了新项目,没有更改配置,发布,x64)
int it_max = 10; // do it 10 times ...
int num_bits = std::numeric_limits<int>::max(); // 2147483647
std::vector<bool> bit_set(num_bits, true);
for (int it_count = 0; it_count < it_max; ++it_count)
{
std::fill(elem_ptrs[0], elem_ptrs[0] + num_elems, 0);
} // Elapse Time : 0.397sec
for (int it_count = 0; it_count < it_max; ++it_count)
{
std::fill(bit_set.begin(), bit_set.end(), false);
} // Elapse Time : 18.734sec
for (int it_count = 0; it_count < it_max; ++it_count)
{
for (int i = 0; i < num_bits; ++i)
{
bit_set[i] = false;
}
} // Elapse Time : 21.498sec
for (int it_count = 0; it_count < it_max; ++it_count)
{
bit_set.assign(num_bits, false);
} // Elapse Time : 21.779sec
for (int it_count = 0; it_count < it_max; ++it_count)
{
bit_set.swap(std::vector<bool>(num_bits, false)); // You can not use elem_ptrs anymore
} // Elapse Time : 1.3sec
有一点需要注意。当你用另一个swap()
原始向量时,指针的向量就变得无用了!
23.2.5 C++国际标准的类向量甚至告诉我们
为了优化空间分配,提供了布尔元素向量的专门化:
之后提供比特集专用化。就vector<bool>
的标准而言,供应商需要使用位集来实现它,以优化空间。优化空间是有代价的,而不是优化速度。
如果从图书馆拿到一本书比在所有用容器紧紧钉在一起的图书馆书籍之间找到一本书更容易。。。。
举个例子,您正试图从头到尾做一个std::fill
或std::copy
。但情况并非总是如此,有时它并不只是简单地映射到整个字节。所以,这在速度优化方面有点问题。在这种情况下,你很容易将每一位都更改为一,这只是将字节更改为0xF,但这里的情况并非如此;如果只更改某个字节的某些位,则会变得更加困难。然后,您需要实际计算字节的大小;这不是一件小事*,或者至少不是当前硬件上的原子操作。
这是一个过早的优化故事,它在空间方面很好,但在性能方面很糟糕。
有一张"is a multiple of 8 bits"
支票值得开销吗?我对此表示怀疑。
*我们在这里谈论的是多个比特,对于只有一个比特的情况,你当然可以做一个比特运算