Live data from Hacker News

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

forrestthewoods.com

71–80 of 238 posts

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

#71
post #68

Earlier quoted context omitted.

As a Rust beginner that likes to learn the hard way I think I have some insights why Rust seems cumbersome and/or hard for programmers trying it. Rust uses syntax that feels familiar but means completely different things than in pretty much any other language. For example '=' doesn't mean assign handle or copy. It by default means move. 'let' doesn't mean create a name for something. It means create physical space fo…

But Rc is only useful for creating tree-like data-structures. One non-tree cross-link or back-link and you'll have to redesign your entire code.

If you further wrap Rc in an Option you can set crosslinks and backlinks to None when you are dropping your data to get rid of the problem of crosslinks or backlinks making reference-counting leak memory. Then you just need to be mindful to not loose handle to a cycle of your nodes before you break the cycle by setting some crosslinks to None.

You can fairly easily refactor your almost-tree code to adapt it to that additional Option wrap.

Of course you might instead opt to introduce some garbage collector crate into your project. They usually provide garbage collected Rc equivalent, which makes swapping it out very easy.

Rc's are really very useful first approach to making anything complex in Rust.

I usually have something like

    struct NodeStruct {
      my_data: i32,
      link: Node
    }
and

    struct Node(Rc);
or

    struct Node(Option>); 
if I need cross-links.

Great thing is you can then add 'methods' to your type with

    impl Node {}
Or define operators and other traits with:

    impl Add for Node {}
Sometimes, when I need mutability I even wrap the NodeStruct in RefCell.

It seems like a lot of wrappers but thanks to them you can have very nice code that uses this type that has pretty much 'normal modren language' semantics + value semantics and is still blazing fast.

When you implement Ord, Eq, Hash they all go through all the wrappers and let you treat your final type Node as a comparable, sortable, hashable and cheaply clonable value. Dereferencing also goes through all or most of the wrappers automatically.

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

#72

Earlier quoted context omitted.

The assumption behind such arguments is when a performance problem does arise, a profiler will point to a single, easy to fix, smoking gun. Unfortunately this is not always the case. Performance problems can be hard to diagnose and hard to fix. A lot of damage has been done by unexamined / dogmatic "root of all evil" mantra.

In the vast majority of situations (1) you'll prematurely optimize in the wrong place and (2) yes the profiler will point to a single, easy-to-fix smoking gun. Situations otherwise are the exception, rather than the rule, and it takes an expert to (1) recognize those situations and (2) know exactly how to write optimized code in that situation. That's why "don't prematurely optimize" is a good rule of thumb - because…

> In the vast majority of situations [..] yes the profiler will point to a single, easy-to-fix smoking gun.

[citation needed]

This claim depends hugely on the industry you're actually working in and the problem space. Things like UIs & games basically never have a single, easy-to-fix smoking gun. The entire app is more or less a hotspot - be it interactive performance, startup performance, RAM usage, or general responsiveness.

And once you're gone down the route of "build it first, optimize it later" you're pretty much fucked when you get to the "optimize" step because now your performance mistakes are basically unfixable without a rewrite - every layer of your architecture has issues that you can't fix without drastic overhauls. It would have been much easier to do some up-front measurements, get some guidelines in place (even if they aren't perfect), and then build the app.

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

#73
post #56

Earlier quoted context omitted.

As long as the calling convention is deterministic from the declaration of the function it should be fine right?

If it's deterministic, the compiler cannot actually choose the best way to optimise it.

It just have to come up with the same best way each time?

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

#74

Earlier quoted context omitted.

Agreed - and this applies in nearly every language: start simple, trust your compiler, and optimize only when performance becomes untenable.

The assumption behind such arguments is when a performance problem does arise, a profiler will point to a single, easy to fix, smoking gun. Unfortunately this is not always the case. Performance problems can be hard to diagnose and hard to fix. A lot of damage has been done by unexamined / dogmatic "root of all evil" mantra.

