> Go 1.17 implements a new way of passing function arguments and results using registers instead of the stack. Benchmarks for a representative set of Go packages and programs show performance improvements of about 5%, and a typical reduction in binary size of about 2%. This is currently enabled for Linux, macOS, and Windows on the 64-bit x86 architecture (the linux/amd64, darwin/amd64, and windows/amd64 ports).
I love how they're doing it in such an iterative fashion: even assembly functions don't have to be rewritten. Then again, I guess doing it progressively like this is the only feasible way to avoid reworking all the low-level assembly routines in one fell swoop.
$ time goawk_go1.16 'BEGIN { for (i=0; i<100000000; i++) s += i; print(s) }'
4999999950000000
real 0m10.158s ...
$ time goawk_go1.17 'BEGIN { for (i=0; i<100000000; i++) s += i; print(s) }'
4999999950000000
real 0m6.268s ...
I wonder why it's so much better than their advertised 5% perf improvement? Here's a quick CPU profile: https://i.imgur.com/csJyOYq.png ... I don't get too much out of it at a glance, just seems like everything's a bunch faster.I use VSCode a lot, and in VSCode you can press "F12" to jump to the location of a function definition. In Go I find myself deep diving into github libraries all the time just because F12 takes me there. That never happened with C/C++ - the tooling was just too disparate and fragile.
My biggest complaint with Go is lack of generics. I mean technically you can achieve generics with an unholy empty interface/reflection incantation but it's a huge pain compared to C++.
Truly, it is night and day. I can get Real code intelligence on almost anything, including large C projects with custom Makefiles that compile cross-arch like Wine. It only ever has trouble with unusual translation unit patterns. (You can see this in Higan for example, which has .cpp files including other .cpp files; clangd seemingly fails to put them in the context of the intended translation unit.)
It’s not as good as Go, but it is life-changing for me.
It's not like Go's tooling is that much better than the best modern C++ tools, but the ecosystem is so much more unified by having 1 blessed set of tooling. Rather than 20 different package managers/(meta)build systems. No need to worry that I am using build system X, but I want to use a library using build system Y.
But if you're coding C/C++ in a "proper" IDE like Visual Studio, CLion or Xcode, such features are expected to work out of the box. IME, Intellisense-like features are usually only a pain to setup in "non-integated" development environments like Vim or other text editors.
After a while I actually understood why Go is such a successful language / ecosystem:
Go's priority is to make projects easier. It is doing so by all it's smaller and larger features respectivly skipped features. But one soon understands the big picture of the Go team. Go is designed by very experienced devs who knew what is important and what not.
In my Go projects, I don't have to worry about:
- Memory safety
- Tooling
- Performance
- Structure (once I understood the package desgin philosophie)
- Difficult syntax
- Concurrency
- Libraries, as we can do most with the standard lib
- Maturenes and stability
Instead I focus on the things that count:
- Solve the problem at hand
- Create correct, stable and maintable software
And as this was not enough, the Go team comes around the corner with an 5 % average performace gift.
Awesome.
It's enough in 95% of cases and it works every time in every environment. In the time it takes me to get a debugger running and attached to a process, I've print-debugged it and fixed it already =)
You have no idea how amazing that feels.
By 3.6 it was pretty good though and even migrations and dual code bases were easier because they went back and allowed things like u"" for unicode strings to continue to mean unicode strings in python 3 - before that they'd actually blocked unicode in 3 that was working well in 2.x code bases! It was a total in your face move to break the ecosystem even though supporting u"" would have been almost no cost in terms of improving compatibility with existing code. ASCII handling (important for things like web tech stacks header handling etc) also improved thankfully. 3.x made things a lot harder initially (and slower too).
Also a lot of code doesn't look the same over time - the conventions (formatting) are either very poorly defined or change. Go seems to have pretty much one format from fmt
etc
The more Go code I write, the more I like the language, the tools, and the standard library.
Low-friction, high-quality software development.
If only I could use it professionally, instead of C++.
When you have a team of varying skill levels who need to deliver shit that works on a schedule and still have some kind of performance, you use Go.
Even more so if you need to hand off the maintenance to a team of randoms from god knows where and THEY need to be able to keep it running and add new features.
It doesn't have the fanciest new EXPRESSIVE operators or anything exciting really. It just does the job, albeit verbosely.
You can do fancy shit on your own time.
I don’t know what to think about the upcoming generics. It feels late to make such a big change so long after the language has been established. At least a plan for it should have been integrated into the language from the start.
It feels like a missed opportunity - an effort with similar funding but a more sound theoretical foundation than being Newsqueak 3.0 could have become an industry game changer. Instead it fills a niche, which is a success, albeit smaller.
I agree with that. I think looking back on it, Go arrived on to the scene at a perfect time where lots of people were rearchitecting so there was lots of natural appetite in the industry for a new language. Go vacuumed up a lot of this opportunity but ultimately hasn't really evolved the practice of software engineering all that much.
This is recoverable. The Java world was also saddled with a decade of awful pre-generic code, but we burned almost all of it to the ground and started over.
That's a pretty good description of Rust 0.x. Might ring a bell.
Once generics are finally added the language should basically be a no-brainer for any serious production code.
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]
Both seem harder than necessary.Is that code just idiomatic, and Go programmers recognize it instantly? Or maybe they don't deal with slices that often?
a = slices.Delete(a, i, i+1)
That said, in code I write, I rarely need to do an in-place delete from a slice. I think it's rare enough that recognizing the idiom is okay.You can restructure it if you don't need to preserve order:
a[i] = a[len(a)-1]
a = a[:len(a)-1]
In general* Go does not hide complexity from you. It's a blessing and a curse.*: because the Internet is a nitpicky place.
For every complain like that in Go I'll find a hundred in C++, ten in Java / JavaScript / Python.
As to your question, as a full-time Go programmer: this doesn't come up often.
When it does, I google "slice tricks" and copy & paste the formula.
It'll be fixed by next release because generics will enable writing a function that does that and that function will be in standard library.
Why wasn't this fixed before generics? Because the only way to do it would be via a compiler built-in function and Go team is rightfully reluctant to bloat the language / compiler for minor functionality like that.
On the second glance, one can see why probably it was left out: there are two fundamental different ways to implement the deletion. You can copy the last element into the slot of the deleted element, or you can move all elements after the deleted one place to the left. The latter is what you implemented and preserves the ordering, which often is desirable. The former is way more efficient, if you don't need to preserve the order. A default implementation would probably choose to preserve the order and thus introduce a systematic inefficiency which is easy to be avoided.
Fortunately, with the introduction of generics and especially the new slice package, this discussion becomes moot.
For now you just have to accept these quirks as part of the tradeoff of working with the language. I don't find it that big of a deal esp with the solution on the horizon.
Have you ever had to remove elements from a list/slice?
I didn't completely expect it, but I now find myself reaching for Go first when writing something like a small script or utility that I'd previously have written in something like Ruby or Python. It all feels much more solid.
That said… I really chafe against the anaemic type system when writing anything a larger than that. I hate not being able to communicate things like "this can never be nil and if it's is then it's an error" to the compiler. I resent having to write three lines of boilerplate every time I want to reverse a flipping 10-item slice, and the design decisions leading to the existence of `time.IsZero()` are bordering on criminal.
I don't know if I'm the odd one out for feeling like that – parts of the language and ecosystem are just absolutely wonderful to work with, but the unergonomic sharp edges bother me so much that I end up finding it really _annoying_ to write a lot of it.
Up above I talk about gopls's postfix completions, and reversing a slice is one: https://github.com/golang/tools/releases/tag/gopls%2Fv0.7.0
What's the rationale for Time.IsZero()? Seems like every use case for Time.IsZero() I think of is a code smell. The 1970 epoch is an implementation detail that should be hidden or configurable. People probably use IsZero() as a sentinel value to indicate "undefined time", even though the 1970 epoch is a valid point in time.
Go has a notion of "zero value". When you don't assign a value explicitly it'll be set by the compiler to "zero value" of that type.
This is much better than C/C++ of "random value".
For primitive types, the compiler decides what "zero value" is. For structs, each component is set to its zero value.
For good reasons (language simplicity) Go doesn't allow the user to declare what the zero value is (something that you can do in e.g. C++ via a constructor).
Time is a struct. Its zero value is the same as for any other struct and not meaningful time value.
It's a pragmatic necessity to be able to query "is this time value an unset time value?". A pragmatic solution for this need is to provide IsZero() method on Time struct.
// IsZero reports whether t represents the zero time instant,
// January 1, year 1, 00:00:00 UTC.
func (t Time) IsZero() bool {
return t.sec() == 0 && t.nsec() == 0
}Go 1.17 Beta - https://news.ycombinator.com/item?id=27462884 - June 2021 (118 comments)
Go 1.17 is deprecating the traditional use of 'go get' - https://news.ycombinator.com/item?id=27630625 - June 2021 (215 comments)
Very good for the ecosystem. Nudge folks to upgrade from broken/insecure versions
This is a very welcome change and make the go.mod much more obvious to understand. Hooray!
[1] https://groups.google.com/g/golang-dev/c/U7eW9i0cqmo/m/ffs0t...
Now add pattern matching please ! Well done to everyone that worked on Go ! It's still a pleasure to use.
The way I see it, both Go and Java are crawling towards the same ideal: a compiled, statically typed, garbage collected, modern enough program language targeted for a majority of back-end concerns. Go has to close the gap mostly just with generics; and Java with memory layout (Valhalla), concurrency (Loom), and ergonomics (Amber); but they will both be there in 3-5 years.
Take this with a grain of salt if you like, but while I probably find it more fun to program in Go, I don't think there's a compelling reason to switch a large codebase from Java to Go. It often feels like my company is needlessly divided by programming languages even though they solve fairly similar problems. It's one thing to move off of a dying language, but Java is certainly not that.
Otherwise try a "go mod init" in the root of your project.
Yay!
It doesn't encourage you to do fancy language tricks, it kindly directs you to get your shit done, compiled and delivered.
Frankly, people are making boatloads of money off of software written in Python and JS--languages which are both far less safe than Go and far slower and yet Go is on par with those languages with respect to productivity (I argue it's more productive due to its static typing features, but others argue it's less productive presumably because of the learning curve). Most software doesn't need to be so fast or correct.
Also, I work in distributed systems and SaaS (software as a service). In this world, most errors are silly type errors ("undefined is not a function", forgetting to `await` some async function, etc) that would be caught with a flat-footed type system like Go's. Beyond that, the next largest bucket are issues that even a sophisticated type system like Rust's wouldn't help with, such as infrastructure issues (missing/misconfiguration of some cloud permission, networking rule, etc), misconfiguration of the service, race condition with many processes accessing the same networked resource, deployment error, etc. This means that Rust's stated advantages aren't really as nice as they initially sound.
As a programmer, I can appreciate a super fast language with a strict type system; however, as an engineer or a technologist, Rust is rarely a good fit for me. Go seems to be a lot closer to the sweet spot, which makes sense because it was developed expressly with distributed systems in mind.
I can’t speak to python, but typescript has basically fixed this problem overnight in the javascript ecosystem. The typescript compiler finds almost all small bugs like this while I’m coding. And as an added bonus, type hints allow the IDE to be much more helpful - adding to jump to function support, autocomplete, method parameter suggestions (or documentation on hover). And typescript is easier to read than javascript because you don’t have to guess what data type some variable is.
I love javascript’s quick and dirty nature, but I still use typescript instead of javascript now for any code I write that I expect to survive the week. (And as a bonus: you still get IDE type hints when calling typescript functions from raw javascript!)
Typescript’s type system is also more powerful than Go’s. It supports enums, genetics and type unions (eg x: string | number).
Go sits in the awkward place of having a worse type system and no significant advantages over typescript for me. It’s awkward to use go on the web. Go is faster than javascript on the server but that usually doesn’t matter. And when it does I can reach for C or Rust. Both of which work really well with native code, or with JS through wasm.
Rust is much harder to learn than Go - but personally I’ve climbed that hill already. Once you’re over that hill, Rust is much more expressive. On purpose both ways - go isn’t trying to be expressive, and rust is. I love rust’s parametric enums and output types in traits. Which I now sorely miss in other languages.
I can imagine Go shining a lot more brightly when working with a team which has mixed skill levels. Most of my work lately has been solo, so I don’t need gofmt to enforce a consistent code style, or anything like that. I do miss Go’s green threads. Rust’s afterthought scattergun approach with threads and futures feels like a mess. But I can’t see myself ever really using Go. It’s weak in areas I want it to be strong (eg the type system). And strong in areas I just don’t care much about. (Eg consistency).
The borrow checker and GC aren't really doing the same thing though. One very important distinction in some fields (but unimportant in others) is that GC only really cares about memory resources. So you are (or should be) doing explicit manual cleanup for all non-memory resources in a GC language. Rust isn't, in Rust we don't write explicit resource cleanup for a programmable interrupt controller, or a file, or a database connection -- the resource knows how to manage itself, and Rust promises to tell it immediately when it falls out of use, whereas a GC can't promise to ever clean up which is why Go doesn't even bother providing a means to do this automatically for your non-memory resources when they're garbage collected (Java does, but again, no promises it ever fires, so, don't rely on this).
Even if it weren't for the performance I would use Rust over Go due to the lack of generics alone, as well as the associated general philosophy of Go that boilerplate is good and one should repeat oneself as much as possible.
Yes you have cargo flamegraph for profiling locally and you now have pprof-rs to mimick Go's embedded pprof support. But allocation heap profiling is still something I struggle with.
I saw there was a pprof-rs PR with a heap profiler but there was some doubt as to whether it worked correctly; to get a feeling of how that approach would work but without having to fork pprof-rs I implemented the https://github.com/mkmik/heappy crate which I can use to produce memory allocation flamegraphs (using the same "go tool pprof" tooling!) in real code I run and figure out if it works in practice before pushing it upstream.
But stuff you give for granted like figuring out which structure accounts for most used memory, is very hard to achieve. The servo project uses an internal macro that help you trace the object sizes but it's hard to use outside the servo project.
The GC makes some things very easy, and it's not just about programmers not having to care about memory; it's also that the same reference tracing mechanism used to implement GC can be used to cheaply get profiling information.
Have you tried this one? https://github.com/koute/memory-profiler
Switch to a dumb allocator and then profile mmap calls or page faults? That should get you large allocations at least. It's a pretty crude proxy. The other allocation profilers I'm aware of cause significant slowdowns.
Rust is great for language enthusiats doing hobby projects or for learning. The Rust book is great. Rust has great language features.
Having that said, Rust is the right tool for the job for a very small niche. Basically, if you would have used C++ before and don't need much developer reach or mature libraries. And only for the rare cases you really cannot affort a GC (even though Go's GC is highly optimized).
Go on the other hand, is an industrial strength proven and mature general purpose language. It is the best fit for various kind of networking application. Especially APIs, but also infrastructure where you can live with an GC. CLIs are great with Go as well.
If you're working on a professional grade project (where the GC is acceptable), Go is much superior than Rust in all regards.
Rust is advertised for years and years and didn't have it's breakthrough yet. This empirical fact cannot be ignored. There're reasons for this, of course. Some are:
- Writing Rust consumes so much more mental power with so little gain. That mental energy should be directed to solving the problem.
- Go makes everything besides your problem at hand easy. You can focus on solving your problem, not fighting the Borrow Checker
- Rust's ecosystem is not reliable. Many essential libs are one-man-shows. Version 0.1 everywhere.
- Rust is for and by language enthusiats. If you need to rely on libs for longer than a couple of years it is a hight risk for your project
- In terms of real world performance: Go is so close to Rust that there're very very very few use cases that really need that marginal gain