为什么C语言能够比Go或D语言更快地构建小型程序?

huangapple go评论102阅读模式
英文:

Why C builds small programs faster than Go or D?

问题

Go和D都声称拥有非常快的编译器,这是由于它们自身语言的现代设计,考虑到并发的单遍解析。

考虑到大多数构建时间都浪费在链接阶段,我想知道为什么gcc在小程序上仍然更快。

C代码:

#include <stdio.h>    
int main() {
    printf("Hello\n");
}

执行时间:

$ time gcc hello.c
real    0m0.724s
user    0m0.030s
sys     0m0.046s

D代码(惯用写法):

import std.stdio;
void main() {
    writeln("Hello\n");
}

执行时间:

$ time dmd hello.d
real    0m1.620s
user    0m0.047s
sys     0m0.015s

D代码(使用hack):

import core.stdc.stdio;
void main() {
    printf("Hello\n");
}

执行时间:

$ time dmd hello.d
real    0m1.593s
user    0m0.061s
sys     0m0.000s

$ time dmd -c hello.d
real    0m1.203s
user    0m0.030s
sys     0m0.031s

Go代码:

package main
import "fmt"
func main() {
    fmt.Println("Hello.")
}

执行时间:

$ time go build hello.go
real    0m2.109s
user    0m0.016s
sys     0m0.031s

Java代码:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello.");
    }
}

执行时间:

$ time javac Hello.java
real    0m1.500s
user    0m0.031s
sys     0m0.031s
英文:

Go and D advertised to have incredibly fast compilers.
Due to a modern design of the language itself with concurrent single-pass parsing in mind.

With an understanding that most of the build time wasted in linking phase. I wonder why gcc still faster on small programs.

C

#include &lt;stdio.h&gt;    
int main() {
    printf(&quot;Hello\n&quot;);
}

<pre>
$ time gcc hello.c
real 0m0.724s
user 0m0.030s
sys 0m0.046s
</pre>

D

Idiomatic

import std.stdio;
void main() {
    writeln(&quot;Hello\n&quot;);
}

<pre>
$ time dmd hello.d

real 0m1.620s
user 0m0.047s
sys 0m0.015s
</pre>

With hacks

import core.stdc.stdio;
void main() {
    printf(&quot;Hello\n&quot;);
}

<pre>
$ time dmd hello.d
real 0m1.593s
user 0m0.061s
sys 0m0.000s

$ time dmd -c hello.d
real 0m1.203s
user 0m0.030s
sys 0m0.031s
</pre>

Go

package main
import &quot;fmt&quot;
func main() {
    fmt.Println(&quot;Hello.&quot;)
}

<pre>
$ time go build hello.go
real 0m2.109s
user 0m0.016s
sys 0m0.031s
</pre>

Java

public class Hello {
    public static void main(String[] args) {
        System.out.println(&quot;Hello.&quot;);
    }
}

<pre>
$ time javac Hello.java
real 0m1.500s
user 0m0.031s
sys 0m0.031s
</pre>

答案1

得分: 9

运行compiler filename实际上仍然会运行链接器,并且可能会将大量标准库复制到生成的可执行文件中(特别是对于D和Go来说,它们默认静态链接其语言运行时以获得更好的兼容性)。

给定这个简单的D语言Hello World程序:

import std.stdio;
void main() { writeln("hello world"); }

让我在我的电脑上展示一些时间:

$ time dmd hello.d

real    0m0.204s
user    0m0.177s
sys     0m0.025s

与使用-c跳过链接步骤相比,意味着“编译,不链接”:

$ time dmd -c hello.d

real    0m0.054s
user    0m0.048s
sys     0m0.006s

将时间缩短到了第一次运行的四分之一 - 在这个小程序中,几乎有四分之三的编译时间实际上是链接时间。

现在,让我稍微修改一下程序:

import core.stdc.stdio;
void main() { printf("hello world\n"); }

$ time dmd -c hello.d

real    0m0.017s
user    0m0.015s
sys     0m0.001s

通过使用printf而不是writeln,时间减少了一半!我会回到这个问题。

为了进行比较,编译+链接:

$ time dmd hello.d

