为什么用Golang编写的某些函数运行起来比用Java还要慢?

时间:2019-01-25 02:02:55

标签: java performance go

我已经用Golang和Java测试了几个简单的函数。令我惊讶的是,Java有时比Golang快(尤其是在递归函数和标准库中的某些函数(例如math / rand.Rand)中)。我想知道为什么。这是我用于测试和结果的一些代码。

Golang代码:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func calPi(pointCount int) float64 {
    inCircleCount := 0

    var x, y float64
    var Pi float64

    for i := 0; i < pointCount; i++ {
        x = rand.Float64()
        y = rand.Float64()

        if x*x+y*y < 1 {
            inCircleCount++
        }
    }

    Pi = (4.0 * float64(inCircleCount)) / float64(pointCount)

    return Pi
}

func fibonacci(c int64) int64 {
    if c < 2 {
        return c
    }

    return fibonacci(c-2) + fibonacci(c-1)
}

func main() {
    rand.Seed(time.Now().Unix()) 

    fmt.Printf("Test 1\n")

    startTime := time.Now()

    result := 0.0

    for i := 0.0; i < 1000000000; i = i + 1 {
        result += i * i
    }

    endTime := time.Now()

    fmt.Printf("Result: %v\n", result)

    fmt.Printf("Duration: %v\n", endTime.Sub(startTime))

    fmt.Printf("Test 2\n")

    startTime = time.Now()

    resultInt := fibonacci(50)

    endTime = time.Now()

    fmt.Printf("Result: %v\n", resultInt)

    fmt.Printf("Duration: %v\n", endTime.Sub(startTime))

    fmt.Printf("Test 3\n")

    startTime = time.Now()

    result = 0.0

    for i := 0.0; i < 100000000; i = i + 1 {
        result += rand.Float64()
    }

    endTime = time.Now()

    fmt.Printf("Result: %v\n", result)

    fmt.Printf("Duration: %v\n s", endTime.Sub(startTime))

    fmt.Printf("Test 4\n")

    startTime = time.Now()

    result = calPi(100000000)

    endTime = time.Now()

    fmt.Printf("Result: %v\n", result)

    fmt.Printf("Duration: %v s\n", endTime.Sub(startTime))

}

结果:

Test 1
Result: 3.333333328333552e+26
Duration: 1.449212507s
Test 2
Result: 12586269025
Duration: 1m31.645050682s
Test 3
Result: 4.999483069673434e+07
Duration: 2.534121566s
 sTest 4
Result: 3.14147056
Duration: 5.036491495s s

Java代码:

public class Performance {

    public static double calPi(int pointCount) {
        int inCircleCount = 0;

        double x, y;
        double Pi;

        for (int i = 0; i < pointCount; i++) {
            x = Math.random();
            y = Math.random();

            if (x * x + y * y < 1) {
                inCircleCount++;
            }
        }

        Pi = (4.0 * inCircleCount) / pointCount;

        return Pi;
    }

    public static double cal(double a, double b, double c) {
        return a * b / (c + 1) + a;
    }

    public static long fibonacci(long c) {
        if (c < 2)
            return c;
        return fibonacci(c - 2) + fibonacci(c - 1);
    }

    public static void main(String[] args) {

        System.out.println("Test 1");

        long startTime = System.currentTimeMillis();

        double result = 0.0;

        for (double i = 0.0; i < 1000000000; i = i + 1) {
            result += i * i;
        }

        long endTime = System.currentTimeMillis();

        float duration = (float) (endTime - startTime) / 1000;

        System.out.println("Result: " + result);
        System.out.println("Duration: " + duration + " s");

        System.out.println("Test 2");

        startTime = System.currentTimeMillis();

        long resultInt = fibonacci(50);

        endTime = System.currentTimeMillis();

        duration = (float) (endTime - startTime) / 1000;

        System.out.println("Result: " + resultInt);
        System.out.println("Duration: " + duration + " s");

        System.out.println("Test 3");

        startTime = System.currentTimeMillis();

        result = 0.0;

        for (double i = 0; i < 100000000; i = i + 1) {
            result += Math.random();
        }

        endTime = System.currentTimeMillis();

        duration = (float) (endTime - startTime) / 1000;

        System.out.println("Result: " + result);
        System.out.println("Duration: " + duration + " s");

        System.out.println("Test 4");

        startTime = System.currentTimeMillis();

        result = calPi(100000000);

        endTime = System.currentTimeMillis();

        duration = (float) (endTime - startTime) / 1000;

        System.out.println("Result: " + result);
        System.out.println("Duration: " + duration + " s");

    }
}

