为什么g ++和clang ++之间的编译时间差异很大?

Hon*_*hen 9 c++ gcc clang

我遇到了一个谈话幻灯片,在第12页有一个例子说明了存在继承时类型检查的难度.

struct a {
  typedef int foo;
};
struct a1 : a {};
struct a2 : a {};
#define X(b, a)                \
  struct a##1 : b##1, b##2 {}; \
  struct a##2 : b##1, b##2 {};
X(a, b) X(b, c) X(c, d) X(d, e) X(e, f) X(f, g) X(g, h) X(h, i) X(i, j) X(j, k)
    X(k, l) X(l, m) X(m, n) n1::foo main() {}
Run Code Online (Sandbox Code Playgroud)

预处理后,它转换为:

struct a {
  typedef int foo;
};
struct a1 : a {};
struct a2 : a {};

struct b1 : a1, a2 {};
struct b2 : a1, a2 {};
struct c1 : b1, b2 {};
struct c2 : b1, b2 {};
struct d1 : c1, c2 {};
struct d2 : c1, c2 {};
struct e1 : d1, d2 {};
struct e2 : d1, d2 {};
struct f1 : e1, e2 {};
struct f2 : e1, e2 {};
struct g1 : f1, f2 {};
struct g2 : f1, f2 {};
struct h1 : g1, g2 {};
struct h2 : g1, g2 {};
struct i1 : h1, h2 {};
struct i2 : h1, h2 {};
struct j1 : i1, i2 {};
struct j2 : i1, i2 {};
struct k1 : j1, j2 {};
struct k2 : j1, j2 {};
struct l1 : k1, k2 {};
struct l2 : k1, k2 {};
struct m1 : l1, l2 {};
struct m2 : l1, l2 {};
struct n1 : m1, m2 {};
struct n2 : m1, m2 {};
n1::foo main() {}
Run Code Online (Sandbox Code Playgroud)

当我使用g ++(Debian 4.9.1-16)编译它时,它确实消耗了大量时间:

$ time g++ type_check.cc
real    29.35s
user    29.34s
sys     0.00s
Run Code Online (Sandbox Code Playgroud)

而clang ++(clang版本3.4.2(标签/ RELEASE_34/dot2-final),x86_64-unknown-linux-gnu)的速度要快得多.

$ time clang++ type_check.cc

real    0.06s
user    0.04s
sys     0.00s
Run Code Online (Sandbox Code Playgroud)

为什么检查多重继承的两侧的foo类型是否与 gcc/icc相比花费了这么多时间,而不是为了clang?

Gam*_*per 5

我想这是因为过早的优化而且是一个错误.

我认为GCC跟踪继承并且像Clang memoize基类一样,在解析"下一个基类"时立即发现模糊性,但与Clang GCC不同,如果没有成员则跳过这个fase(但是我认为它应该考虑使用typedef作为成员) )

证据:

改变结构

struct a {
    int a; //add this
    typedef int foo;
};
Run Code Online (Sandbox Code Playgroud)

和代码编译速度最快(至少相同的数量级)Clang.

在GCC 4.8.0/4.8.1/4.9.0上测试

编辑:

因为@HongxuChen现在正在编辑我正在编辑的更多信息:这些都是个人考虑因素,可能也不正确,但对我来说似乎是合理的.

我并不感到惊讶编译时间如此深入的继承树爆炸而没有记忆.

如果你试图添加,基本上你有指数复杂性

X(a, b) X(b, c) X(c, d) X(d, e) X(e, f) X(f, g) X(g, h) X(h, i) X(i, j) X(j, k)
X(k, l) X(l, m) X(m, n) 
X(n, o)                 //add this one
o1::foo main() {}
Run Code Online (Sandbox Code Playgroud)

你会发现编译时间现在是两次.

每次编译器执行两次检查时,它的增长速度并不快,特别是考虑到内部编译器执行的复杂操作可能需要数千甚至更多,汇编指令和缓存未命中.

在你的情况下我们有2 ^ 14个操作(两次2 ^ 13).假设2Ghz单核意味着在39秒内进行16.384次操作,这意味着420次操作/秒,因此需要4.7%的指令/操作.

似乎有470万条指令/操作是如此之多,但并非如此.将编译时间增加几秒钟非常容易.

强迫一个场景,Clang和GCC都不能使用memoization并看到哪个更快,但我不知道如何做到这一点,无论如何他们应该使用memoization来保存大部分工作.

注意:我知道我没有直接调查GCC代码,这对GCC开发人员来说也许是一项繁重的任务.我在编写解析器和元编译器方面经验最少,因此我部分地面对编译过程中可能出现的最常见问题(不是直接GCC),其他用户肯定比我更有资格回答这个问题.