[HN Gopher] Go Does Not Need a Java Style GC
___________________________________________________________________
Go Does Not Need a Java Style GC
Author : nahuel0x
Score : 84 points
Date : 2021-11-23 15:47 UTC (7 hours ago)
(HTM) web link (erik-engheim.medium.com)
(TXT) w3m dump (erik-engheim.medium.com)
| natanbc wrote:
| > In a multithreaded program, a bump allocator requires locks.
| That kills their performance advantage.
|
| Java uses per-thread pointer bump allocators[1]
|
| > While Java does it as well, it doesn't utilize this info to put
| objects on the stack.
|
| Correct, but it does scalar replacement[2] which puts them in
| registers instead
|
| > Why can Go run its GC concurrently and not Java? Because Go
| does not fix any pointers or move any objects in memory.
|
| Most java GCs are concurrent[3], if you want super low pauses you
| can get those too[4][5]. Pointers can get fixed while the
| application is running with GC barriers
|
| [1]: https://shipilev.net/jvm/anatomy-quarks/4-tlab-allocation/
|
| [2]: https://shipilev.net/jvm/anatomy-quarks/18-scalar-
| replacemen...
|
| [3]: https://shipilev.net/jvm/anatomy-quarks/3-gc-design-and-
| paus...
|
| [4]: https://wiki.openjdk.java.net/display/zgc/Main
|
| [5]: https://wiki.openjdk.java.net/display/shenandoah
| majou wrote:
| ZGC[4] in particular has me excited, enough so to want to pick
| up a JVM language.
| Thaxll wrote:
| ZGC and Shenandoah can be slower than G1, those are not
| silver bullets. The fact that there is 4-5 GCs explains the
| situation, there is not a single GC that is better than the
| others.
|
| It really depends of the workload.
| geodel wrote:
| Indeed. It is strange that no official JDK document puts
| pros/cons of GCs packaged with standard JDKs in some kinda
| easy-to-read table/matrix.
| the-alchemist wrote:
| ZGC is already available, since JDK 15, September 2020. =)
|
| https://wiki.openjdk.java.net/display/zgc/Main#Main-
| ChangeLo...
| majou wrote:
| Max pause times of 0.5ms is what got me really interested.
|
| It feels like a huge trade-off of GCs is almost completely
| gone.
|
| https://malloc.se/blog/zgc-jdk16
| silon42 wrote:
| There's still a memory tradeoff, some due to GC, some due
| to Java (lots of runtime reflection...). Guessing 2-4x.
| marginalia_nu wrote:
| This is a bit of a tangent, but you can get into
| situations where Java's memory-overhead becomes pretty
| untenable. I was in a situation of having to keep track
| of ~1 billion short strings of a median length of maybe 7
| characters.
|
| In terms of just data, that should clock in at about 10
| Gb; in practice it was closer to 24 Gb. I tried going
| with just byte[]-instances instead, which didn't help a
| lot. Using long byte[]-instances and indexing those
| doesn't help as much as you'd think because they get
| sliced up into small objects behind the scenes.
|
| I ended up memory mapping blocks of memory and basically
| implementing my own append-only allocator.
| jeeeb wrote:
| FWIW. This would probably present a challenge in most
| (all?) languages.
|
| For example in libc++ due to SSO an std::string has a
| minimum size of 24 bytes.
|
| For a billion strings less than 15 chars (+ the null
| byte) that gets you to 24GB, and that's optimistically
| assuming each string is allocated in place.
|
| I doubt heap allocated char* would do much better either.
| Just having a billion 8 byte pointers eats a lot of
| memory. You'd really need some sort of string packing
| scheme similar to what you did in Java.
| marginalia_nu wrote:
| It's _a lot_ easier to build custom allocators in C++
| though.
|
| For one, Java has a maximum mmap-size of 2 Gb, and as a
| cherry on top of that turd, you have no control over
| their lifecycle. The language is very clearly not
| designed for this type of work, and if you try to make it
| do it anyway, it fights you every step of the way.
| kasperni wrote:
| The foreign memory API which is currently incubating
| should help with most of these limitations:
| https://openjdk.java.net/jeps/419
| hashmash wrote:
| The Lilliput project aims to address this:
| https://wiki.openjdk.java.net/display/lilliput
| CyberDildonics wrote:
| I would never write something like this in java, but to
| be fair, a program shouldn't be written like this in the
| first place. If you "need" a billion strings in memory
| and you didn't design for that with something that would
| scale better, you messed up a long time ago.
| marginalia_nu wrote:
| Huh, I didn't really have a problem solving the problem
| as it came up. Like many scaling problems, it wasn't a
| problem until it was. Then I fixed it. Now I have a
| solution that can deal with ten times as many strings as
| before. If I grow out of that one, I'll come up with a
| better design.
|
| I could have gotten 10 times as much hardware instead,
| but that would be an incredible waste of money compared
| to just spending a few days writing more hardware-
| efficient code.
| kaba0 wrote:
| Also, throughput. But latency and throughput are almost
| universally opposite ends of the same axis -- that's why
| it's great that Java allows for choosing a GC
| implementation.
| masklinn wrote:
| The GC memory overhead affects all languages with a GC
| more advanced than refcounting. It certainly does affect
| Go as well.
| masklinn wrote:
| > It feels like a huge trade-off of GCs is almost
| completely gone.
|
| FWIW the tradeoff of low latency GC is usually paid in
| throughput.
|
| That is definitely the case for Go, which can lag very
| much behind allocations (so if your allocation pattern is
| bad enough the heap will keep growing despite the _live_
| heap being stable, because the GC is unable to clear the
| dead heap fast enough for the new allocations).
| sam_bishop wrote:
| I agree. The author seems to know quite a bit about Go and GCs,
| but doesn't seem to have much experience with Java. As a Java
| performance engineer, it sounds like he is comparing Go to how
| he thinks Java works based on what he's read about it.
| pjmlp wrote:
| Additionally he doesn't seem to know that much about C#,
| which also has advanced GC, while allowing for C++ like
| memory management, if needed.
| mappu wrote:
| "Scalar replacement" explodes the object into its class member
| variables and does not construct a class object at all. That
| does result in the exact same `sub %esp` (that Go would do for
| any struct), but it is restricted to only working if every
| single usage of that class type is fully inlined and the class
| is never passed anywhere that needs it in its object form.
|
| It's worse than what Go has. Go can stack-allocate any struct
| and still pass pointers to it to non-inlined functions.
| pkolaczk wrote:
| Scalar replacement does not work even in very trivial cases:
| https://pkolaczk.github.io/overhead-of-optional/
|
| In all those cases, Optionals were inlined, didn't escape,
| yet they haven't been properly optimized out.
| madmax108 wrote:
| As some of the other comments in the thread allude, this is quite
| a rudimentary (or rather outdated) understanding of how Java GC
| operates and ends up (unfortunately) turning an otherwise good
| comparison into a straw-man argument.
|
| As someone who's worked with Java from the days where "If you
| want superhigh performance from Java without GC pauses, then just
| turn off GC and restart your process every X hours" was
| considered a "valid" way to run high-performance Java systems, I
| think the changes Java has made to GC are among the biggest
| improvements to the framework/JVM and have contributed vastly to
| JVM stability and growth over the last decade.
| geodel wrote:
| Fundamentals of Java about data/class layouts in memory have
| remain same for decades. So author is right at big picture.
|
| > I think the changes Java has made to GC are among the biggest
| improvements to the framework/JVM and have contributed vastly
| to JVM stability and growth over the last decade.
|
| This is of course true. However the point is for Java it is
| absolute necessity for Go it may be nice to have.
| esarbe wrote:
| > Java is a language that basically outsourced memory management
| entirely to its garbage collector. This turned out to be a big
| mistake.
|
| Given that Java is not far behind C and C++ for many kinds of
| workload and in quite some scenarios can outperform C code, I'm
| not sure that I buy this line of reasoning.
|
| > Doing these updates requires freezing all threads.
|
| Eh, what? There are multi-threaded GCs, what is this article even
| talking about?
|
| > However, this does not put C# and Java on equal footing with
| languages like Go and C/C++ in terms of memory management
| flexibility
|
| In which world are Go and C in the same worlds when it comes to
| memory management flexibility? That's like comparing a language
| that uses a Garbage Collector to one that requires manual memory
| management. Because - it is.
|
| > Modern Languages Don't Need Compacting GCs
|
| > If need be, the Pacer slows down allocation while speeding up
| marking.
|
| So, you just traded in one drawback for another?
|
| Heck, I get it. The JVM is not a thin graceful fawn. It's a
| complicated beast that requires years of experience to tame it -
| and even then it will come back from time to time an bite you.
| There are many good points to critique the JVM - but I don't get
| the feeling that the author of this article has spent much time
| with modern JVMs because he's not pointing out any of them.
| Yoric wrote:
| I'm a bit skeptical.
|
| Yes, Go has value types and pointers. But whether you need a
| modern GC will undoubtedly depend a lot on the type of algorithms
| you need to execute. Also, it's great that you can implement
| (some form of) allocators, and that will definitely help for many
| algorithms, but that's definitely a case of tradeoff between
| convenience and readability. Similarly, unless I'm mistaken,
| TCMalloc "solves" fragmentation in two cases: either allocations
| are small (very common) or memory allocation maps neatly to
| threads (much less common). That's two good cases to have, but I
| wouldn't count on it solving memory allocation on its own for,
| say, a browser engine or a videogame.
|
| Oh, and hasn't Java's GC been fully concurrent for a while now?
|
| That being said, revisiting the assumptions made by Java (and
| other languages) is a very good idea.
| seunosewa wrote:
| I'm cautious about believing this sort of claim because I
| remember reading about why Go doesn't need generics, yet here we
| are waiting for Go Generics to be ready. However, the article
| convincingly explains who Java has a greater need for a
| compacting GC - it creates more garbage. This doesn't necessarily
| mean Go won't benefit from having a generational, compacting GC
| at some point, for some applications.
| nemo1618 wrote:
| > I remember reading about why Go doesn't need generics, yet
| here we are waiting for Go Generics to be ready
|
| Hey, us generics-naysayers are still out here (grumbling)! :)
| mohanmcgeek wrote:
| The JVM world right now has quite a few GCs and your choice
| essentially dictates your program's performance tradeoffs.
|
| But I'm not sure if there will ever be a point where this is
| true for Go considering how little gets into the stack compared
| to Java.
| JulianMorrison wrote:
| Go creates less garbage _and_ also stack allocates things using
| escape analysis which as the article explains, is effectively a
| form of generational garbage collection.
| kaba0 wrote:
| As far as I know (I'm not too familiar with Go), Go mostly
| stack allocates based on the developer's intent, eg. by using
| structs.
|
| Java doesn't (yet) have an option for value types that can be
| reliably stack allocated, so it resorts to very complex
| escape analysis. Calling the former escape analysis is a bit
| misleading imo, even if technically true.
| vore wrote:
| Go is a little more elaborate than that: if a value is
| initialized as a pointer-to-struct (e.g. foo := &Foo{...})
| and it doesn't escape the function, Go will allocate it as
| if were a value type.
| JulianMorrison wrote:
| You can stack-allocate in C, with alloca() or by taking
| the address of a local, and use it like a pointer. So
| long as you're extremely sure nothing is going to hang
| onto the pointer beyond the lifetime of that stack frame,
| it's fine.
|
| Same thing with Go, except that the compiler makes the
| decision.
| masklinn wrote:
| However it's a weak one, the stack allocation acts as a form
| of extremely limited nursery, but lots of "escaping" objects
| could well fit into a nursery, to say nothing of "heap"
| objects (like strings and slices) which always trigger heap
| allocations.
|
| Furthermore AFAIK most generational GCs have 3 generations,
| not 2 (let alone 1.5).
|
| It does make the tradeoff more complicated, a generational GC
| is not simple (especially in a language with ubiquitous
| mutability), but the casual dismissals are... troubling.
|
| And that's before mentioning the regularly problematic lack
| of tuning knobs of the Go GC, also often dismissed as "java
| concerns" (which Go users have to work around using ugly
| hacks when hit, because they don't have tuning knobs).
| geodel wrote:
| > Go users have to work around using ugly hacks when hit,
| because they don't have tuning knobs
|
| Yeah, Java users just have to hire Java performance tuning
| experts from sprawling Java perf consulting cottage
| industry. Can't get much simpler than that.
| runevault wrote:
| Even if it doesn't need specifically a compacting QC having a
| swappable more tunable one might still be a big deal for people
| who need something other than the current GC. Like a simple
| example is the Discord article about how their use case could
| not work with Go because it always ran the GC every two
| minutes. If they could optionally disable that feature they
| theoretically could have kept using Go instead of rewriting to
| Rust.
| lostcolony wrote:
| That wasn't my read.
|
| The Discord team that wrote the article I believe you're
| referencing ( https://discord.com/blog/why-discord-is-
| switching-from-go-to... ) wanted GC, the problem was that
| they had a huge cache which took a long time for the GC to
| scan. They could shrink the cache and it would remain
| performant in the face of GC, but they took latency hits due
| to increased cache misses. After a bunch of testing and
| tweaking they found a goldilocks zone where performance was
| okay.
|
| Rust was introduced because it was already something other
| teams were interested in, and when they tried a quick
| prototype, they avoided the issue entirely, and saw better
| performance even in the prototype than their finely tuned Go
| code, so they switched to it.
| pkaye wrote:
| You can disable the GC in go.
| https://pkg.go.dev/runtime/debug#SetGCPercent
| Andys wrote:
| The go gc was greatly improved in the versions subsequent to
| the ones used by Discord. The timing there was unfortunate.
| geodel wrote:
| Besides sometimes engineers just want to use Rust.
| hopsas wrote:
| Do not add articles behind pay wall.
| hopsas wrote:
| Do not add articles behind paywall.
| avita1 wrote:
| Cool article, I'm not sure I agree with the headline.
|
| I used to write low-scale Java apps, and now I write memory
| intensive Go apps. I've often wondered what would happen if Go
| _did_ have a JVM style GC.
|
| It's relatively common in Go to resort to idioms that let you
| avoid hitting the GC. Some things that come to mind:
|
| * all the tricks you can do with a slice that have two slice
| headers pointing to the same block of memory [1]
|
| * object pooling, something so common in Go it's part of the
| standard library [2]
|
| Both are technically possible in Java, but I've never seen them
| used commonly (though in fairness I've never written performance
| critical Java.) If Go had a more sophisticated GC, would these
| techniques be necessary?
|
| Also Java is supposed to be getting value types soon (tm) [3]
|
| [1] https://ueokande.github.io/go-slice-tricks/
|
| [2] https://pkg.go.dev/sync#Pool
|
| [3] https://openjdk.java.net/jeps/169
| bestinterest wrote:
| How have you found Go in contrast to Java. Is the simplicity
| worth it?
| munificent wrote:
| _> Both are technically possible in Java, but I 've never seen
| them used commonly (though in fairness I've never written
| performance critical Java.)_
|
| I don't know about the Java world, but in C#--especially in
| games written in Unity--object pooling is very common.
| jillesvangurp wrote:
| Java has a pretty decent standard library with different list,
| map and set implementations and quite a few third party
| libraries with yet more data structures. Honestly, Go felt a
| bit primitive and verbose to me on that front on the few times
| I used it. Simplicity has a price and some limitations.
|
| There are also other tricks you can do like for example using
| off heap memory (e.g. Lucene does this), using array buffers,
| or using native libraries. There obviously is a lot of very
| memory intensive, widely used software written for the JVM and
| no shortage of dealing with all sorts of memory related
| challenges. I'd even go as far as to argue that quite a few of
| those software packages might be a little out of the comfort
| zone for Go. Maybe if it were used more for such things, there
| would be increased demand for better GCs as well?
|
| Object pooling is pretty common for things like connection
| pools. For example apache commons pool is used for doing
| connection pooling (database, http, redis, etc.) in Spring Boot
| and probably a lot more products. Also there are thread pools,
| worker pools and probably quite a few more that are pretty
| widely used and quite a few of those come with the Java
| standard library. Caching libraries are also pretty common and
| well supported popular web frameworks like Spring.
|
| A typical Java based search or database software product
| (Elasticsearch, Kafka, Casandra, etc.) is likely to use all of
| the above. Likewise for things like Hadoop, Spark, Neo4j, etc.
|
| Of course there's a difference between Java the language and
| the JVM, which is also targeted by quite a few other languages.
| For example, I've been using Kotlin for the last few years.
| There are functional languages like Scala and Clojure. And
| people even run scripting languages on jython, jruby, groovy,
| or javascript on it.
|
| There even have been some attempts to make Go run on the JVM.
| Apparently performance, concurrency and memory management were
| big motivators for attempting that (you know, stuff the JVM
| does at scale): https://githubmemory.com/repo/golang-
| jvm/golang-jvm
|
| Their pitch: "You can use go-jvm simply as a faster version of
| Golang, you can use it to run Golang on the JVM and access
| powerful JVM libraries such as highly tuned concurrency
| primitives, you can use it to embed Golang as a scripting
| language in your Java program, or many other possibilities."
| fl0ki wrote:
| Not sure if you're in on the joke, but for those who didn't
| go to the repo itself:
|
| https://github.com/golang-jvm/golang-jvm
|
| It's just a copy-paste of JRuby on April 1st and the readme
| now includes a rickroll.
|
| Maybe it's irresponsible of them to leave it up in a way that
| Google still finds as a legitimate-looking search result.
| geodel wrote:
| > There even have been some attempts to make Go run on the
| JVM. Apparently performance, concurrency and memory
| management were big motivators for attempting that (you know,
| stuff the JVM does at scale):
|
| This seems legit. Just links to their website/Wiki are not
| working right now.
| sam_bishop wrote:
| Pooling objects (for the purposes of minimizing GC) is consider
| a bad practice in modern Java. The article suggests that
| compacting, generational collectors are a bad thing, but they
| can dramatically speed up the amount of time it takes to
| deallocate memory if most of your objects in a given region of
| memory are now dead. All you have to do is remove objects that
| are still alive, and you're done: that region is now available
| for use again. The result is that long-lived objects have a
| greater overhead.
| papercrane wrote:
| Object pooling in Java used to be fairly common. I don't see it
| much anymore in new code, but used to run into it all the time
| when writing code for Java 1.4/5. Even Sun used pooling when
| they wrote EJBs. Individual EJBs can be recycled instead of
| released to the GC.
|
| Nowadays the GC implementations are good enough that's it's not
| worth the effort and complexity.
|
| Though now that I think about it Netty provides an object
| pooling mechanism.
| maxpert wrote:
| Low Java knowledge and more Fan babel rather than true critique.
| dragontamer wrote:
| > In a multithreaded program, a bump allocator requires locks.
| That kills their performance advantage.
|
| Wait, what?
|
| What's wrong with: char theHeap[0x1000000];
| atomic_ulong bumpPtr; void* bump_malloc(int size){
| uint32_t returnCandidate = bumpPtr.fetch_add(size,
| std::memory_order_relaxed); if(returnCandidate +
| size >= HEAP_SIZE){ // Garbage collect. Super
| complicated, lets ignore it lol. // Once garbage
| collect is done, try to malloc again. If fail then panic().
| } return &theHeap[returnCandidate]; }
|
| ------
|
| You don't even need acq_release consistency here, as far as I can
| tell. Even purely relaxed memory ordering seems to work, which
| means you definitely don't need locks or even memory barriers.
|
| The exception is maybe the garbage-collect routine. Locking for
| garbage collect is probably reasonable (other programmers accept
| that garbage-collection is heavy and may incur a large running +
| synchronization costs), but keeping locks outside of the "hot
| path" is the goal.
|
| ------
|
| This is what I do with my GPU test programs, where locking is a
| very, very bad idea (but possible to do). Even atomics are kinda-
| bad in the GPU world but relaxed-atomics are quite fast.
|
| -------
|
| > In Java, this requires 15 000 separate allocations, each
| producing a separate reference that must be managed.
|
| Wait, what? points [] array = new
| points[15000];
|
| This is a singular alloc in Java. 15000 _constructors_ will be
| called IIRC (My Java is rusty though, someone double-check me on
| that), but that's not the same as 15000 allocs being called, not
| by a long shot.
|
| ------
|
| Frankly, it seems like this poster is reasonably good at
| understanding Go performance, but doesn't seem to know much about
| Java performance or implementations.
| GreenToad wrote:
| Point[] array=new Point[15000];
|
| In java would create an array with null references, to fill it
| up you need to create each object so that point is valid.
| dragontamer wrote:
| I stand corrected on that point.
| jeffbee wrote:
| There are very few falsifiable statements in this article but the
| extant ones are all demonstrably false, starting with this
| whopper:
|
| "This typically causes Java programs to have complete freezes of
| several hundred milliseconds where objects get moved around"
|
| Yeah, i mean, definitely not. The only language I regularly work
| with that has this property is, surprise, Go.
|
| The proof is in the tasting, as they say. Go look at the gRPC
| daily benchmarks if you want hard data. Java beats Go latency at
| the median and at the tail while having substantially better
| specific throughput. In other words the Java GC has no tradeoff
| versus the Go GC; it is better in _every way_.
| feffe wrote:
| To be fair, early Java definitely had lots of issues with GC
| and "hanging" programs due to GC activity. It's a failure of
| the article to bring it up if modern Java implementation are
| better on that point. As an example of bad reputation, I think
| the latest Eclipse IDE on the latest Java SDK is still sucky
| and because of my ignorance I blame it on Java.
|
| I was disappointed with the first Go gRPC implementation, it
| allocated like crazy and was really slow because of it. It's
| been rewritten since then but I don't know how much better it
| is now. To get good performance out of Go it is important to
| think about memory allocations (unfortunately), just as it is
| in C and C++. Although allocating like crazy in C/C++ will
| probably mostly result in a general loss of performance, not
| affect tail latencies in network protocols much.
| Thaxll wrote:
| The Java gRPC SDK is heavily optimized by Google, much more
| than the Go one. Until recently Java pauses were a real issue,
|
| I worked with a lot of Java based solutions ( elastic search,
| hadoop etc ... ) and was never impressed by the GC.
| jeffbee wrote:
| Maybe, but it's still not possible that the Java test has a
| median latency of 150us, a tail latency of 350us, and a
| regular need to stop the world for hundreds of milliseconds.
| That statement is simply not compatible with reality.
| Thaxll wrote:
| As I said the Java SDK is heavily optimized to do the least
| minimum of allocations. I mean if you don't allocate much
| the GC is not really an issue.
| jeffbee wrote:
| OK but again, that is a statement totally contrary to the
| article. The article is claiming two things: that it is
| easier in Go to avoid the heap while Java is utterly
| dependent on allocating everything on the heap - which is
| not consistent with the experience of actual Java and Go
| programmers - and that Java has a "preference for high
| throughput and high latency" which it doesn't.
| Yoric wrote:
| FWIW, I used to write some high-performance JavaScript.
| One of the tricks of the trade was to allocate early and
| make sure you avoid allocating once the loading phase has
| started, hence avoiding triggering the garbage-collector
| (in most runtimes/languages, gc phases are typically
| triggered by the allocator). This involved writing very
| non-idiomatic code, to a large extent reimplementing a
| form of high-level custom allocator on top of the
| existing allocator/gc, but it worked.
|
| I'd be very surprised if the same wasn't possible in
| Java.
|
| I don't know if this is how gRPC is written but it
| _could_ explain an apparent contradiction.
| kaba0 wrote:
| While I agree that the java gRPC lib is better maintained, I
| don't agree with your second point. Java's GCs are the state
| of the art, that can manage heap sizes up to 16 _tera_ bytes.
| Other GC implementations would simply die there.
| throwaway894345 wrote:
| That's neat, but a lot of applications will never need
| 16TB.
| hashmash wrote:
| The author doesn't really understand how Java escape analysis
| works, and just focuses on one key aspect: "It does not replace a
| heap allocation with a stack allocation for objects that do not
| globally escape."
|
| The author then implies that escape analysis is only used to
| reduce lock acquisition. Java escape analysis will replace a heap
| allocation with a stack allocation if the code is fully inlined.
| This is known as scalar replacement.
| pjmlp wrote:
| I guess it is the same reasoning like Go not needing generics.
| geodel wrote:
| No, it is same reasoning as Java not needing dense memory
| layouts
| dandotway wrote:
| The binary-trees benchmark on The Debian Language Shootout[1]
| involves allocating millions of short-lived trees and traversing
| them. It is informative about GC performance even with the caveat
| that there are 'lies, damned lies, and benchmarks', because many
| real-world graph analysis and brute force tree search algorithms
| similarly allocate zillions of short-lived nodes. For non-GC
| languages like C/C++/Rust it gives a decent idea of the
| performance difference between malloc'ing and freeing individual
| objects vs doing bulk arena allocations: language
| secs GC'd language? ======== ====
| ============== C++ (g++) 0.94 Rust
| 1.09 C (gcc) 1.54 Free Pascal 1.99
| Intel Fortran 2.38 Java 2.48 yes
| <==== Lisp (SBCL) 2.84 yes Ada (GNAT)
| 3.12 OCaml 4.68 yes Racket
| 4.81 yes C# .NET 4.81 yes
| Haskell (GHC) 5.02 yes Erlang 5.19
| yes F# .NET 6.06 yes Node.js
| 7.20 yes Julia 7.43 yes Chapel
| 7.96 yes Dart 9.90 yes Go
| 12.23 yes <==== Swift 16.15 *
| "Automatic Reference Counting" Smalltalk (VW) 16.33
| yes PHP 18.64 yes Ruby
| 23.80 yes Python 3 48.03 yes Lua
| 48.15 yes Perl 53.02 yes
|
| So Java has the fastest GC for this test, 2.48 secs vs 12.23 secs
| for Golang. The Java code is also notably perfectly idiomatic for
| multicore, it doesn't do heroic "avoid GC by writing C-like code
| manipulating a fixed global memory array" tricks. The Java code
| is also more concise.
|
| The 'plain C' code that uses Apache Portable Runtime memory pools
| instead of standard malloc/free and uses OpenMP #pragma's strikes
| me as more 'heroic' than 'idiomatic', whereas C++ and Rust use
| standard libraries/crates and idiomatic patterns. (Note that
| OpenMP is 'standard' for high-performance C and well supported
| across GCC/LLVM/Microsoft/Intel compilers. But still....)
|
| OCaml and Haskell made impressive showings for functional
| languages which are in practice the easiest for dealing with
| complicated tree algorithms, which is perhaps why the formally
| verified C compiler, CompCert, is implemented in OCaml, as is
| Frama-C for formally verifying ISO C programs, as is the Coq
| theorem prover, etc.
|
| [1] https://benchmarksgame-
| team.pages.debian.net/benchmarksgame/... [Edited link]
| geodel wrote:
| > So Java has the fastest GC for this test, 2.48 secs vs 12.23
| secs for Golang.
|
| Further not mentioning memory used Java/Go programs makes it
| very fair comparison. Because GC perf does not depend on memory
| allocated.
| dandotway wrote:
| Right now in the datacenter CPU usage is considerably more
| expensive than RAM usage. Ram consumes comparatively little
| power, whereas burning hot CPUs+GPUs are the reason
| datacenters are favored near cooling water and power
| stations. 2.48 vs 12.23 seconds for Java and Go is a big deal
| for how many solar panels or tons of coal are needed to run
| an app on Xeon or Epyc instances, whereas 1.7GB vs 0.4GB for
| Java and Go, a 4x difference in low-power memory usage, is
| not so big of deal.
|
| At any rate I did link to the full table so everyone can see
| the mem usage, source listings, etc.
| [deleted]
| bitwize wrote:
| What it needs is Rust style static lifetime management.
| Yoric wrote:
| As a Rust developer, contributor and generally a big fan of
| Rust, I'd tend to disagree. Having a garbage-collector is a
| lifesaver for many algorithms. Static lifetime management is
| great but isn't something you want to force developers to use
| when they're more interested in coming up with solutions
| quickly.
|
| Also, I really don't see how it would work in Go. You require a
| pretty strong type system to make static lifetime management
| work. Unless something has changed radically in the recent
| past, that's not something that the Go community had much
| interest in (yes, I know that generics are one step in that
| direction, but I'm not aware of further steps being discussed).
| AtlasBarfed wrote:
| If you're at the point that serious GC performance is being
| examined, then a rewrite to rust is something that should be
| considered on the strategic roadmap if it is code you have
| control over (versus third party / OSS software).
|
| I think Go has a maximum expansion footprint. People
| presumably use Go because it is faster and better GC than
| Java. Rust will probably eat a lot of that territory. That
| will leave people that like it strictly for
| language/idiomatic reasons, and that won't be enough.
|
| The entire meta-point of the post is to try to argue that Go
| is "better" than Java GC-wise. Well, it is and it isn't in
| reality, as benchmarks and people in the know have said. If
| it is "better" it is a very unconvincing win.
|
| As someone wise said 10 years ago, you almost need 10x the
| performance to have a convincing improvement to get laypeople
| to notice, and to get dev people to consider switching from
| legacy/entrenched ways of doing things.
|
| Here, there's basically no real world improvement to point
| to.
| IceWreck wrote:
| > You require a pretty strong type system to make static
| lifetime management work
|
| Go is strongly and statically typed.
| Yoric wrote:
| It's a spectrum. Rust is further than Go in the direction
| of "strong type system", and somewhat lateral wrt Haskell,
| F# or OCaml, for instance (each is more strongly typed in
| different directions). Idris or Coq are further than Rust
| in most directions, etc.
| lucian1900 wrote:
| For some value of "strongly". It has nil and lacks sum
| types, so it is comparatively much weaker than even
| Rust/Swift.
| masklinn wrote:
| Go's type system is much, much weaker than what is needed
| for safe static lifetime management be even remotely close
| to workable.
|
| Static typing is not an on-off, there are statically typed
| languages with extremely weak type systems (e.g. C),
| languages with somewhat weak type systems (e.g. Java, Go),
| languages with strong type systems (e.g. OCaml, Haskell)
| and languages with extremely strong type systems (e.g. ATS,
| Idris).
|
| And that's a simplification because it's not linear either.
| remexre wrote:
| Rust's lifetimes need full subtyping, with covariant and
| contravariant type constructors (though I don't think it
| supports annotations for them, and always infers them
| instead), which I think would make the generics
| implementation quite a bit more complicated than it
| currently is...
| Yoric wrote:
| Yeah, the fact that co/contravariant type constructors
| aren't (or aren't always?) annotated has always felt a
| bit awkward to me. Ah, well, it works :)
| Ericson2314 wrote:
| This is mostly stupid. Being able to have many GCs is a good
| thing. The big reason for "value types" is controlling spacial
| locality in memory, not GCs being bad per-se.
|
| Also, they undersell the java/c# situation. C# has "ref, out, or
| in", but even without those, you can always make a reference
| wrapper that has the value type as a field. So "reference types
| suck because coppying" is nonsense garbage.
| throwaway894345 wrote:
| I mean, I've dabbled in all of these languages, and I much
| prefer Go's value types to ref, out, in, and a half dozen GCs.
| It's nice that these VM languages have a distinct thing for
| every eventuality, but I much prefer a single thing that works
| 99% of the time.
| Ericson2314 wrote:
| I _usually_ am bashing Go, but I am not this time. :D
|
| I am saying this blog author is confused on what the runtimes
| are capable of. I don't mean to say ref out and in are good
| language ergonomics or whatever, but simply that they show
| the compilation target is capable of expressing thing these
| things.
|
| The only thing I know of that the go runtime can do that
| these others can't is "interior pointers", i.e. pointers to a
| field of a larger object. You can always just copy the field
| into a new box, but that breaks mutation semantics. Java gets
| away with this precisely because most fields are themselves
| boxed...bu that's exactly the no-control-over-locality
| problem we're trying to solve.
___________________________________________________________________
(page generated 2021-11-23 23:01 UTC)