Go 1.19 Released
go.dev
go.dev
[0]: https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i...
And your sarcasm aside, Java has the state-of-the-art GCs by a long shot.
After years of real world experience, I can say that the Go garbage collector worked fantastically across a range of applications that I've been involved in. No tool is perfect for every job, of course, and having hundreds of knobs does not make Java perfect for every job either.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
That benchmark does not mean Java's GC is faster when you're "actually allocating on the heap". It means Java's GC is faster when you're spewing garbage onto the heap as fast as possible.
Since Go makes heavy use of stack allocation where possible, short-lived garbage usually doesn't end up on the heap, but this benchmark is designed to force that outcome. Go's garbage collector is optimized to minimize latency and STW, but this does come at the cost of decreased throughput. The opposite can be said of the majority of Java GC implementations, which trade latency and STW time for increased throughput.
This depends on implementation. ZGC is not generational yet, but I don’t see how generational or not would invalidate a GC benchmark for the default implementation.
> That benchmark does not mean Java's GC is faster when you're "actually allocating on the heap".
It actually does at least in the discussion of _default gc implementations_. Just because the default (and only) implementation for Go sacrifices throughput (and relies on the compiler to stack allocate) doesn’t invalidate the benchmark.
And honestly if I was in Vegas I’d still bet that ZGC (Java’s non-generational, latency sensitive GC) would beat Go’s implementation here.
I never said the benchmark was invalidated, I said that you interpreted the meaning of the benchmark wrong. I like how your reply completely ignored what I said the benchmark meant. That benchmark has a very narrow focus, and generational makes a huge difference for that one benchmark.
> And honestly if I was in Vegas I’d still bet that ZGC (Java’s non-generational, latency sensitive GC) would beat Go’s implementation here.
I would love to see a holistic set of benchmarks comparing them. Even just that one very narrow benchmark you linked would be fun to see — if ZGC is so good, surely one of the implementations of the benchmark on the website is using ZGC? But I haven’t had time to dig through them.
But, part of Go’s charm is that idiomatic code rarely puts tons of pressure on the GC anyways, and a GC can never be faster than stack allocation for a multitude of reasons… which is also why C# has value types.
But, if I understood correctly, that design both requires you to explicitly mark a type as a value type when you define it and it doesn’t support inheritance, so I’m skeptical that it will see any real adoption for a long time.
Java hasn’t had decades to develop a culture around this feature the way that C# has, and neither are like Go where everything is value typed unless it is one of the built in pointer types, or you explicitly put the value behind a pointer. (The pointers themselves are values, of course, but I think few people are interested in that distinction here.)
Go isn’t perfect by a long shot, I just don’t think adding the millionth keyword is going to be a silver bullet for Java’s predisposition towards GC pressure.
Also, Java is an exceedingly small language, so the millionth keyword comment is unwarranted.
Small compared to what?
Java and C++ are extremely competitive in feature bloat, and I can hardly think of anything that is comparable to them. Even C# has a smaller surface area, in my opinion, even though it manages to implement a variety of useful features that Java currently lacks (like async/await and value types), but C# is still a large language — no doubt about it. C# just manages the interactions of its features better than Java, in my opinion, which makes it feel simpler.
I have honestly never heard anyone call Java a small language. It is far from that!
> Java’s standard lib has been planned with value types in mind for some time now, plenty of classes will be able to take advantage of them.
Aren’t the majority of the standard container types using some form of inheritance? That’s what I recall, and that rules them out.
But Java? It only has classes, inheritance (no multiple inheritance as c++), interfaces, objects which are instances of said classes, 7 primitive types and I am basically at the end of java’s feature list. Lambdas are often hated because they were implemented at first as classes with a single method (they no longer compile to that), so they are syntactic sugar only in a way. Classes can have static methods as well, and there are 3 visibility modifiers (which are also language feature of Go, just implicit in naming convention).
Everything else is library calls in the language.
Not even close. There are quite a few keywords that modify behavior, including abstract, final, transient, synchronized, etc. You also have method overloading, including constructors which use a separate syntax from normal method declaration. Java also relies heavily on non-linear control flow thanks to exception handling. Go has panics, but it isn’t normal to see them in my experience, and indicates a serious bug with your code, whereas Java exceptions are extremely normal to see… but that doesn’t mean non-linear control flow is simple.
All Java classes have implicit methods like toString and equals, which you need to be aware of, and then this also lets you override the behavior of the equals method… yet you can’t override any operator, for some reason. And the actual equals operator is not at all what the average person expects when they’re getting started in Java, since it is referential equality, not value equality, except when it isn’t. I guess it is “simple” that the language lets you override the equals method, since that is consistent with other methods, but why is it a method at all? Why doesn’t the equals operator just do the expected thing, which would be equivalent to calling an invisible, non-overridable equals method? If no operator should be overridable, then no operator should be overridable, and the equals method is effectively an operator since the regular equals operator is just a footgun most of the time, except for the cases where it is performing value equality checks. Java has other footguns that stem from the language, not the standard library, like hashCode. I could go on, but really, why bother?
If you only want to talk about the basic features of the language and ignore the many keywords and edge cases you can run into in the syntax, then even C++ is simple. It’s the interaction of all these features that makes the language complex, including the implicit nullability of almost everything.
> there are 3 visibility modifiers (which are also language feature of Go, just implicit in naming convention).
Java actually has four access modifiers, not three, one of them is just implicit.[0] Go only has the notion of public and private, and private in Go is probably closer to the "default" access modifier in Java.
Compare to actually smaller languages like Go, or really small languages like Lua, Lisp, Tcl, or Smalltalk.
Java isn’t the worst language by a long shot, but it is one I’m unlikely to intentionally pick for anything.
You’ve left a bunch of comments all around this thread defending Java against even the smallest slights. I’m not interested in debating pointless aspects of Java forever this morning. I’ve used Java. I’ve used C++. I’ve used many languages in many contexts. You may disagree with my conclusions, and that’s fine. In my experience, Go takes less code and is less error prone than Java, in addition to being nicer to deploy and run. That's enough for me to choose it over Java, even if Go isn't perfect either.
I have zero problem with anyone choosing their favorite language for some job, but do not make a language look better by spreading bullshit about another (GC-knobs, java language being complex).
The benchmark doesn’t show it, but it is nonetheless true. Java uses thread local allocation buffers, which is possible due to moving GC, and it is literally as fast as it gets (a single, non-atomic pointer bump)
Java’s GC design requires more barriers which hinder performance throughout the lifetime of a heap allocation, not just at the time the value was allocated.
Either way, Go also uses per-thread caches in its allocator to avoid contention between threads. I don’t believe it is a bump allocator, but this goes back to other tradeoffs being discussed. The number of barriers, the duration of STW, etc.
I don’t understand why so many people in here feel the need to declare the superiority of Java GC. Java’s garbage collectors are better for Java, due to the way that Java allocates practically everything on the heap. That doesn’t mean they have zero performance tradeoffs and that they’re perfect. They do have tradeoffs, and not just in complexity of tuning.
“Generics are great. we haven’t gotten around to doing it the right way”
vs
“if we start introducing knobs, soon enough, knob turning is going to be a full time role “
Nice improvement to switch statements!
> optimization of switches would be different for different architectures. every x86, amd, intel, and then all the non x86s. they all have very different execution times and cache times and pipeline times. there is no single answer. there is usually a huge pipeline cost for a computed jump.
> when you are considering native-client restrictions, jump tables become impossible.
> also note that go switches are different than c switches. non-constant cases have to be tested individually no matter what.
> having said that, a large (where large depends on architecture) dense switch would be faster than what is implemented now. BUT there are two parameters needed to implement it. how large and how dense. no matter what is used for large and dense, there will be a micro-benchmark on some machine, under some alignment, with some branch cache that will look wrong.
> what is implemented is as follows.
> 1. in order, all non-constant cases are compiled and tested as if-elses.
> 2. groups of larger than 3 constant cases are binary divided and conquered.
> 3. 3 or fewer cases are compared linearly.
> i honestly dont think there is a much better algorithm across all machines.
exec.LookPath("prog")
you now have to explicitly say exec.LookPath("./prog")
to find prog in the current directory. The os/exec docs have more info on how to error handle this correctly.Edit: checking the actual os/exec docs instead of the release notes, it’s actually not as bad as it sounds. What the change actually seems to be is that relative paths are now ignored even if they are in $PATH.
But this is a change on Windows, where bad practices stick around because backward compatibility. (Lots of things would break if cmd.exe changed; but fortunately they fixed this in PowerShell.)
[1]: https://people.engr.ncsu.edu/gjin2/Classes/246/Spring2019/Se...
A demonstration of the issue: https://go.dev/play/p/R9dm6uYZSas?v=goprev which fails in 1.18 but passes in 1.19.
A little background of the issue:
1. Go uses UTF-8 encoding. UTF-8 is a superset 7 bit, 128 code point ASCII. (Two UTF-8 creators, Rob Pike and Ken Thompson, are also two of Go's creators.) 2. All UTF-8 points over byte 128 must be encoded as two bytes. All code points 128 and below are encoded as a single byte. 3. Single byte code points between 129 and 256 are valid in Go, but not as UTF-8. 4. Go uses different notations to make the distinction between 129+ code points that are two bytes and 1-256 code points that are a single byte, notably the code points in the 129-256 range. Go represents single byte code points with the \x notation, "\xXX", and multi-byte code points with the \u notation, \uXXXX. All ASCII and single byte 129-256 codes should always be represented with "\x" notation.
The core of the issue is that Go was printing code point 128, \x7F, as \u007f. As previously said, the "\u" notation is reserved for valid UTF-8 points past the ASCII range, which are always encoded as a minimum of two bytes. \x7F is a valid single byte code point in the ASCII range and should not be printed in the "\u" notation.
We discussed this issue on an old thread, and Rob filed a Github issue https://github.com/golang/go/issues/52062, and it was fixed within hours.
So the following line in the release note is us!
>Quote and related functions now quote the rune U+007F as \x7f, not \u007f, for consistency with other ASCII values.
How did we find this? We're fans of arbitrary base conversion (https://convert.zamicol.com) and we discovered this issue while checking our work in our Go libraries. If encoded as two bytes, base conversion would be less useful.
Thanks Go team!
That's code point 127, which has always been quite a special case as the only non-printable ASCII codepoint larger than the smallest printable codepoint.
> All ASCII and single byte [128]-256 codes should always be represented with "\x" notation... the "\u" notation is reserved for valid UTF-8 points past the ASCII range... all valid UTF-8 "\u" notation characters must be a minimum of two bytes.
Says who?
I mean I guess some consistency (though, with what?) is nice, but you should be plenty prepared for the other format too. `strconv.Quote` is only meant to be equivalent to Go's source string literal representation and that allows both, and JSON only allows \u, and Python's source literals also allow both, and and and....
> UTF-8 points... single byte code points... multi-byte code points
This is... at best, unnecessarily and fundamentally confusing language.
The playground example (https://go.dev/play/p/R9dm6uYZSas?v=goprev) is a great example. All single bytes, 0-255, print as the printable character or if non-printable as \x except 127. There's nothing special about 127 to deserve this.
Inversely, \u denotes multibyte for all code points except 127. Once again, why is 127 special?
There's two possible fixes: note that 127 is special (even without a reason, but at least document it), or change the behavior to align with everything else. UTF-8 itself was a response in part to perceived arbitrary decisions made in other encodings; I'm not surprised that the second fix was preferred.
Our chief concern was how many bytes were used in encoding, and that's when we ran into this issue. If not fixed, our tests in our library had to notate why 127 is special (because Go says so), or hope for a change. Now that it's fixed, there's no need for downstream documentation.
It's a minor change, but now no one else ever has to spend the time we took to look into this issue because now there are no surprises. That makes it worth it.
>That's code point 127
How does that joke go? There's only two hard things in computer science...
Now we need to lockstep our team's version upgrade since I just learned some tooling will otherwise bounce it back and forth. "Thanks."
> There's nothing special about 127 to deserve this.
Other than the thing I said, which means it is now literally a special-case in the fmt code where it wasn't before.
That sounds like an interesting issue. Could you perhaps go into more detail?
>means it is now literally a special-case in the fmt code
No. There is no special case, and the logic (literally) runs in the same switch case. https://go-review.googlesource.com/c/go/+/397255/4/src/strco...
- case r < ' ':
+ case r < ' ' || r == 0x7f:
does not introduce a special case, I don't think we'll find any accord through discussion.Range selection is not a special case, and it's equivalent to writing it as `case r == 0x00, r == 0x01, r == 0x02 ... r ==0x7f`. The short hand, `r < 0x20`, is far more readable. I would reiterate, the lack of the need for another literal `case` shows that logically this is not a special case.
In the sense of the abstract idea of ASCII, 0x7f is unique in its position, but so are all characters. There is no meaning in its positional placement in ASCII. It's totally arbitrary and was thought to be a useful convention. If position denoted other relevant, and unique, meaning to printing, then yes it could be a special case in certain circumstances. But its position has no additional information. And that's the key, no information means no special case.
This is also not true.
Every time new version of Go is released I am thinking about updating it, but then why if it works...
As someone whose primary dev machine runs Windows, this is a very convenient improvement to a pitfall I ran into. I probably won't stop manually adding the JS mimetype for a while though.
> Go’s memory model now explicitly defines the behavior of the sync/atomic package.
[1]: https://pkg.go.dev/sync/atomic@master#Pointer
Generics are a language level feature, so it must be done at the language/compiler level, otherwise people are forced to implement it using "go generate" or other metaprogramming. The standard library doesn't ever have to change in that regard. If people want a generic version of some function, they can just write it themself. Its possible/likely that more generic functions will be added to standard library, but its not a foregone conclusion that it needs to happen, or even should happen.
A lot of exciting new features, but really happy to see this one.
Cowards!
There’s just nothing comparable from a developers productivity perspective to what Rails offers.
Would I write a command line tool in Go? For sure.
How do others think about Go for web dev?
Simpler apps or services are simple in Go. Apps with heavy customization are also great with Go, and there's now plenty of libraries to help you outsource heavy lifting.