将我的一个计算密集型后端程序从Java迁移到Go后,我发现性能下降而不是提高。我经过了一些测试,发现数组排序代码似乎是罪魁祸首(我在程序中大量使用了它)。我已经编写了下面的两个简化程序进行比较,以某种方式看来,Go的内置排序功能比Java的Arrays.sort方法要慢得多?
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
func main() {
fmt.Println("Starting")
const x = 1000000
const y = x * 10
var s [y]float64
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
start1 := time.Now()
for i := 0; i < y; i++ {
s[i] = r1.Float64()
}
end1 := time.Since(start1)
ss := s[:]
start2 := time.Now()
sort.Float64s(ss)
end2 := time.Since(start2)
fmt.Println(end1)
fmt.Println(end2)
fmt.Println("Number: ", ss[x])
}
并产生如下结果:
Starting
136.6331ms // The time taken to generate 10,000,000 random numbers
3.456781s // The time taken to sort the 10,000,000 random numbers
Number: 0.10000285497001288
在这里使用Java程序
import java.util.*;
class RSTest {
public static void main(String[] args) {
System.out.println("Starting");
int x = 1000000;
int y = x * 10;
Random gen = new Random(System.currentTimeMillis());
double[] s = new double[y];
long start1 = System.nanoTime();
for (int i = 0; i < y; i++) {
s[i] = gen.nextDouble();
}
long end1 = System.nanoTime();
long start2 = System.nanoTime();
Arrays.sort(s);
long end2 = System.nanoTime();
System.out.println((end1 - start1) / (1000000000.0));
System.out.println((end2 - start2) / (1000000000.0));
System.out.println(s[x]);
}
}
结果是这样的
Starting
0.2252634 // The time taken to generate 10,000,000 random numbers
1.0303157 // The time taken to sort the 10,000,000 random numbers
0.0999513608326642
Go程序大约需要130毫秒才能生成1000万个随机数并将其分配给数组,而Jave大约需要230毫秒才能生成1000万个随机数并将其分配给数组,我认为这是我期望通过改进获得的从Java到Go。
但是对于排序部分,Java仅用1秒钟就对1000万个随机数进行了排序,但是花了3.5秒钟才对1000万个随机数进行了排序?从多次测试来看,这是非常一致的。
那么这是否意味着Go的内置sort函数确实比Java的Arrays.sort方法差很多?还是我使用Go的sort函数错误?还是我的程序有问题?
谢谢。
注意:这是来自Go 1.11和Java 8,这是我在服务器上运行的当前版本。另外,请注意,我在此处发布的这两个程序纯粹是出于测试目的,我已经在几分钟内编写了这些程序,因此可能(或者,当然,肯定是)包含了一些对实际生产系统没有多大意义的代码。 / p>
一些更新:
由于@nussjustin的建议,我尝试进行了排序。切片取得了一些有希望的结果。
由于我目前不在办公室并且使用较慢的笔记本电脑,因此上述两项测试的基准结果现在是这样的:
对于Java Arrays.sort测试
Starting
0.3590694
1.6030528 // The time taken to sort the 10,000,000 random numbers
0.10000905418967532
用于Go排序。Float64s测试
Go
Starting
233.1957ms
5.4633992s // The time taken to sort the 10,000,000 random numbers
Number: 0.10002801819954663
现在用sort.Slice修改Go测试之后
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
func main() {
fmt.Println("Starting")
const x = 1000000
const y = x * 10
var s [y]float64
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
start1 := time.Now()
for i := 0; i < y; i++ {
s[i] = r1.Float64()
}
end1 := time.Since(start1)
ss := s[:]
start2 := time.Now()
sort.Slice(ss, func(i, j int) bool { return ss[i] < ss[j] })
end2 := time.Since(start2)
fmt.Println(end1)
fmt.Println(end2)
fmt.Println("Number: ", ss[x])
}
结果是对sort.Float64s的巨大改进,但仍不及Java的数组排序
Starting
281.4262ms
3.6745684s // The time taken to sort the 10,000,000 random numbers
Number: 0.10010604106864159
我认为有人抱怨测试只有一种分布(后来删除了他的评论),我也测试了对随机数的正态分布进行排序(尽管我会说在对均匀分布进行排序方面存在巨大的性能差异)随机数的划分已经是一个坏兆头,因为对随机数的均匀分布进行排序的算法应该已经很成熟了
我只是将随机数生成器从均匀分布替换为正态分布
开始:
s[i] = r1.NormFloat64()
Java:
s[i] = gen.nextGaussian();
Java的Arrays.sort方法的结果是
Starting
1.4126348
1.6118655
-1.2820310313627319
然后转到排序。切片
Starting
434.9106ms
3.8936811s
Number: -1.2818667132095363
所以Go是sort.Slice仍然比Java的Arrays.sort慢大约两倍,这与均匀分配随机数相同。好处是,在生成随机数的正态分布中,Go比Java快三倍,而在生成均匀的数字分布时要快70%。
答案 0 :(得分:2)
感谢@JimB和@nussjustin的建议,我自己编写了一个简单的quicksort实现,它成功了!
package main
import (
"fmt"
"math/rand"
"time"
)
func qsort(s []float64) []float64 {
if len(s) < 2 {
return s
}
left, right := 0, len(s)-1
pivot := 0
s[pivot], s[right] = s[right], s[pivot]
for i := range s {
if s[i] < s[right] {
s[left], s[i] = s[i], s[left]
left++
}
}
s[left], s[right] = s[right], s[left]
qsort(s[:left])
qsort(s[left+1:])
return s
}
func main() {
fmt.Println("Starting")
const x = 1000000
const y = x * 10
var s [y]float64
s1 := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(s1)
start1 := time.Now()
for i := 0; i < y; i++ {
s[i] = r1.NormFloat64()
}
end1 := time.Since(start1)
ss := s[:]
start2 := time.Now()
ss = qsort(ss)
end2 := time.Since(start2)
fmt.Println(end1)
fmt.Println(end2)
fmt.Println("Number: ", ss[x])
}
通过这种超级原油快速分类,现在我可以实现以下结果
Starting
276.763ms
1.589941s
Number: -1.281875446690731
现在,它的速度始终比Java的Arrays.sort方法快15%左右!
我还实现了一种专门用于Java中double数组的quicksort方法来替换Arrays.sort方法,以查看是否可以获得任何性能提升,性能最终与Arrays.sort相同,仍然大约为10%到15比Go慢%。似乎Arrays.sort已经以某种方式达到了Java可能的最佳性能,并且通过剥离抽象并不会获得任何收益。
所以我想上一课是,如果您想在Go的排序中表现出色,然后自己实现一个quicksort函数,请不要使用内置的sort函数,甚至不要使用sort.Slice的速度要比自写的慢大约两倍sort函数和sort.Float64s慢三倍以上(有时是四倍)!
我猜这些结果最终会使那些评论者对他们所谓的“无效基准”一词不屑一顾。就像我说过的那样,从Java迁移到Go之后的性能下降对于我的生产系统来说是相当真实的,如果无法尽快修复它,我将处于紧要关头,现在希望在替换所有这些排序函数之后,我们终于可以看到一个改善了我们的生产系统的性能,使我今晚可以安然入睡:)
答案 1 :(得分:-2)
二十年前,Java 1.1运行缓慢。从那时起,成千上万的人开始致力于解决该问题。如今,Java代码通常与C ++相当或更快。借助Java 12和GraalVM,我们都会看到另一个提升。
Java中的错误代码可能很慢,但C ++的情况同样如此。 Java没有头脑,您必须使用自己的:-)
要回答这个问题,代码看起来正确。我的猜测是,Java's sort implementation实际上已经过优化,可以处理来自数千个用例的数据。只需看一下长度即可:与500 in Go相比,约有3000行带有大量的角落案例。