The misapplication of that mantra doesn’t justify the design damage done by dogmatically passing everything by ref.

There’s no hard and fast rule here. Even if there was, optimizers still occasionally surprise seasoned native devs in both positive and negative ways.

Glad the author’s first instinct was to pull out profiling tools.

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

#75
post #28

Anyone know why seemingly knowledgeable people (like the person who wrote this article) don't use micro benchmarking frameworks when they run these tests? Also, whenever you do one of these, please post the full source with it. There's no reason to leave your readers in the dark, wondering what could be going on, which is exactly what I'm doing now, because there's almost no excuse for c++ to be slower in a task than…

For C++ I guess you could make the claim that it's just too annoying to take a dependency on something like google-benchmark or whatever, since C++ dependency management is such a mess to deal with in general.

But yeah I have no idea why a benchmark framework wasn't used for Rust.

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

#76
post #28

Anyone know why seemingly knowledgeable people (like the person who wrote this article) don't use micro benchmarking frameworks when they run these tests? Also, whenever you do one of these, please post the full source with it. There's no reason to leave your readers in the dark, wondering what could be going on, which is exactly what I'm doing now, because there's almost no excuse for c++ to be slower in a task than…

For C++ I guess you could make the claim that it's just too annoying to take a dependency on something like google-benchmark or whatever, since C++ dependency management is such a mess to deal with in general. But yeah I have no idea why a benchmark framework wasn't used for Rust.

Whenever I don't want to endure that annoyance, i just copy this single file header only microbenchmarking code:

https://github.com/sheredom/ubench.h

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

#77
post #40

This is one of the problems I have with writing rust code. You have to think about so many mundane details that you barely have time left to think about more important and more interesting things.

As a Rust beginner that likes to learn the hard way I think I have some insights why Rust seems cumbersome and/or hard for programmers trying it. Rust uses syntax that feels familiar but means completely different things than in pretty much any other language. For example '=' doesn't mean assign handle or copy. It by default means move. 'let' doesn't mean create a name for something. It means create physical space fo…

> For example '=' doesn't mean assign handle or copy. It by default means move.

Some complain about this, but the fact is there's no such thing as a zero-overhead "copy" for non-trivial types. C++ started out with = meaning clone the object which was an even bigger footgun, and support for move had to be added after the fact.

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

#78
I did the test on my computer:

Rust - By-Copy: 14124, By-Borrow: 8150

C++ - By-Copy: 12160, By-Ref: 11423

P.S. Just built it using LLVM under CLion IDE and the results are:

  G:\temp\cpp\rust-cpp-bench\cpp\cmake\cmake-build- 
   release\fts_cmake_cpp_bench.exe
   Totals:
     Overlaps: 220384338
     By-Copy: 4397
     By-Ref: 4396  Delta: -0.0227428%

  Process finished with exit code 0

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

#79
post #40

This is one of the problems I have with writing rust code. You have to think about so many mundane details that you barely have time left to think about more important and more interesting things.

As a Rust beginner that likes to learn the hard way I think I have some insights why Rust seems cumbersome and/or hard for programmers trying it. Rust uses syntax that feels familiar but means completely different things than in pretty much any other language. For example '=' doesn't mean assign handle or copy. It by default means move. 'let' doesn't mean create a name for something. It means create physical space fo…

Rc, Cell & RefCell are suppose to be rare. For example I've got 2,000 line Rust program in front of me and I've used Arc 3 times and RWLock 1 time, that's all.

You need to structure your program as a Directed Acyclic Graph (DAG), with things interacting only with the things below them in the graph.

Then occasionally you might need to break the DAG structure by using Rc, Cell & RefCell, etc...

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

#80
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 will be slower because it's a copy/a function call/an indirect branch/a channel send/a shared memory access/some other combination of assumptions about what the compiler will generate and what is slow on a CPU"

I always ask them to either prove it or write the simple thing. If the code in question isn't hot enough to bother benchmarking it, the performance benefits probably aren't worth it _even if they exist_.

Post reply on HN