结果:

Test 1
Result: 3.333333328333552E26
Duration: 2.948 s
Test 2
Result: 12586269025
Duration: 60.816 s
Test 3
Result: 4.9999087237930864E7
Duration: 2.448 s
Test 4
Result: 3.14147284
Duration: 4.786 s

测试2结果的差异真的让我震惊!请帮助我找到原因,谢谢。更好的是,如果有人可以给我示例来说明Golang(相对于Java)的优势。

1 个答案:

答案 0 :(得分:3)

在执行之前,Java和Golang程序都被编译成机器语言,这就是JIT代表Java VM的意思。从性能比较的角度来看,两者生成的机器代码之间必须没有那么细微的差别。

不幸的是,我无权访问Java JIT编译器生成的机器代码,但我们可以看看Go编译器(v1.11.4-amd64)为fibonacci函数生成的内容:

        # Do the comparison
        MOVQ    "c", AX
        CMPQ    AX, $2
        JGE     @ELSE
        # Save the func result
        MOVQ    AX, "r"
        # Clean up and return
        MOVQ    24(SP), BP
        ADDQ    $32, SP
        RET
@ELSE:
        # Compute fib(c - 2)
        LEAQ    -2(AX), CX
        MOVQ    CX, (SP)
        CALL    fibonacci
        # Save the call result
        MOVQ    8(SP), AX
        MOVQ    AX, "temp"
        # Compute fib(c - 1)
        MOVQ    "c", CX
        DECQ    CX
        MOVQ    CX, (SP)
        CALL    fibonacci
        # Add previous results together
        MOVQ    16(SP), AX
        ADDQ    8(SP), AX
        # Save the func result
        MOVQ    AX, "r"
        # Clean up and return
        MOVQ    24(SP), BP
        ADDQ    $32, SP
        RET

请注意,这段代码并不完全相同,但是我对其进行了一些修改以使其更加清晰。引用的变量是堆栈位置。

我的结论是,尽管Go编译器确实采用了一些优化技术来生成性能更高的代码(请参见Compiler Optimization),但分配CPU寄存器的效果并不理想(将其与由CPU生成的内容进行比较) C编译器),并且过多地依赖于堆栈,尤其是对于返回值-我认为必须有一个原因可能与语言的工作方式有关(例如,多个返回值)。

更新1

为了比较,这是GCC(amd64)为相同功能生成的机器代码:

        pushq %rbp
        movq  %rsp, %rbp
        pushq %r14
        pushq %rbx
        # Do the comparison
        movq  %rdi, %rbx
        cmpq  $2, %rbx
        jge @ELSE
        # Save "c" in "r"
        movq  %rbx, %rax
        jmp @RETURN
@ELSE:
        # Compute fib(i - 2)
        leaq  -2(%rbx), %rdi
        callq fibonacci
        # Compute fib(i - 1)
        movq  %rax, %r14
        decq  %rbx
        movq  %rbx, %rdi
        callq fibonacci
        # Add previous results together
        addq  %r14, %rax
@RETURN:
        popq  %rbx
        popq  %r14
        popq  %rbp
        retq

更新2

话虽如此,我坚信在现实世界的项目中,语言运行时(例如对象分配,垃圾回收,调用间接调用,动态加载,并发支持等)将对整体性能产生更大的影响而不是在功能级别上进行微优化。