英文:
Simple data stream: Go being super slow compared to Java
问题
作为一个Java开发者,我目前正在研究Go语言,因为我认为它是一种有趣的语言。
为了开始学习,我决定将几个月前我写的一个简单的Java项目用Go重新编写,以比较性能,并主要比较代码的可读性和复杂性。
以下是Java代码示例:
public static void main(String[] args) {
long start = System.currentTimeMillis();
Stream<Container> s = Stream.from(new Iterator<Container>() {
int i = 0;
@Override
public boolean hasNext() {
return i < 10000000;
}
@Override
public Container next() {
return new Container(i++);
}
});
s = s.map((Container _source) -> new Container(_source.value * 2));
int j = 0;
while (s.hasNext()) {
s.next();
j++;
}
System.out.println(System.currentTimeMillis() - start);
System.out.println("j:" + j);
}
public static class Container {
int value;
public Container(int v) {
value = v;
}
}
其中map
函数是:
return new Stream<R>() {
@Override
public boolean hasNext() {
return Stream.this.hasNext();
}
@Override
public R next() {
return _f.apply(Stream.this.next());
}
};
Stream
类只是对java.util.Iterator
的扩展,以添加自定义方法。除了map
之外,其他方法与标准的Java Stream
API不同。
为了重现这个功能,我写了以下Go代码:
package main
import (
"fmt"
)
type Iterator interface {
HasNext() bool
Next() interface{}
}
type Stream interface {
HasNext() bool
Next() interface{}
Map(transformer func(interface{}) interface{}) Stream
}
///////////////////////////////////////
type incremetingIterator struct {
i int
}
type SampleEntry struct {
value int
}
func (s *SampleEntry) Value() int {
return s.value
}
func (s *incremetingIterator) HasNext() bool {
return s.i < 10000000
}
func (s *incremetingIterator) Next() interface{} {
s.i = s.i + 1
return &SampleEntry{
value: s.i,
}
}
func CreateIterator() Iterator {
return &incremetingIterator{
i: 0,
}
}
///////////////////////////////////////
type stream struct {
source Iterator
}
func (s *stream) HasNext() bool {
return s.source.HasNext()
}
func (s *stream) Next() interface{} {
return s.source.Next()
}
func (s *stream) Map(tr func(interface{}) interface{}) Stream {
return &stream{
source: &mapIterator{
source: s,
transformer: tr,
},
}
}
func FromIterator(it Iterator) Stream {
return &stream{
source: it,
}
}
///////////////////////////////////////
type mapIterator struct {
source Iterator
transformer func(interface{}) interface{}
}
func (s *mapIterator) HasNext() bool {
return s.source.HasNext()
}
func (s *mapIterator) Next() interface{} {
return s.transformer(s.source.Next())
}
///////////////////////////////////////
func main() {
it := CreateIterator()
ss := FromIterator(it)
ss = ss.Map(func(in interface{}) interface{} {
return &SampleEntry{
value: 2 * in.(*SampleEntry).value,
}
})
fmt.Println("Start")
for ss.HasNext() {
ss.Next()
}
fmt.Println("Over")
}
这两段代码产生相同的结果,但是当Java代码大约需要20毫秒时,Go代码需要1050毫秒(测试运行了多次,每次有1000万个项目)。
我对Go非常陌生(几个小时前才开始学习),所以如果我做了什么很糟糕的事情,请多包涵。
谢谢!
英文:
As a Java dev, I'm currently looking at Go because I think it's an interesting language.
To start with it, I decided to take a simple Java project I wrote months ago, and re-write it in Go to compare performances and (mainly, actually) compare the code readability/complexity.
The Java code sample is the following:
public static void main(String[] args) {
long start = System.currentTimeMillis();
Stream<Container> s = Stream.from(new Iterator<Container>() {
int i = 0;
@Override
public boolean hasNext() {
return i < 10000000;
}
@Override
public Container next() {
return new Container(i++);
}
});
s = s.map((Container _source) -> new Container(_source.value * 2));
int j = 0;
while (s.hasNext()) {
s.next();
j++;
}
System.out.println(System.currentTimeMillis() - start);
System.out.println("j:" + j);
}
public static class Container {
int value;
public Container(int v) {
value = v;
}
}
Where the map
function is:
return new Stream<R>() {
@Override
public boolean hasNext() {
return Stream.this.hasNext();
}
@Override
public R next() {
return _f.apply(Stream.this.next());
}
};
And the Stream
class is just an extension to java.util.Iterator
to add custom methods to it. Other methods than map
differs from standard Java Stream
API.
Anyway, to reproduce this, I wrote the following Go code:
package main
import (
"fmt"
)
type Iterator interface {
HasNext() bool
Next() interface{}
}
type Stream interface {
HasNext() bool
Next() interface{}
Map(transformer func(interface{}) interface{}) Stream
}
///////////////////////////////////////
type incremetingIterator struct {
i int
}
type SampleEntry struct {
value int
}
func (s *SampleEntry) Value() int {
return s.value
}
func (s *incremetingIterator) HasNext() bool {
return s.i < 10000000
}
func (s *incremetingIterator) Next() interface{} {
s.i = s.i + 1
return &SampleEntry{
value: s.i,
}
}
func CreateIterator() Iterator {
return &incremetingIterator{
i: 0,
}
}
///////////////////////////////////////
type stream struct {
source Iterator
}
func (s *stream) HasNext() bool {
return s.source.HasNext()
}
func (s *stream) Next() interface{} {
return s.source.Next()
}
func (s *stream) Map(tr func(interface{}) interface{}) Stream {
return &stream{
source: &mapIterator{
source: s,
transformer: tr,
},
}
}
func FromIterator(it Iterator) Stream {
return &stream{
source: it,
}
}
///////////////////////////////////////
type mapIterator struct {
source Iterator
transformer func(interface{}) interface{}
}
func (s *mapIterator) HasNext() bool {
return s.source.HasNext()
}
func (s *mapIterator) Next() interface{} {
return s.transformer(s.source.Next())
}
///////////////////////////////////////
func main() {
it := CreateIterator()
ss := FromIterator(it)
ss = ss.Map(func(in interface{}) interface{} {
return &SampleEntry{
value: 2 * in.(*SampleEntry).value,
}
})
fmt.Println("Start")
for ss.HasNext() {
ss.Next()
}
fmt.Println("Over")
}
Both producing the same result but when Java takes about 20ms, Go takes 1050ms (with 10M items, test ran several times).
I'm very new to Go (started couple of hours ago) so please be indulgent if I did something really bad
Thank you!
答案1
得分: 6
另一个答案对原始任务进行了相当“剧烈”的更改,并回到了一个简单的循环。我认为这是不同的代码,因此不能用来比较执行时间(该循环也可以用Java编写,这将导致更小的执行时间)。
现在让我们试着保持问题的“流式方式”。
事先注意:
首先要注意的一点是,在Java中,System.currentTimeMillis()
的粒度可能约为10毫秒(!!),与结果的数量级相同!这意味着Java的20毫秒可能会有巨大的误差!因此,你应该使用System.nanoTime()
来测量代码的执行时间!有关详细信息,请参阅https://stackoverflow.com/questions/13062345/measuring-time-differences-using-system-currenttimemillis/13062443#13062443。
此外,这不是测量执行时间的正确方法,因为第一次运行可能会比较慢。有关详细信息,请参阅https://stackoverflow.com/questions/41608578/order-of-the-code-and-performance/41608707#41608707。
起源
你原始的Go提案在我的计算机上大约运行了1.1秒,与你的结果大致相同。
移除interface{}
项类型
由于Go没有泛型,试图用interface{}
模拟这种行为并不相同,而且如果你要处理的值是原始类型(例如int
)或某些简单的结构体(类似于Java中的Container
类型),则会严重影响性能。参见:反射定律#接口的表示。将int
(或任何其他具体类型)包装在接口中需要创建一个(类型;值)对,其中包含要包装的动态类型和值(创建此对还涉及复制要包装的值;请参见答案https://stackoverflow.com/questions/36077566/how-can-a-slice-contain-itself/36078970#36078970中对此的分析)。此外,当你想要访问该值时,你必须使用类型断言,这是一种运行时检查,因此编译器无法帮助优化它(检查将增加代码执行时间)!
因此,我们不要使用interface{}
作为我们的项类型,而是使用具体类型:
type Container struct {
value int
}
我们将在迭代器和流的Next()
方法中使用它:Next() Container
,以及在映射函数中使用它:
type Mapper func(Container) Container
此外,我们可以利用嵌入,因为Iterator
的方法集是Stream
的子集。
话不多说,这是完整的可运行示例:
package main
import (
"fmt"
"time"
)
type Container struct {
value int
}
type Iterator interface {
HasNext() bool
Next() Container
}
type incIter struct {
i int
}
func (it *incIter) HasNext() bool {
return it.i < 10000000
}
func (it *incIter) Next() Container {
it.i++
return Container{value: it.i}
}
type Mapper func(Container) Container
type Stream interface {
Iterator
Map(Mapper) Stream
}
type iterStream struct {
Iterator
}
func NewStreamFromIter(it Iterator) Stream {
return iterStream{Iterator: it}
}
func (is iterStream) Map(f Mapper) Stream {
return mapperStream{Stream: is, f: f}
}
type mapperStream struct {
Stream
f Mapper
}
func (ms mapperStream) Next() Container {
return ms.f(ms.Stream.Next())
}
func (ms mapperStream) Map(f Mapper) Stream {
return nil // Not implemented / needed
}
func main() {
s := NewStreamFromIter(&incIter{})
s = s.Map(func(in Container) Container {
return Container{value: in.value * 2}
})
fmt.Println("Start")
start := time.Now()
j := 0
for s.HasNext() {
s.Next()
j++
}
fmt.Println(time.Since(start))
fmt.Println("j:", j)
}
执行时间:210毫秒。很好,我们已经将其加速了5倍,但与Java的Stream
性能相比仍然有很大差距。
“移除”Iterator
和Stream
类型
由于我们不能使用泛型,因此_接口_类型Iterator
和Stream
实际上不需要是接口,因为如果我们想要使用它们来定义其他类型的迭代器和流,我们将需要它们的新类型。
因此,我们要做的下一件事是删除Stream
和Iterator
,并使用它们的具体类型,即上面的实现。这不会影响可读性,事实上,解决方案更加简洁:
package main
import (
"fmt"
"time"
)
type Container struct {
value int
}
type incIter struct {
i int
}
func (it *incIter) HasNext() bool {
return it.i < 10000000
}
func (it *incIter) Next() Container {
it.i++
return Container{value: it.i}
}
type Mapper func(Container) Container
type iterStream struct {
*incIter
}
func NewStreamFromIter(it *incIter) iterStream {
return iterStream{incIter: it}
}
func (is iterStream) Map(f Mapper) mapperStream {
return mapperStream{iterStream: is, f: f}
}
type mapperStream struct {
iterStream
f Mapper
}
func (ms mapperStream) Next() Container {
return ms.f(ms.iterStream.Next())
}
func main() {
s0 := NewStreamFromIter(&incIter{})
s := s0.Map(func(in Container) Container {
return Container{value: in.value * 2}
})
fmt.Println("Start")
start := time.Now()
j := 0
for s.HasNext() {
s.Next()
j++
}
fmt.Println(time.Since(start))
fmt.Println("j:", j)
}
执行时间:50毫秒,与我们之前的解决方案相比,我们又将其加速了4倍!现在这与Java的解决方案相同数量级,而且我们没有失去“流式方式”。总体而言,与提问者的提案相比,性能提升了22倍。
考虑到你在Java中使用了System.currentTimeMillis()
来测量执行时间,这可能与Java的性能相同。提问者确认:它是相同的!
关于相同的性能
现在我们正在讨论在不同语言中执行相当简单、基本任务的大致“相同”代码。如果它们执行基本任务,那么一个语言不会比另一个更好。
还要记住,Java是一个“成熟的成年人”(超过21岁),并且有足够的时间来发展和优化;实际上,Java的JIT(即时编译)对于长时间运行的进程(如你的进程)做得很好。Go还很年轻,只是一个“孩子”(距离现在11天后将满5岁),在可预见的未来可能会有更好的性能改进。
进一步改进
这种“流式”方式可能不是解决你要解决的问题的“Go”方式。这只是你的Java解决方案的“镜像”代码,使用了更符合Go习惯的结构。
相反,你应该利用Go对并发的出色支持,即_goroutine_(参见go
语句),它们比Java的线程更高效,以及其他语言结构,如通道(参见答案https://stackoverflow.com/questions/39826692/what-are-golang-channels-used-for/39826883#39826883)和select
语句。
通过将原始的大任务适当地分块/分区为较小的任务,_goroutine工作池_可能非常强大,可以处理大量的数据。参见
https://stackoverflow.com/questions/38170852/is-this-an-idiomatic-worker-thread-pool-in-go/38172204#38172204
此外,你在评论中声称“我没有10M个要处理的项,而是10G个,无法放入内存”。如果是这样,考虑到从外部系统获取数据进行处理的IO时间和延迟。如果这需要很长时间,它可能会超过应用程序中的处理时间,而应用程序的执行时间可能无关紧要。
Go不是为了从执行时间中挤出每一纳秒,而是为你提供了一种简单、简洁的语言和工具,通过编写简单的代码,你可以轻松地控制和利用可用的资源(如goroutine和多核CPU)。
(尝试比较Go语言规范和Java语言规范。就个人而言,我已经多次阅读过Go的语言规范,但从未能读完Java的。)
英文:
The other answer changed the original task quite "dramatically", and reverted to a simple loop. I consider it to be different code, and as such, it cannot be used to compare execution times (that loop could be written in Java as well, which would give smaller execution time).
Now let's try to keep the "streaming manner" of the problem at hand.
Note beforehand:
One thing to note beforehand. In Java, the granularity of System.currentTimeMillis()
could be around 10 ms (!!) which is in the same order of magnitude of the result! This means the error rate could be huge in Java's 20 ms! So instead you should use System.nanoTime()
to measure code execution times! For details, see https://stackoverflow.com/questions/13062345/measuring-time-differences-using-system-currenttimemillis/13062443#13062443.
Also this is not the correct way to measure execution times, as running things for the first time might run several times slower. For details, see https://stackoverflow.com/questions/41608578/order-of-the-code-and-performance/41608707#41608707.
Genesis
Your original Go proposal runs on my computer roughly for 1.1 seconds, which is about the same as yours.
Removing interface{}
item type
Go doesn't have generics, trying to mimic this behavior with interface{}
is not the same and have serious performance impact if the value you want to work with is a primitive type (e.g. int
) or some simple structs (like the Go equivalent of your Java Container
type). See: The Laws of Reflection #The representation of an interface. Wrapping an int
(or any other concrete type) in an interface requires creating a (type;value) pair holding the dynamic type and value to be wrapped (creation of this pair also involves copying the value being wrapped; see an analysis of this in the answer https://stackoverflow.com/questions/36077566/how-can-a-slice-contain-itself/36078970#36078970). Moreover when you want to access the value, you have to use a type assertion which is a runtime check, so the compiler can't be of any help optimizing that (and the check will add to the code execution time)!
So let's not use interface{}
for our items, but instead use a concrete type for our case:
type Container struct {
value int
}
We will use this in the iterator's and stream's next method: Next() Container
, and in the mapper function:
type Mapper func(Container) Container
Also we may utilize embedding, as the method set of Iterator
is a subset of that of Stream
.
Without further ado, here is the complete, runnable example:
package main
import (
"fmt"
"time"
)
type Container struct {
value int
}
type Iterator interface {
HasNext() bool
Next() Container
}
type incIter struct {
i int
}
func (it *incIter) HasNext() bool {
return it.i < 10000000
}
func (it *incIter) Next() Container {
it.i++
return Container{value: it.i}
}
type Mapper func(Container) Container
type Stream interface {
Iterator
Map(Mapper) Stream
}
type iterStream struct {
Iterator
}
func NewStreamFromIter(it Iterator) Stream {
return iterStream{Iterator: it}
}
func (is iterStream) Map(f Mapper) Stream {
return mapperStream{Stream: is, f: f}
}
type mapperStream struct {
Stream
f Mapper
}
func (ms mapperStream) Next() Container {
return ms.f(ms.Stream.Next())
}
func (ms mapperStream) Map(f Mapper) Stream {
return nil // Not implemented / needed
}
func main() {
s := NewStreamFromIter(&incIter{})
s = s.Map(func(in Container) Container {
return Container{value: in.value * 2}
})
fmt.Println("Start")
start := time.Now()
j := 0
for s.HasNext() {
s.Next()
j++
}
fmt.Println(time.Since(start))
fmt.Println("j:", j)
}
Execution time: 210 ms. Nice, we're already sped it up 5 times, yet we're far from Java's Stream
performance.
"Removing" Iterator
and Stream
types
Since we can't use generics, the interface types Iterator
and Stream
doesn't really need to be interfaces, since we would need new types of them if we'd wanted to use them to define iterators and streams of another types.
So the next thing we do is we remove Stream
and Iterator
, and we use their concrete types, their implementations above. This will not hurt readability at all, in fact the solution is shorter:
package main
import (
"fmt"
"time"
)
type Container struct {
value int
}
type incIter struct {
i int
}
func (it *incIter) HasNext() bool {
return it.i < 10000000
}
func (it *incIter) Next() Container {
it.i++
return Container{value: it.i}
}
type Mapper func(Container) Container
type iterStream struct {
*incIter
}
func NewStreamFromIter(it *incIter) iterStream {
return iterStream{incIter: it}
}
func (is iterStream) Map(f Mapper) mapperStream {
return mapperStream{iterStream: is, f: f}
}
type mapperStream struct {
iterStream
f Mapper
}
func (ms mapperStream) Next() Container {
return ms.f(ms.iterStream.Next())
}
func main() {
s0 := NewStreamFromIter(&incIter{})
s := s0.Map(func(in Container) Container {
return Container{value: in.value * 2}
})
fmt.Println("Start")
start := time.Now()
j := 0
for s.HasNext() {
s.Next()
j++
}
fmt.Println(time.Since(start))
fmt.Println("j:", j)
}
Execution time: 50 ms, we've again sped it up 4 times compared to our previous solution! Now that's the same order of magnitude of the Java's solution, and we've lost nothing from the "streaming manner". Overall gain from the asker's proposal: 22 times faster.
Given the fact that in Java you used System.currentTimeMillis()
to measure execution, this may even be the same as Java's performance. Asker confirmed: it's the same!
Regarding the same performance
Now we're talking about roughly the "same" code which does pretty simple, basic tasks, in different languages. If they're doing basic tasks, there is not much one language could do better than the other.
Also keep in mind that Java is a mature adult (over 21 years old), and had an enormous time to evolve and be optimized; actually Java's JIT (just-in-time compilation) is doing a pretty good job for long running processes, such as yours. Go is much younger, still just a kid (will be 5 years old 11 days from now), and probably will have better performance improvements in the foreseeable future than Java.
Further improvements
This "streamy" way may not be the "Go" way to approach the problem you're trying to solve. This is merely the "mirror" code of your Java's solution, using more idiomatic constructs of Go.
Instead you should take advantage of Go's excellent support for concurrency, namely goroutines (see go
statement) which are much more efficient than Java's threads, and other language constructs such as channels (see answer https://stackoverflow.com/questions/39826692/what-are-golang-channels-used-for/39826883#39826883) and select
statement.
Properly chunking / partitioning your originally big task to smaller ones, a goroutine worker pool might be quite powerful to process big amount of data. See
https://stackoverflow.com/questions/38170852/is-this-an-idiomatic-worker-thread-pool-in-go/38172204#38172204
Also you claimed in your comment that "I don't have 10M items to process but more 10G which won't fit in memory". If this is the case, think about IO time and the delay of the external system you're fetching the data from to process. If that takes significant time, it might out-weight the processing time in the app, and app's execution time might not matter (at all).
Go is not about squeezing every nanosecond out of execution time, but rather providing you a simple, minimalist language and tools, by which you can easily (by writing simple code) take control of and utilize your available resources (e.g. goroutines and multi-core CPU).
(Try to compare the Go language spec and the Java language spec. Personally I've read Go's lang spec multiple times, but could never get to the end of Java's.)
答案2
得分: 5
这是一个我认为很有趣的问题,因为它涉及到Java和Go之间的区别,并突显了代码移植的困难。以下是用Go实现的相同功能的代码(运行时间约为50毫秒):
values := make([]int64, 10000000)
start := time.Now()
fmt.Println("Start")
for i := int64(0); i < 10000000; i++ {
values[i] = 2 * i
}
fmt.Println("Over after:", time.Now().Sub(start))
更严谨地说,以下是使用映射(map)对条目(entry)切片进行操作的更符合惯例的版本,它可以适用于任何类型的条目结构。在我的机器上,这个版本的运行时间是30毫秒,比上面的for循环更快(有人能解释为什么吗?),所以可能与你的Java版本类似:
package main
import (
"fmt"
"time"
)
type Entry struct {
Value int64
}
type EntrySlice []*Entry
func New(l int64) EntrySlice {
entries := make(EntrySlice, l)
for i := int64(0); i < l; i++ {
entries[i] = &Entry{Value: i}
}
return entries
}
func (entries EntrySlice) Map(fn func(i int64) int64) {
for _, e := range entries {
e.Value = fn(e.Value)
}
}
func main() {
entries := New(10000000)
start := time.Now()
fmt.Println("Start")
entries.Map(func(v int64) int64 {
return 2 * v
})
fmt.Println("Over after:", time.Now().Sub(start))
}
以下是使操作变得更加昂贵的因素:
- 传递interface{}类型的参数,请避免这样做。
- 构建单独的迭代器类型,应使用range或for循环。
- 分配内存,因此应该在原地进行转换,而不是为每个结果分配新的结构体。
关于使用interface{},我建议避免这样做,这意味着你必须为每种类型编写单独的映射函数,这并不困难。不要构建迭代器,使用range可能更合适。关于原地转换,如果为每个结果分配新的结构体,会给垃圾回收器带来压力,使用下面这样的Map函数会慢一个数量级:
entries.Map(func(e *Entry) *Entry {
return &Entry{Value: 2 * e.Value}
})
如果要流式处理数据,可以将数据拆分成块,并执行与上述相同的操作(如果依赖于先前的计算结果,则需要保留上一个对象的备忘录)。如果有独立的计算(不像这里的例子),还可以将工作分发给一组goroutine来加快处理速度(这会增加开销,在简单的例子中可能不会更快)。
最后,如果你对使用Go进行数据处理感兴趣,我建议访问这个新网站:http://gopherdata.io/
英文:
This is I think an interesting question as it gets to the heart of differences between Java and Go and highlights the difficulties of porting code. Here is the same thing in go minus all the machinery (time ~50ms here):
values := make([]int64, 10000000)
start := time.Now()
fmt.Println("Start")
for i := int64(0); i < 10000000; i++ {
values[i] = 2 * i
}
fmt.Println("Over after:", time.Now().Sub(start))
More seriously here is the same thing with a map over a slice of entries which is a more idiomatic version of what you have above and could work with any sort of Entry struct. This actually works out at a faster time on my machine of 30ms than the for loop above (anyone care to explain why?), so probably similar to your Java version:
package main
import (
"fmt"
"time"
)
type Entry struct {
Value int64
}
type EntrySlice []*Entry
func New(l int64) EntrySlice {
entries := make(EntrySlice, l)
for i := int64(0); i < l; i++ {
entries[i] = &Entry{Value: i}
}
return entries
}
func (entries EntrySlice) Map(fn func(i int64) int64) {
for _, e := range entries {
e.Value = fn(e.Value)
}
}
func main() {
entries := New(10000000)
start := time.Now()
fmt.Println("Start")
entries.Map(func(v int64) int64 {
return 2 * v
})
fmt.Println("Over after:", time.Now().Sub(start))
}
Things that will make operations more expensive -
- Passing around interface{}, don't do this
- Building a separate iterator type - use range or for loops
- Allocations - so building new types to store answers, transform in place
Re using interface{}, I would avoid this - this means you have to write a separate map (say) for each type, not a great hardship. Instead of building an iterator, a range is probably more appropriate. Re transforming in place, if you allocate new structs for each result it'll put pressure on the garbage collector, using a Map func like this is an order of magnitude slower:
entries.Map(func(e *Entry) *Entry {
return &Entry{Value: 2 * e.Value}
})
To stream split the data into chunks and do the same as above (keeping a memo of last object if you depend on previous calcs). If you have independent calculations (not as here) you could also fan out to a bunch of goroutines doing the work and get it done faster if there is a lot of it (this has overhead, in simple examples it won't be faster).
Finally, if you're interested in data processing with go, I'd recommend visiting this new site: http://gopherdata.io/
答案3
得分: 0
作为对之前评论的补充,我修改了Java和Go实现的代码,使其运行测试100次。
有趣的是,Go的运行时间在69到72毫秒之间保持不变。
然而,Java在第一次运行时需要71毫秒(71毫秒,19毫秒,12毫秒),然后在5到7毫秒之间。
根据我的测试和理解,这是因为JVM需要一些时间来正确加载类并进行一些优化。
最终,我仍然存在着10倍的性能差异,但我不会放弃,我会努力更好地理解Go的工作原理,以尝试使其更快速
英文:
Just as a complement to the previous comments, I changed the code of both Java and Go implementations to run the test 100 times.
What's interesting here is that Go takes a constant time between 69 and 72ms.
Owever, Java takes 71ms the first time (71ms, 19ms, 12ms) and then between 5 and 7ms.
From my test and understanding, this comes from the fact that the JVM takes a bit of time to properly load the classes and do some optimization.
In the end I'm still having this 10 times performance difference but I'm not giving up and I'll try to have a better understanding of how Go works to try to have it more fast
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论