绑定检查成本高昂 (> x2 倍运行时开销)
我从我的一位教授那里得到了这一点。我对此很困惑。 据我所知,程序中最耗时的部分是IO(来自网络和来自硬盘)。
但是 C 或 C++ 中的边界检查并不总是与这两个输入源相关。 例如,我使用
memcpy(dest, src, length(src))
将一个 buff 的内容复制到 C 中的另一个。在此之前,我检查 src
的大小以防止堆溢出。我能想象的过程是:获取src
的起始地址和\x00
中的src
字节(从汇编语言的角度来看,我将src
的内容一一复制,看看该字节是否相当于 \x00
)。得到2个地址后,只需将它们相减即可得到src
的长度。我从记忆中读出了src
的内容。我们都知道从记忆中读取内容很快。
我刚刚运行了一个程序,并且打开了迭代器边界检查。
运行时间从 789 毫秒变为 2608 毫秒。
所以,是的,这很重要。并非总是如此,但肯定比从未有过。
特别是,边界检查迭代器需要的存储空间至少是简单指针的两倍,而且不容易优化。理论上它们简单高效,但实际上您只是不想做不需要的工作。
哦,我有没有提到编译时间也从 7.72 秒缩短到 13.21 秒?
对于你们当中许多非信徒...
这个微型示例在没有边界检查的情况下需要 130 毫秒,而在使用时则需要 300 毫秒:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
template<class T>
struct Vector
{
T *b, *e;
typedef T &reference;
Vector(size_t n) : b(new T[n]), e(b + n) { }
template<bool Checked>
T &at(size_t i)
{
if constexpr (Checked)
{ if (i >= e - b) { abort(); } }
return b[i];
}
};
template<bool Checked>
void test(int N, size_t x, Vector<size_t> &v, bool output)
{
for (size_t *p = v.b; p != v.e; ++p) { *p = 1; }
clock_t begin = clock();
for (int j = 0; j < N; ++j)
{
for (size_t k = 8, n = v.e - v.b; k < n; ++k)
{
size_t i = k ^ x;
v.at<Checked>(i) += v.at<Checked>(i - 8);
v.at<Checked>(i) ^= v.at<Checked>(i - 7);
v.at<Checked>(i) -= v.at<Checked>(i - 6);
v.at<Checked>(i) ^= v.at<Checked>(i - 5);
v.at<Checked>(i) += v.at<Checked>(i - 4);
v.at<Checked>(i) ^= v.at<Checked>(i - 3);
v.at<Checked>(i) -= v.at<Checked>(i - 2);
v.at<Checked>(i) ^= v.at<Checked>(i - 1);
}
}
clock_t end = clock();
if (output)
{
fprintf(
stderr, "%-9s: %u ms\n",
Checked ? "Checked" : "Unchecked",
static_cast<unsigned int>((clock() - begin) * 1000ULL / CLOCKS_PER_SEC));
}
}
int main(int argc, char **argv)
{
size_t x = argc - 1;
int N = (1 << 11) + x;
Vector<size_t> v((1 << 15) + (x ^ 0x100));
test<true>(N, x, v, false); test<false>(N, x, v, false); // Warm-up round
test<true>(N, x, v, true); test<false>(N, x, v, true);
test<true>(N, x, v, true); test<false>(N, x, v, true);
test<true>(N, x, v, true); test<false>(N, x, v, true);
}
借助现代代码生成和高度流水线化的 CPU 架构,可以在零或很少的额外执行成本下完成边界检查。 这是因为边界检查可以与内存获取并行发生。