Golang与Java的速度比较

6
我可以协助您进行翻译。以下是需要翻译的内容:

我已经用Java和Go编写了一个程序。我的Java程序执行大约需要5.95秒,而Go程序执行需要大约41.675789791秒。虽然Go的速度与C或C++相当,因为它像C一样编译,那么为什么会存在这么大的性能差异呢?程序如下:

Go程序

package main


import (
    "math"
    "fmt"
    "time"
)

func main() {
    fmt.Printf("vvalue is %v", testFun(10, 16666611, 1000000000))
}

func fun(x float64) float64 {
    return math.Pow(x, 2) - x
}

func testFun(first float64, second float64, times int) float64 {
    var i = 0
    var result float64 = 0
    var dx float64
    dx = (second - first) / float64(times)
    for ; i < times; i++ {
        result += fun(first + float64(i) * dx)
    }
    return result * dx
}   

Java程序
public class Test {
public static void main(String[] args) {
    Test test = new Test();
    double re = test.testFun(10, 16666611, 1000000000);
    System.out.println(re);
}

private double testFun(double first, double second, long times) {
    int i = 0;
    double result = 0;
    double dx = (second - first) / times;
    for (; i < times; i++) {
        result += fun(first + i * dx);
    }
    return result * dx;
}

private double fun(double v) {
    return Math.pow(v, 2) - v;
}
}

8
Java使用JIT(Just In Time)编译器进行优化,这意味着它会生成针对其所运行的机器特定的机器代码,因此不要仅仅认为Java很慢(较慢)。 - Mark Rotteveel
5
除非你的程序大部分执行时间用于计算指数,否则这只是一场徒劳的追逐。编写一个真正的程序,进行基准测试,使用性能分析工具找出热点,并对其进行优化,重新运行基准测试,反复进行此操作。这种微型基准测试并不能代表任何实际情况。 - Adrian
4个回答

13
(在上面答案的提示下,我进行了更多测试,加入了一个额外的C版本)

在我的Linux机器上,当times=100000000时。

测试结果:

  • 当指数= 2.4
 Java: result: 1.053906e+24, during: 7432 ms
    C: result: 1.053906e+24, during: 5544 ms
   Go: result: 1.053906e+24, during: 8.716807708s
当指数为2时,仍然使用pow()Pow()
 Java: result: 1.543194e+21, during: 630 ms
    C: result: 1.543194e+21, during: 852 ms
   Go: result: 1.543194e+21, during: 3.336549272s
当指数等于2时,使用x * x代替。
 Java: result: 1.543194e+21, during: 636 ms
    C: result: 1.543194e+21, during: 340 ms
   Go: result: 1.543194e+21, during: 115.491272ms
  • go 通常非常快,根据最后一次测试,比 Java,甚至 C 都要快。
  • 然而,根据前两个测试,Java 确实有一个很好的 pow() 实现。

Code

Test.java:

/**
 * Compile:
 *  javac Test.java
 * Run:
 *  java Test
 */ 
public class Test {
    public static void main(String[] args) {
        Test test = new Test();
    long startAt = System.currentTimeMillis();
        double re = test.testFun(10, 16666611, 100000000);
    long during = System.currentTimeMillis() - startAt;
        System.out.printf("%10s: result: %e, during: %d ms\n", "Java", re, during);
    }

    private double testFun(double first, double second, long times) {
        int i = 0;
        double result = 0;
        double dx = (second - first) / times;
        for (; i < times; i++) {
            result += fun(first + i * dx);
        }
        return result * dx;
    }

    private double fun(double v) {
        return v * v - v;
        // return Math.pow(v, 2) - v;
        // return Math.pow(v, 2.4) - v;
    }
}

test.c:

/**
 * compile with:
 *  gcc test.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

double fun(double v) {
    return v * v - v;
    // return pow(v, 2) - v;
    // return pow(v, 2.4) - v;
}

double testFun(double first, double second, long times) {
    int i;
    double result = 0;
    double dx = (second - first) / times;
    for (i = 0; i < times; i++) {
        result += fun(first + i * dx);
    }
    return result * dx;
}

long long current_timestamp() {
    struct timeval te;
    gettimeofday(&te, NULL); // get current time
    long long milliseconds =
        te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds
    // printf("milliseconds: %lld\n", milliseconds);
    return milliseconds;
}

int main(int argc, char *argv[]) {
    long long startAt = current_timestamp();
    double re = testFun(10, 16666611, 100000000);
    long long during = current_timestamp() - startAt;
    printf("%10s: result: %e, during: %lld ms\n", "C", re, during);
    return 0;
}

test.go:

/**
 * How to run:
 *  go run test.go
 */
package main

import (
    "fmt"
    "math"
    "time"
)

func main() {
    startAt := time.Now()
    result := testFun(10, 16666611, 100000000)
    during := time.Since(startAt)
    fmt.Printf("%10s: result: %e, during: %v\n", "Go", result, during)

    _ = math.Pow
}

func fun(x float64) float64 {
    return x*x - x
    // return math.Pow(x, 2) - x
    // return math.Pow(x, 2.4) - x
}

