在C或C++中检查边界是否昂贵?

JAC*_*K M 10 c c++

绑定检查很昂贵(> x2运行时开销)

我从我的一位教授那里得到了这一点.我很困惑.据我所知,程序中最耗时的部分是IO(来自网络和硬盘).

但是用C或C++检查边界并不总是与那两个输入源相关.例如,我使用C将一个buff的内容复制到另一个buff memcpy(dest, src, length(src)).在此之前,我检查大小src以防止堆溢出.我可以成像的进程是:获取起始地址src\x00字节src(在汇编语言的视图中,我src逐个复制内容并查看字节是否等效\x00).获得2地址后,只需减去它们即可获得长度src.我src从内存中读到了内容.我们都知道从记忆中读取东西很快.

Meh*_*dad 10

我刚刚运行了一个程序,启用了迭代器边界检查.

运行时间从789毫秒到2608毫秒.

是的,这很重要.不是所有的时间,但肯定比从来没有.

特别是,绑定检查的迭代器至少需要两倍于简单指针的存储空间,而且,不容易优化.从理论上讲,它们简单而有效,但在实践中,你根本不想做你不需要的工作.

哦,我提到编译时间从7.72秒到13.21秒也是如此?


对于你们中间的许多非信徒来说......一个微型例子需要0.92秒而没有边界检查1.96秒.


因为对所有事情都抱有很多怀疑,包括vector效率......这是另一个:

#include <cstdio>
#include <ctime>

template<class T> struct Vector
{
    T *b, *e;
    Vector(size_t n) : b(new T[n]), e(b + n) { }
    T &operator[](size_t i) { return b[i]; }
    T &at(size_t i) { if (i >= e - b) { throw "invalid"; } return b[i]; }
};

#define at operator[]  // Comment this out to enable bounds-checking

int main(int argc, char **argv)
{
    Vector<size_t> v(1 << 16);
    for (size_t *p = v.b; p != v.e; ++p) { *p = 1; }
    clock_t begin = clock();
    for (int j = 0; j < 1 << 12; ++j)
    {
        for (size_t i = 8, n = v.e - v.b; i < n; ++i)
        {
            v.at(i) += v.at(i - 8);
            v.at(i) ^= v.at(i - 7);
            v.at(i) -= v.at(i - 6);
            v.at(i) ^= v.at(i - 5);
            v.at(i) += v.at(i - 4);
            v.at(i) ^= v.at(i - 3);
            v.at(i) -= v.at(i - 2);
            v.at(i) ^= v.at(i - 1);
        }
    }
    clock_t end = clock();
    fprintf(stderr, "%u\n", clock() - begin);
}
Run Code Online (Sandbox Code Playgroud)

2.09秒0.88秒.

  • @ThomasMatthews:是的,在那些应用程序中,你首先不会问这个问题. (6认同)
  • 在许多应用中,例如安全关键,无论额外的速度或空间如何,都需要进行边界检查. (5认同)
  • 我会提到_why_它也会让它变慢.具体来说,带有`-Ofast`的GCC 4.9展开未经检查的代码,但不是已检查的代码.传入[`-fno-tree-loop-optimize`](http://mailman.cs.huji.ac.il/pipermail/linux-il/2009-December/003402.html),他们突然在同一个地方运行速度(由于一些额外的错过优化,速度甚至比最初检查的代码慢得多). (5认同)
  • @Mehrdad你所展示的并不是那种绑定检查会造成2倍的减速; 你已经证明它可以_prevent_你正确指出的2倍加速是一个有限的循环子集.同样,最终结果在_this_特定代码中是相同的,但不一定在没有特别优化的情况下检查失败.在任何情况下,"可以"是有限用途的度量; 一个人可以在有或没有绑定检查的情况下使代码任意变坏.绑定检查可能会使代码在常见情况下变慢(即重要的),但这并未证明这一点. (5认同)
  • @StuartOlsen:有趣的是,这个特定的例子可能就是这种情况,但正如你可能已经意识到的那样,它只是一个错过特定优化的例子,而作为一般规则,即使没有特定/特定的优化,边界检查也会慢得多.我最初运行它的项目比任何只从单一优化中获益的任何东西都要复杂得多; 这个例子的唯一目的是一劳永逸地证明这个场景并不像人们似乎相信的那样牵强,事实上它发生在非常简单的程序中. (2认同)
  • @StuartOlsen:我的意思是,我在答案中特别提到绑定检查代码更难以优化,这恰好针对您刚才描述的那种情况.优化器变得越来越好并不重要 - 它们不能解决停机问题.并不是所有的循环都能做出很好的i ++增量.制作一个编译器无法优化的循环并不是很难,但是我不会浪费时间去做,因为它错过了我试图用答案做出的更大的一点:绑定检查*可以*确实导致2x减速作为结果. (2认同)
  • @StuartOlsen:您可以随心所欲地说出来,最终结果就是最终结果.减慢速度并阻止其加速对我来说没有任何有意义的差异.对我来说,节省的一分钱就是一分钱.你可以不同意. (2认同)
  • @Mehrdad至于优化者:我可以想到许多"重要"的事情,而且它们中的大多数都无法解决停顿问题.优化者就是其中之一.至于最终结果:是的,这是最终结果 - 只有那样.在一般情况下,它完全表明绑定支票的性能影响并不明显.要非常清楚,我并不反对它表明绑定的检查可能会导致减速.我只是说这种演示对解决这个广泛的问题没什么作用. (2认同)
  • @StuartOlsen:好吧,然后忽略这个演示.我认为这是令人信服的,但显然你没有,而且我并不打算说服世界上每一个人(有些人不能被任何事情说服,期间).事实上,我甚至没有真正拥有它,因为我认为没有必要(或足够).这只是一个补充,我欢迎你忽略它,如果你不认为它支持答案.这并不是说我可以强迫你得出一个与你想要绘制的结论不同的结论. (2认同)

wal*_*lyk 3

直到 20 世纪 80 年代之前,情况一直如此。

借助现代代码生成和高度流水线化的 CPU 架构,可以在零或很少的额外执行成本下完成边界检查。这是因为边界检查可以与内存获取并行发生。

  • 我也不想在这里展开全面的讨论,但“比 1980 年更好”并不意味着什么。既不是“它无法改进”,也不是“它与整个程序相比没有什么区别”。 (6认同)
  • 另外,“可以并行发生”并不一定意味着编译器总是可以成功地调度指令,使其“确实并行发生”......即使它可以很好地调度它们,事实是 `array[n] = v` 通常可以在没有边界检查的情况下编译为单个指令,但可能需要六个或更多的边界检查,并且必须执行和退出这些附加指令的某些子集,这会导致运行时间显着增加,无论其中一部分可以并行运行也可以不并行运行... (3认同)