real    0m0.099s
user    0m0.083s
sys     0m0.014s

这给我们一个大致的了解:

  • 链接器占用了大部分时间。使用-c可以将其排除在外。

  • 解析标准库也需要很多时间。仅使用C函数而不是D库可以消除这一点,并提供更为直接的比较。

  • 但是,使用stdlib对于观察可扩展性很重要。

D语言(我推测,Go也是如此,但我对它们了解不多)的目标是减少编译中等到大型程序的时间。小程序已经很快 - 等待几分之一秒(或者在较慢的计算机上可能是一两秒钟,在我现在使用的计算机上有一个不错的SSD,这将加快速度,在旋转硬盘上运行相同的命令大约会加倍时间!)对于一个小的构建来说并不是什么大问题。

等待几分钟来构建一个大型项目是一个问题。如果我们可以将其缩短到几秒钟,那将是一个重大的胜利。

编译100,000行的时间比编译10行的时间更重要。因此,初始化时间并不重要。链接时间很重要,但编译器本身对此没有太多作为(链接器是由独立团队编写的单独程序,尽管也有改进链接器的努力)。

因此,D语言构建包括标准库所花费的时间是令人印象深刻的。比C语言的Hello World慢(因为C编译器在较小的库上做的工作较少),但你已经看到与C++ Hello World相比的好处,后者每行更慢,并且在每次构建时也倾向于有更多的工作要做(解析#include等)。

一个好的编译器基准测试应该要隔离这些问题,并更多地测试可扩展性,而不仅仅是小程序。尽管如果你正确运行测试以确保公平比较,D语言在小程序上也表现得非常好。

英文:

Running compiler filename actually still runs the linker and may copy in a good amount of the standard library to the generated executable (especially hurting D and Go, which static link their language runtimes by default for better compatibility).

Given this trivial D hello world:

import std.stdio;
void main() { writeln(&quot;hello world&quot;); }

Let me show you some timings on my computer:

$ time dmd hello.d

real    0m0.204s
user    0m0.177s
sys     0m0.025s

Contrast to skipping the link step with -c, which means "compile, do not link":

$ time dmd -c hello.d

real    0m0.054s
user    0m0.048s
sys     0m0.006s

Cuts down the time to about 1/4 the first run - in this small program, nearly 3/4 of the compile time is actually linking.

Now, let me modify the program a bit:

import core.stdc.stdio;
void main() { printf(&quot;hello world\n&quot;); }

$ time dmd -c hello.d

real    0m0.017s
user    0m0.015s
sys     0m0.001s

Cut in half by using printf instead of writeln! I'll come back to this.

And, just for comparison's sake, compile+link:

$ time dmd hello.d

real    0m0.099s
user    0m0.083s
sys     0m0.014s

This gives us an idea of what's going on:

  • the linker eats a good chunk of time. Using -c removes it from the equation.

  • parsing the standard library also takes a good chunk of time. Using just the C function instead of the D lib cuts that out and gives a more apples-to-apples look.

  • But, using the stdlib is important to see the scalability thing.

What D (and I presume, Go, but I don't know much about them) aims to do is reduce the time to compile medium to large programs. Small programs are already fast - waiting for a fraction of a second (or perhaps one or two on slower computers, the one I'm on now has a nice SSD on it which will speed this up, running the same command on a spinning hard disk about doubles the time!) for a small build isn't a big deal.

Waiting several minutes for a big build is a problem though. If we can cut that down to several seconds, it is a major win.

The time to compile 100,000 lines is more important than the time to compile 10. So init times aren't really important. Link times matter, but there's not much the compiler itself does about that (the linker is a separate program written by separate teams, though there are efforts to improve that too elsewhere).

So the time D takes to build including the standard library is where it gets impressive. Slower than a C hello world (because the C compiler does less work with the smaller lib), but you already see benefits over a C++ hello world, which is slower per line and also tends to have more work to do on each build (parsing #includes, etc).

A good compiler benchmark would want to isolate these issues and test scalability more than small programs. Though D does very well on small programs too if you run the test right to ensure a fair comparison.

huangapple
  • 本文由 发表于 2016年3月18日 00:30:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/36066447.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定