Live data from Hacker News

Should small Rust structs be passed by-copy or by-borrow? (2019)

forrestthewoods.com

141–150 of 238 posts

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#141

A potential lesson here (i.e. I am applying confirmation bias to retroactively view this article as justification for a strongly held opinion, lol): Unless you are gonna benchmark something, for details like this you should pretty much always just trust the damn compiler and write the code in the most maintainable way. This comes up in code review a LOT at my work: - "you can write this simpler with XYZ" - "but that…

I generally agree, but it’s also not obvious to me in Rust (or in Go) whether passing by reference or by copy is more maintainable or clear. I guess what I want is some guidance on what I should do by default, which you sort of give with “do what is more maintainable”, but I can’t tell what that means in practice (I’ve been told to default to pass-by-reference in the past because most traits take &self and not self).

> I’ve been told to default to pass-by-reference in the past because most traits take &self and not self

This is only blanket advice for designing traits, because as the trait author you don't know what concrete type the downstream user is going to want to use, and taking `&self` in that circumstance is the choice that is friendliest to both Copy and non-Copy types.

If you're just writing a non-generic function and you do know what concrete types you're using, the flowchart is pretty simple:

1. If the type is not Copy, then pass by-ref if you just need to read the value, pass by-mutable-ref if you just need to mutate the value, and pass by-value if you want to consume the value.

2. If the type is Copy, then pass by-value, but if your type is really big or if benchmarking has determined that this is a critical code path then pass by-ref.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#142

Earlier quoted context omitted.

If memory budget matters, you have a memory budget. So you should be measuring and you can actually tell where you need improvements. But in practice what we see overwhelmingly is that people want to do this stuff but they aren't measuring, because measuring is boring whereas making the code more complicated to show off how much you think you know about optimisation is easy. Knock it off.

Then knock off trying to use Rust as a systems language. Linear types make refactoring this nearly impossible if it does become and issue.

Works really nice for me, of course, I actually measure what I'm doing.

You edited your comment, so I guess I will too: Rust doesn't actually have Linear types. Linear types ("must use or compile error") would be tricky to provide, Aria blogged about it back in the day. So that's definitely going to be a problem with your refactoring.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#143
post #121

Earlier quoted context omitted.

> But if you have ten thousand cuts you’re probably doomed. And those don’t show up cleanly in a flame graph. I am reminded of the lovely nanosecond/microsecond talk by Grace Hopper. If your code does a little bit of setup and then spends all of its time in a single hotspot, fine. But if your code is full of microsecond-suboptimal speed bumps, you can probably hide your hotspot altogether. And a flat-ish flame graph…

> There's a reason that ancient software running on ancient hardware is way more responsive & snappy than what we have today. Laziness. Laziness in terms of using entirely inappropriate algorithms, sure. Laziness in not microbenchmarking minutia? It shouldn't be. There's a limit on how much that can hurt you. I would say much less than a factor of ten, but let's go with 10x just for argument's sake. If you have a CPU…

When I rewrite python code in C, I often hit 1000x speedups, and sub-100x is rare. And that's line-for-line. When I fix an accidentally-quadratic issue, for example, I've seen speedups in the billions without even changing the language.