func testFun(first float64, second float64, times int) float64 {
    var i = 0
    var result float64 = 0
    var dx float64
    dx = (second - first) / float64(times)
    for ; i < times; i++ {
        result += fun(first + float64(i)*dx)
    }
    return result * dx
}

编译:

javac Test.java; gcc test.c -lm; go build test.go

运行:

java Test; ./a.out ; ./test

@更新 - C程序使用-O2-O3选项

正如评论中 Raffaello 建议的那样,在编译C程序时,可以使用-O2-O3来进一步优化程序。

以下是测试结果:

  • 当指数为2.4时。
            C: result: 1.543194e+21, during: 5805 ms
 C with `-O2`: result: 1.543194e+21, during: 5324 ms
 C with `-O3`: result: 1.543194e+21, during: 5326 ms
当指数为2时,仍然使用pow()Pow()
            C: result: 1.543194e+21, during: 897 ms
 C with `-O2`: result: 1.543194e+21, during: 119 ms
 C with `-O3`: result: 1.543194e+21, during: 121 ms
  • 当指数 exponent = 2 时,使用 x * x 替代。
            C: result: 1.543194e+21, during: 353 ms
 C with `-O2`: result: 1.543194e+21, during: 122 ms
 C with `-O3`: result: 1.543194e+21, during: 119 ms
摘要 - (-O2-O3 选项):
  • 使用-O2-O3选项
    • 当基数为整数(例如2)时,可以使C程序快几倍。
    • 当基数为浮点数(例如2.4)时,也会更快,但速度提升非常小。
  • 在上述测试中,-O2-O3之间的差距很小。

1
Go会自动内联fun()并展开核心循环。GCC可能会这样做。显式使用C11内联,-O3和-funroll-all-loops可以加速C并提供更准确的比较。循环中的“dx”乘法也可以用迭代加法替换。 - Kevin Thibedeau
1
看到用 C 写的 Go 编译器失去了 C 是令人惊讶的。 - Robula
2
你应该使用-o2参数编译C代码以进行优化(甚至可以使用-o3)。有了正确的C编译标志,更新基准测试结果会更好。 :) - Raffaello
2
@Raffaello 谢谢你的提示,确实 -O2 或者 -O3 会加速上面的 C 程序,我已经更新了答案并附上了额外测试的结果。顺便提一下,选项应该是大写的 O,而不是小写的。 - Eric

10

不要从其他语言进行翻译。使用Go编写您的程序的Go版本。例如,x * x - x

package main

import (
    "fmt"
    "math"
    "time"
)

func main() {
    start := time.Now()
    v := testFun(10, 16666611, 1000000000)
    since := time.Since(start)
    fmt.Printf("value is %v\ntime is %v\n", v, since)
}

func fun(x float64) float64 {
    return x*x - x
}

func testFun(first float64, second float64, times int) float64 {
    sum := float64(0)
    dx := (second - first) / float64(times)
    for i := 0; i < times; i++ {
        sum += fun(first + float64(i)*dx)
    }
    return sum * dx
}

输出:

$ go version
go version devel +5c11480631 Fri Aug 10 20:02:31 2018 +0000 linux/amd64
$ go run speed.go
value is 1.543194272428967e+21
time is 1.011965238s
$

你会得到什么结果?

20
好的回答! - user3403807
这意味着如果n不是浮点类型,那么在Go中计算大整数n的x^n需要自己实现Power函数,而不是使用math.Pow函数? - ankur agrawal
1
对于这种类型的示例,每种编程语言都是相同的。在该基准测试中,实际上使用相同的"math.Pow"函数而不是x*x-x表达式是诚实的。因此,这是无关紧要的。 - Raffaello
即使在编写Java时,为什么有人会使用指数为2的Math.pow?这与从另一种语言翻译无关,而是与将通用函数用于特殊情况有关。OP只是幸运地发现Java已经针对整数值进行了一些优化。 我们还可以将Math.pow(x, 2.5)x * x * Math.sqrt(x)进行比较。后者在两种语言中可能更快。 - Sven

8
我建议,在Go语言中,math.Pow(x,y)实际上执行的是x^y,没有针对整数值y进行任何优化,而Math.pow(x,y)仅在y==2时执行x*x。至少当我在两个程序中都用简单的x*x替换pow时,Java需要6.5秒,而Go只需1.4秒。如果仍然使用pow,则Java仍然需要6.5秒,而Go需要29.4秒。

1
似乎在Go语言中,math.Pow()函数的速度较慢。当Java和Go都使用组件2.4时,Go比Java稍微慢一些,但是当避免使用math.Pow()函数时,Go比Java快得多。 - Eric
这意味着如果n不是浮点类型,那么在Go中计算大整数n的x^n需要自己实现Power函数,而不是使用math.Pow函数? - ankur agrawal

-2
理论上,Go 需要在开始时编译所有源代码,因此启动时间会比较慢,但是一旦完成启动并逐个执行命令,它将运行得非常快。执行时间可以与 C/C++ 进行比较。你不能只通过几行代码来比较它们,在一个包含数千个文件的程序中,启动时间会受到很大影响。

4
Go语言和C/C++一样是编译型语言,因此它不会在开始时编译所有的源代码。您需要先进行编译,然后运行可执行文件。 - Pizza lord

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接