Live data from Hacker News

Making a Go program faster with a one-character change

hmarr.com

131–140 of 249 posts

Re: Making a Go program faster with a one-character change

#131
post #95

This is the kind of stuff that the compiler needs to really understand. If all this de-referencing and referencing magic is at the control of the user, it needs to have meaningful effect on what the code does. Otherwise we might as well just write C.

The compiler does understand it and did what was asked - it was just written rather poorly.

There are valid use cases for wanting to take a copy, and then pass along a pointer of the copy. Perhaps to go through a series of modification methods that don't touch the original. I'd sure hate it if the compiler tried to outsmart me on that and changed the behavior away from what I'd written.

Re: Making a Go program faster with a one-character change

#133
post #21

As an old C/C++ programmer, I'm always surprised by how often software developers are surprised by the performance costs of inopportune value semantics (C and C++ even more so, punishes you severely for using value semantics when you shouldn't). I increasingly see the wisdom of languages with implicit reference semantics. It's not that value semantics can't be better (they most assuredly can be), or that reference se…

Becoming good at Go is mostly knowing all the sharp edges with the built in types IMO. Of course go routines and concurrency primitives are difficult to master, but that is a different beast and if you understand concurrency from some other languages Go just makes that easy. But knowing all the behaviors of slices, and really intuiting how they work makes your life a lot easier and a lot less prone to bugs. And slice behavior almost all comes down to what is a slice internally and how and where am I copying things as I wrote my code. Generally the copying is fine, but in some cases it is not or it is in a tight performance critical section where you need to be thinking about it.

Esit: Also, pointers into slices will probably leave you sad. You get a pointer to the slice storage, not a pointer to the thing. And if the slice resizes your pointer mow references a dead slice. Basically, pointers and slices are not friends. Unless you have a slice of pointers, which idiomatic go avoids unless there is a decent reason for it :)

Re: Making a Go program faster with a one-character change

#134

Earlier quoted context omitted.

What I found frustrating that there is nothing stopping a function from returning both a non nil value and a non nil error. I've seen weird code that did that and it could have easily resulted in incorrect behavior.

The one place where you’ll actually see this is in Read(). If you try to read 1000 bytes, you might get “800 bytes read, EOF” as a result. The worst part is that os.File won’t ever do this!

I really hate when people use "errors" for signals. Or, alternately, use the signals mechanisms for actual errors. An error should be "something went wrong", and reading a file to the EOF is not "going wrong", that's just what the "read()" should do if you tell it so!

I like Go's multiple returns and error checking by default, but it definitely should have been implemented with some sort of "Result/Error" type union instead. Go made so many good decisions, that I am frankly amazed they made this one really bad one.

Re: Making a Go program faster with a one-character change

#135
post #130

Earlier quoted context omitted.

In general this doesn't work, the history rule says mutable types are not proper subtypes of immutable ones (and the converse is obvious). If you want to capture mutability in your type system, it needs to be orthogonal to subtyping (like C/C++ const).

C++ still has this problem - std::unordered_map ` and `std::unordered_map ` are basically unrelated types - you can't const-cast the templated const away. (I may be misunderstanding here)

> you can't const-cast the templated const away.

That seems like a good thing. If you're handed a map to const values you can't just go "imma gunna mutate them anyway".

Re: Making a Go program faster with a one-character change

#136
post #11
post #6

Went from 4.139s to 2.413s. I fail to see how it is 70%. I think it is explained as 4.139/2.413 = 1.7 which of course doesn't make sense here.

Doh! Thanks for pointing out another silly mistake – I'll fix that.

@hcm: Would have loved to see the 'after' flamegraph just for comparison purposes! I'm still trying to get used to groking flamegraphs when optimizing. They're a somewhat new tool, IMO.

Re: Making a Go program faster with a one-character change

#137

Earlier quoted context omitted.

What I found frustrating that there is nothing stopping a function from returning both a non nil value and a non nil error. I've seen weird code that did that and it could have easily resulted in incorrect behavior.

The one place where you’ll actually see this is in Read(). If you try to read 1000 bytes, you might get “800 bytes read, EOF” as a result. The worst part is that os.File won’t ever do this!

Presumably this is for performance reasons: if a `Read` call involves, say, a network request, then returning "800, EOF" in one roundtrip is certainly nicer than "800, nil" -> "0, EOF" in two roundtrips. In practice... I suspect this happens rarely enough that it's not worth the cost. The performance edge case could be handled with an alternative API, e.g. an optional EOF() method.

Re: Making a Go program faster with a one-character change

#138
post #89
post #21

As an old C/C++ programmer, I'm always surprised by how often software developers are surprised by the performance costs of inopportune value semantics (C and C++ even more so, punishes you severely for using value semantics when you shouldn't). I increasingly see the wisdom of languages with implicit reference semantics. It's not that value semantics can't be better (they most assuredly can be), or that reference se…

I think the main issue in C++ isn't value semantics, it's deep copy semantics. E.g. in a functional language ADTs are immutable and don't have identity. They can be freely copied, or not, passed by reference or by value, but they are never deep copied . Comparison may be deep, but not passing them. That is to say, I think I mostly am agreeing with you. In Java, objects are always passed by reference, never by value,…

I still don't see why value structs need to be immutable; ints are mutable in all languages, and structs are mutable in C, C++, and Rust (if you `let mut`) and it's a feature of the language.

Re: Making a Go program faster with a one-character change

#139

> I did consider two other approaches: Changing Ruleset from being []Rule to []*Rule, which would mean we no longer need to explicitly take a reference to the rule. Returning a Rule rather than a *Rule. This would still copy the Rule, but it should stay on the stack instead of moving to the heap. > However, both of these would have resulted in a breaking change as this method is part of the public API. The problem wi…

Sometimes it doesn't matter if a public API is incorrect, because it's set in stone for whatever reason, and you just need to fix the problem internally.

Re: Making a Go program faster with a one-character change

#140
post #21

As an old C/C++ programmer, I'm always surprised by how often software developers are surprised by the performance costs of inopportune value semantics (C and C++ even more so, punishes you severely for using value semantics when you shouldn't). I increasingly see the wisdom of languages with implicit reference semantics. It's not that value semantics can't be better (they most assuredly can be), or that reference se…

The performance issue here is not value semantics, it's the overhead of automatic lifetime management. The copy is cheap. The lifetime tracking is not because it forces a heap allocation and creates additional GC pressure. In fact, assuming Rule is small, if Match returned by value, the code would be similarly as fast.
Post reply on HN