People have lionized Knuth's quote about premature optimization, and used that to ignore performance issues across the board. Since the early '00s, we have not seen a 500x improvement in CPU speed. It's less than 2x on frequency, and let's say 8x on core-count for most users (which doesn't help your single-core lazy programmer). In my experience, programmers will make projections based on a 500x-faster processors that will never arrive, because it's easier than honing their skills and keeping them sharp. And even if these magical THz-frequency chips arrive, if you have three layers of 10x slowdowns, you're back down to GHz.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#144
post #94

Earlier quoted context omitted.

One neat thing here is that the compiler is aware of which types are `Copy` and not internally mutable (not contianing an `UnsafeCell`). For these types, passing `&T` and `T` are equivalent, so the compiler could just choose the faster option. Even if it's not smart enough to do that today, it could implement this optimization in the future. This could work even without inlining, since the Rust calling convention is…

It would be more advisable to add this as a clippy hint, because `&T` and `T` are not always equivalent wrt. FFI.

Indeed, but the compiler is still capable of doing it on a case-by-case basis. Quite often the observed semantics are identical and it's easy for the backend to see that a pointer has been created only to be immediately dereferenced.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#145

I always prefer by-borrow. That's because in the future this struct may become non-copy and that means some unnecessary refactoring. My thinking is a bit like "don't take ownership if not needed" - the "not needed" part is the most important thing. Don't require things that are not needed.

If it's a 3D real-valued vector, or similarly basic structure, you can be fairly certain, that it will stay copyable.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#146

A potential lesson here (i.e. I am applying confirmation bias to retroactively view this article as justification for a strongly held opinion, lol): Unless you are gonna benchmark something, for details like this you should pretty much always just trust the damn compiler and write the code in the most maintainable way. This comes up in code review a LOT at my work: - "you can write this simpler with XYZ" - "but that…

> I always ask them to either prove it or write the simple thing.

Even if they do, they also need to make a case that in this specific case, performance matters enough to pessimize code simplicity and maintainability.

Also, if performance is that critical, it's imperative to benchmark again after each compiler release to guard against codegen regressions. And benchmark after changing this piece of code. Otherwise, we can say that performance doesn't really matter.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#147

A potential lesson here (i.e. I am applying confirmation bias to retroactively view this article as justification for a strongly held opinion, lol): Unless you are gonna benchmark something, for details like this you should pretty much always just trust the damn compiler and write the code in the most maintainable way. This comes up in code review a LOT at my work: - "you can write this simpler with XYZ" - "but that…

Even if you do benchmark something, maintainability can be more important than a marginal performance improvement.

I've seen this happen a lot with JavaScript, particularly in the last 5-10 years as JS engines have developed increasingly sophisticated approaches to performance. Today's optimization can be tomorrow's de-optimization. Even given an unchanging landscape of compiler/interpreter, tightly-optimized code can become de-optimized when updated and extended, as compared to maintainable code that may not suffer much performance degradation upon extension.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#148

A potential lesson here (i.e. I am applying confirmation bias to retroactively view this article as justification for a strongly held opinion, lol): Unless you are gonna benchmark something, for details like this you should pretty much always just trust the damn compiler and write the code in the most maintainable way. This comes up in code review a LOT at my work: - "you can write this simpler with XYZ" - "but that…

> I always ask them to either prove it or write the simple thing. Even if they do, they also need to make a case that in this specific case, performance matters enough to pessimize code simplicity and maintainability. Also, if performance is that critical, it's imperative to benchmark again after each compiler release to guard against codegen regressions. And benchmark after changing this piece of code. Otherwise, we…

I think you're missing the point of the previous comment, they are saying a good proxy for it being worth it to optimize, is if you're willing to benchmark it.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#149
post #115

Earlier quoted context omitted.

Ugh, you are right but then someone comes and uses this to rationalize not including things like map, filter and reduce in a language because they are supposedly too complicated and you can just do it with a for loop

I work in a Rust codebase that uses a lot of functional functions, and I’ll say this: on average the imperative style takes less lines of code and less indentation. I also find it more readable personally, and idiomatic.

Just because we’re on the topic of performance: the rust optimizer can sometimes generate better code if you use map / filter / etc. The slice iterator in any context is a huge win over manual array iteration because it only needs to do bounds checking once.

Javascript (v8, last I checked) is the opposite. Simple for loops almost always outperform anything else.

Re: Should small Rust structs be passed by-copy or by-borrow? (2019)

#150

A potential lesson here (i.e. I am applying confirmation bias to retroactively view this article as justification for a strongly held opinion, lol): Unless you are gonna benchmark something, for details like this you should pretty much always just trust the damn compiler and write the code in the most maintainable way. This comes up in code review a LOT at my work: - "you can write this simpler with XYZ" - "but that…

This seems to be an unpopular opinion, but I feel similarly about how sometimes people seem to toss out `inline` (and even more suspect, `inline(always)` annotations on Rust functions like candy on Halloween and there are almost never any sort of actual measurements of whether it actually helps in the cases it's used. It's not even that I think it really hurts that much in most of the stuff I've worked on (which tends to be more sensitive to concurrency design and network round trips), but I can't help but worry that people using stuff like this when they don't seem to fully grasp the implications is a recipe for trouble.
Post reply on HN