为什么有些用Golang编写的函数运行速度比Java还要慢



我已经用Golang和Java测试了几个简单的函数。让我惊讶的是,Java有时比Golang更快(尤其是在递归函数和标准库中的一些函数,如mather/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 1n")
startTime := time.Now()
result := 0.0
for i := 0.0; i < 1000000000; i = i + 1 {
result += i * i
}
endTime := time.Now()
fmt.Printf("Result: %vn", result)
fmt.Printf("Duration: %vn", endTime.Sub(startTime))
fmt.Printf("Test 2n")
startTime = time.Now()
resultInt := fibonacci(50)
endTime = time.Now()
fmt.Printf("Result: %vn", resultInt)
fmt.Printf("Duration: %vn", endTime.Sub(startTime))
fmt.Printf("Test 3n")
startTime = time.Now()
result = 0.0
for i := 0.0; i < 100000000; i = i + 1 {
result += rand.Float64()
}
endTime = time.Now()
fmt.Printf("Result: %vn", result)
fmt.Printf("Duration: %vn s", endTime.Sub(startTime))
fmt.Printf("Test 4n")
startTime = time.Now()
result = calPi(100000000)
endTime = time.Now()
fmt.Printf("Result: %vn", result)
fmt.Printf("Duration: %v sn", 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相比)的优势,那就更好了。

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编译器确实使用了一些优化技术来生成更高性能的代码(请参阅编译器优化),但它在分配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

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

相关内容

最新更新