Live data from Hacker News

Making a Go program faster with a one-character change

hmarr.com

181–190 of 249 posts

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

#181
post #33

Earlier quoted context omitted.

> I've only ever found myself missing value semantics when I use languages with implicit reference semantics. Oh, I miss it every time. ;-) I will say though that some newer languages seem to have a confused idea about how to offer mixed semantics. A bunch of them tie semantics to types. The ideal interface can vary by usage context. It's hard enough getting the semantics right as the callee (as opposed to caller), l…

I think this is half right. For anything 64 bits or smaller, value semantics are pretty much always going to be better. That said, being able to choose between value and reference semantics for larger objects per object is a pretty useful feature.

> For anything 64 bits or smaller, value semantics are pretty much always going to be better.

That's assuming a 64-bit CPU (which admittedly seems like a reasonable assumption. The nice thing about the abstraction though is that there's nothing preventing the runtime from applying value semantics for those trivial small-object cases where they're obviously more efficient.

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

#182

The deeper lesson here is "don't use pointers unless you're sure you need them". I've seen quite a few people use pointers for no reason in particular, or there's simply the assumption it's faster (and have done this myself, too), but it puts a lot more pressure on the GC than simple local stack variables. Of course sometimes pointers are faster, or much more convenient. But as a rule of thumb: don't use pointers unl…

Most of the times I use a pointer just because I want to return nil in case of error. Is this a valid reason?

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

#183

Earlier quoted context omitted.

Sorry, because of value semantics. (Also, you wrote "performance costs of inopportune value semantics". So the correction is annoying; you know what I meant.) This stuff seeps in without the programmer realizing it because Go made a deliberate design decision to have automatic lifetime management. In other words, this is a feature of the language and not a bug. The only way for this to not seep in is if Go forced pro…

Sorry for being annoying. I wasn't trying to be. I would disagree about this stuff seeping in because of automatic lifetime management. The equivalent code in Java, Smalltalk, etc., would either not have the performance problem or it would be much more obvious that the code wasn't as efficient as it could be.

> Sorry for being annoying. I wasn't trying to be.

Thanks, no worries.

You're right that Java doesn't have this performance problem because it (apart from primitives) has no implicit copies. (So the code wouldn't copy, it would return a reference, hence no allocation.)

I think what you're trying to say is: programs written languages with value semantics can implicitly create more objects, which (in some cases) have additional allocation/lifetime tracking in languages with automatic lifetime management. So maybe one solution is to prevent copies in most circumstances, and when you do need to copy, make them explicit.

But I think this conflicts with Go's philosophy as well. First, in highly concurrent programs, copies are necessary. A mutex/atomic will become a bottleneck; sharing is bad for performance. This means that copies should be part of most types in the language. (If they aren't, you'll run into issues like not being able to pickle when using multiprocessing in Python [1].)

OK, so we need copies. But clearly, I shouldn't have to write `a = b.Copy()` if b is an integer. That's too verbose. And if everything has a `.Copy()` call, that leads to visual noise and it's not clear when copies are actually expensive. So what set of "blessed" types should be implicitly copyable? Java picks primitives, but this means it's basically impossible to create an int-like object with value semantics. I think this is bad. C++ is at the other extreme with "(almost) all objects are implicitly copyable" but some people dislike how forgetting a & on a std::vector type might lead to a huge copy.

Go has chosen a reasonable middle ground -- dynamically sized types like arrays and maps are not implicitly copyable, but "cheap" objects like structs are implicitly copyable. This means that when you see a Copy call, it's meaningful. But this means you run into these situations when an implicit copy interacting with lifetimes/garbage collection causes a slowdown. But I don't see any other alternative. Getting rid of implicit copies for cheap types will cause visual noise. Getting rid of GC makes your language harder to use. Both of these alternatives seem worse to me.

To be clear, I'm not a huge fan of Go. Especially around the lack of monadic error handling. But I think they have done a decent job wrt. their design goals.

[1] https://stackoverflow.com/questions/8804830/python-multiproc...

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

#184

Earlier quoted context omitted.

> in practice there's a LOT of different ways the memory management can be handled, many of which are deemed "pauseless GC" (though the term is somewhat misleading). Yes, but I'm pretty sure those "pauseless GC" schemes impose other tradeoffs. > My statement was considering that reality though. While not true for some use cases, in the vast majority of cases, the runtime optimizes the allocations more than sufficient…

In hindsight, I think I chose how to present this poorly, because yes, in this case, the allocation is what is killing the performance. I look at it, and I just see unnecessary implied behaviour creating a performance problem. Usually it isn't the allocations themselves that kill you, but it certainly is the case here.

I agree with you (I think?) that the implicit allocations are a pain point. I think in the Go case, it is the allocations that kill you most of the time (at least that's the case for me), but in C++ it's more likely to be expensive copy constructors or destructors or so on.

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

#185
post #144

Earlier quoted context omitted.

Agreed, but usually tuning for short pause times involves trading off throughput or allocation performance. But at the end of the day, if you aren't allocating a bunch of garbage in the first place, then you don't need to be as concerned about the costs of allocating or cleaning up the garbage. I wish Go did more to make allocations explicit so they could be more easily recognized and avoided; I dislike Java's approa…

What do you consider a ‘long’ pause time? I’ve had no issues with Java 17+ under heavy allocation/garbage collection (data encryption pipeline I haven’t tuned to reuse buffers yet), and it’s pause times are on the order of a handful of milliseconds, without meaningful tuning. I think it’s doing something like a GB/s of garbage collection. And the jvm in question is doing a LOT more than just this, so it’s coping with…

I consider tens of milliseconds to be a long pause time (P99 should be more explicit.

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

#186

Earlier quoted context omitted.

> I will say though that some newer languages seem to have a confused idea about how to offer mixed semantics. A bunch of them tie semantics to types. Curious about what you mean here. This sounds like C#'s class/struct distinction to me.

That's exactly the example I was thinking of.

Yeah, I never cared for that. Specifically, I'd prefer that everything was just "struct", but structs could implement interfaces, which is essentially the Go/Rust model.

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

#187
post #25

Somewhat off topic, but I find a different part of this to be quite ugly: if match || err != nil { return rule, err } Translating this code to actual logic takes too much thought and is too fragile. Is that an error path or a success path? It’s both! The logic is “if we found a rule or if there was an error then return a tuple that hopefully indicates the outcome”. If any further code were to be added in this block,…

[deleted]

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

#188
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…

[deleted]

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

#189

Earlier quoted context omitted.

Java has been tunable for a long time. Periodically, the recommended tuning changes, or new GC algorithms become available, etc. But it has long been possible to get short pause times with various combinations of choosing the right algorithm and writing your program the right way. I think what really throws people off here is that getting good performance out of a Java application involves some skills which are alien…

Agreed, but usually tuning for short pause times involves trading off throughput or allocation performance. But at the end of the day, if you aren't allocating a bunch of garbage in the first place, then you don't need to be as concerned about the costs of allocating or cleaning up the garbage. I wish Go did more to make allocations explicit so they could be more easily recognized and avoided; I dislike Java's approa…

Parallel garbage collection in Java has been a thing for a long time. With the right tuning you might have very infrequent STW GCs, even in a game as allocation-heavy as Minecraft.

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

#190
post #41

Earlier quoted context omitted.

The regular return value doesn’t have to be meaningless just because the error is non-nil. In this case the function returns the rule that triggered the error, which is potentially useful information. I don’t think it is an official Go convention that err being non-nil entails that the other return value should be meaningless.

It's quite error prone, most golang code is if err != nil { return nil, err } Now of course it's important to read the documentation, but a language with sum types (or exceptions) would have used a separate type to indicate an error condition plus useful information on that error.

That only works if a function is returning a pointer though, so I don't think it's right to say "most google code." Any function returning a string or any struct by value that can fail doesn't return nil
Post reply on HN