17 ms·
Should small Rust structs be passed by-copy or by-borrow? (2019)
- birdyrooster 4y agoI guess by-copy bc I’m cool
- dwheeler 4y agoThis is one advantage of Ada, where parameters are abstractly declared as "in" or "in out" or "out". The compiler can then decide how to best implement it for that specific size and architecture.
- bvrmn 4y agoAlways curious how Ada solves ABI issue with such optimizations in place.
- rightbyte 4y agoAs long as the calling convention is deterministic from the declaration of the function it should be fine right?
- usrnm 4y agoIf it's deterministic, the compiler cannot actually choose the best way to optimise it.
- rightbyte 4y agoIt just have to come up with the same best way each time?
- deleted 4y ago[deleted]
- int_19h 4y agoAt the minimum, the compiler can look at the size of the type to decide whether copying or indirection makes more sense. The size is stable for a given parameter signature and architecture, so the ABI is stable. But also, the ABI only matters between two pieces that are separately compiled. A static binary optimized at link-time doesn't have to care.
- sampo 4y ago> This is one advantage of Ada, where parameters are abstractly declared as "in" or "in out" or "out". Also Fortran has "in", "inout" and "out".
- pletnes 4y agoFortran also has «default» / no intent. This is somehow different from inout.
- trifurcate 4y agoAlso, MSVC has similar annotations for various static analyses: https://learn.microsoft.com/en-us/cpp/code-quality/understanding-sal?view=msvc-170 https://learn.microsoft.com/en-us/cpp/code-quality/understan...
- jb1991 4y agoAnd Swift also has "inout" parameters.
- stephencanon 4y agoBut not “out” params, sadly. It can return multiple values, so this doesn’t matter much for value types, but it would be nice to be able to specify that a pointer arg is an out-param sometimes and enforce that it is not read from while handling allocation in the caller.
- wiz21c 4y agoand GL/SL IIRC ...
- FpUser 4y agoSo does Delphi / FreePascal
- Congeec 4y agoC++23 is not too late to the party https://en.cppreference.com/w/cpp/memory/out_ptr_t/out_ptr https://en.cppreference.com/w/cpp/memory/out_ptr_t/out_ptr
- chromatin 4y agoDlang can also qualify parameters as in, out, and inout; although I don't know to what degree the compiler is able to use that for optimization purposes (it is used for safety checks IIRC)
- ardel95 4y agoHow is that semantically different from Rust? in - regular function arguments inout - mut function arguments out - function return Is there any additional information that a compiler can infer from Ada’s parameter syntax?
- layer8 4y agoThe difference between passing by reference vs. by value is observable when comparing pointers to the original vs. to the argument. This difference may be unobservable in Ada though (not sure), so Ada would have more freedom choosing between the two.
- deleted 4y ago[deleted]
- deleted 4y ago[deleted]
- rwaksmunski 4y agoA question to the Rust experts, would lifetime annotations 'a in Rust have similar benefit as "in" or "in out" or "out" in Ada and other languages? With the additional benefit in Rust where the compiler can deduce those automatically for most cases?
- chc 4y agoAs a sibling comment points out, "in" is effectively equivalent to "&T", and "inout" is effectively equivalent to "&mut T". Rust is missing purely "out" parameters, but that isn't a very common case, and I'm not sure how much value there is in saying "this reference can't be read" since references are always guaranteed to be valid in Rust.
- db48x 4y agoA Rust program can trivially return a tuple of values, so it doesn't need out params.
- zozbot234 4y agoOut params are needed to ensure zero-copy in-place initialization of large objects, i.e. what C++ does with placement new. You can sort of fake it with MaybeUninit<> https://doc.rust-lang.org/std/mem/union.MaybeUninit.html https://doc.rust-lang.org/std/mem/union.MaybeUninit.html but it's quite fiddly, requires unsafe code, and has undesirable effects on the layout of containing objects.
- db48x 4y agoIn–place initialization is nice to have, but not strictly required.
- deleted 4y ago[deleted]
- im3w1l 4y agoMy first thought was "now what is the calling convention for float parameters again? they are passed in registers right? the compiler can probably arrange so they don't have to actually be copied" and then I realized it will probably even inline it. Anyway, assuming it's not inlined I would guess pass-by-copy, maybe with an occasional exception in code with heavy register pressure. Edit: Actually since it's a structure, the calling convention is to memory allocate it and pass a pointer, doh. So it should actually compile the same.
- unsafecast 4y ago> Edit: Actually since it's a structure, the calling convention is to memory allocate it and pass a pointer, doh. So it should actually compile the same. Depending on calling convention, the structure may be spread out into registers.
- masklinn 4y ago> Edit: Actually since it's a structure, the calling convention is to memory allocate it and pass a pointer, doh. So it should actually compile the same. FWIW the AMD64 SysV v1.0 psABI allows structures of up to 8 members to be passed via registers. Though older revisions limit that to 2 (and it's unclear whether MS's divergent ABI allows aggregates to be splat at all. As sad as it's unsurprising, it does not look like LLVM (linux?) has followed up, on godbolt a 2-struct passes everything via registers but a 3-struct passes everything via the stack. Maybe there's a magic flag to use the 1.0 ABI, but a quick googling didn't reveal one. ICC doesn't seem to have followed up either.
- im3w1l 4y agoHow can an abi allow but not require passing them via registers? Isn't the point of a convention that everyone does it the same way? Other wise the caller could put it on the stack and the callee look for it in a register? Actually reading the word abi made a little neuron light up. To what extent does rust even follow that abi?
- throwawaybycopy 4y agoShould have also tried pass-by-move .
- m00dy 4y agoIt is a problem of statistics and depends on internals of underlying operating system. I’m not sure you really need that sort of optimisation
- eloff 4y agoWhat does this have to do with the operating system? There are no syscalls in the code measured here.
- m00dy 4y agoBecause rust is a compiled language and therefore it means you compile your code to a certain architecture. Who told you about syscalls ? There are systems not using syscalls
- eloff 4y agoThere are no syscalls or equivalent operating system calls in the code paths measured. The architecture is also independent of the operating system, with exceptions in some languages for the calling convention (not in rust, afaik, or at least rust makes no guarantees there.)
- CryZe 4y agoHowever, in practice Rust's calling convention does actually depend on the operating system. So on Linux Rust will make use of the stack red zone, while on Windows it doesn't. (Also some codegen in LLVM depends on the operating system)
- pclmulqdq 4y agoThe dependency is on the ABI, which can be OS-dependent. Also, it is a depressingly manual optimization to do: compilers don't know when it is safe to change a reference to a copy (for example) without an analysis of future code that they don't do.
- 4y ago
- spuz 4y agoI'd be interested to know what the benchmarks of the two rust solutions are when inlining is disabled so we can get an idea of the different performance characteristics of each function call even if it's not a very realistic scenario. The other question I have is which style should you use when writing a library? It's obviously not possible to benchmark all the software that will call your library but you still want to consider readability, performance as well as other factors such as common convention.
- ptero 4y agoI would go with the version that gives the clean user interface (that is, by copy in this case). If it turns out that the other version is significantly more performant and this additional performance is critical for the end users consider adding the by-borrow option. The clarity of the code using a particular library is such an big (but often under-appreciated) benefit that I would heavily lean in this direction when considering interface options. My 2c.
- daviddever23box 4y agoAgreed - and this applies in nearly every language: start simple, trust your compiler, and optimize only when performance becomes untenable.
- osigurdson 4y agoThe 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.
- throw10920 4y agoIn 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 it works the majority of the time, and it takes experience to know when not to apply it.
- kllrnohj 4y ago> 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.
- celeritascelery 4y agoI don’t feel like this gave a satisfactory answer the question. Since everything was inlined, the argument passing convention made no difference in the micro benchmarks. But what happens when it does not inline? Then you would actually be testing by-borrow be by-copy instead of how good rust is at optimizing.
- furyofantares 4y agoI feel like they got excited by their C++ code being so much slower and curious about the "weird" C++ result and forgot to figure out the original question.
- fnordpiglet 4y agoTo be fair I got excited too. But I still want to know the answer as well.
- FpUser 4y ago>"C++ code being so much slower" Rust - Windows - By-Copy: 14124, By-Borrow: 8150 C++ - Windows MS Compiler - By-Copy: 12160, By-Ref: 11423 C++ - Windows LLVM 15 - By-Copy: 4397, By-Ref: 4396 Delta: -0.0227428% So it appears that C++ - Windows LLVM 15 beats Rust by large margin.
- jasonhansel 4y agoIn Rust it's considered idiomatic to pass things by-value whenever you can. Usually this is also the most performant option, since it avoids dereferencing in the callee. Of course, if your struct is truly enormous, you may want to break this rule to avoid large copies. But in that case you probably want to Box<T> the struct anyway. Of course, if your struct contains something that can't be copied--like a Vec<T>--you'll have to decide whether to clone the whole struct (and thus the vector in it), pass the struct by-borrow, or find some other solution.
- brundolf 4y agoI don't think I'd agree that idioms come into play here, one way or the other. Safely borrowing things by reference is one of Rust's headline features
- Rustwerks 4y agoI just went through all of this when building a raytracer. * Sprinkling & around everything in math expressions does make them ugly. Maybe rust needs an asBorrow or similar? * If you inline everything then the speed is the same. * Link time optimizations are also an easy win. https://github.com/mcallahan/lightray https://github.com/mcallahan/lightray
- masklinn 4y ago> * Sprinkling & around everything in math expressions does make them ugly. Maybe rust needs an asBorrow or similar? Do you mean AsRef, or do you mean magic which automatically borrows parameters and is specifically what rust does not do any more than e.g. C does? Though you can probably get both if the by-ref version is faster (or more convenient internally): wrap the by-ref function with a by-value wrapper which is #[inline]-ed, this way the interface is by value but the actual parameter passing is byref (as the value-consuming wrapper will be inlined and essentially removed).
- woodruffw 4y ago> Maybe rust needs an asBorrow or similar? FWIW, the `Borrow`, `AsRef`, and `Deref` traits all exist to support different variants of this.
- ergonaught 4y agoIt's compiled, so, without any investigation at all, I would have been disappointed if there were any significant difference in the code emitted in these cases. I would expect the compiler to do the efficient thing based on usage rather than the particular syntax. I may have too much faith in the compiler.
- CHY872 4y agoI'd expect your claim to be true whenever the callee is inlined into the caller. In this case, the compiler has all the relevant information at the right point in time. As other commenters have pointed out, by enabling inlining the author has gone down a rabbit hole somewhat unrelated to the question, because any copies can be simply elided. If there's no inlining at play, I'd expect vast differences to be possible. For example, imagine a chain of 3 functions - f calls g, g calls h, where one of the arguments is a 1kB struct and the options are passing by copy or by borrowing. In this case, each stack frame will be 1kB in size in the copy case and there will be a large performance overhead as opposed to the by-reference case. One would expect simply calling the function to be similar in overhead to an uncached memory load. Within a single crate the inlining is possible, with multiple crates it's only possible with LTO enabled (and I'm not sure how _probable_ it is that the inlining would occur). In either case, the difference between a 32 byte and 8 byte argument in terms of overhead is likely meaningless - the sort of thing to be optimized if profiling says it's a problem as opposed to ahead of time.
- kibwen 4y ago> Within a single crate (more specifically, codegen unit) the inlining is possible Cross-crate inlining happens all the time. In order to be eligible for inlining, a function needs to have its IR included in the object's metadata. This happens automatically for every generic function (it's the only way monomorphization can work), and for non-generic functions can be enabled manually via the `#[inline]` attribute (which does not force inlining, it only makes it possible to inline at the backend's discretion). However, as you, say, if you have LTO enabled then "cross-crate" inlining can happen regardless, since it's all just one giant compilation unit at that point.
- 4y ago
- tuetuopay 4y agoThis is not really surprising in such a case. The Rust compiler is pretty good at optimizing out uneeded copies. Here it does see that the copied value is not used after the function call, so it should simply not emit the copies in the final assembly.
- eloff 4y agoFor this code, the compiler inlined the call. So there should be no difference between pass by copy or pass by reference, which is what was measured. Where it could matter is when the code isn’t inlined. But with small structs it might not matter all that much. It does sometimes matter though. One optimization I’ve seen in a few places is to box the error type, so that a result doesn’t copy the (usually empty) error by value on the stack. That actually makes a small performance difference, on the order of about 5-10%.
- Veedrac 4y agoThe general usability impact matters slightly less than it looks here, in part because the `do_math` with references in the article has two extra &s, and in part because methods autoreference when called like x.f(). Performance-wise, if you're likely to touch every element in a type anyway, err on the side of copies. They are going to have to end up in registers eventually anyway, so you might as well let the caller find out the best way to put them there.
- kolbe 4y agoAnyone 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 rust--it's just a matter of how much work you need to put in to make it get there.
- forrestthewoods 4y ago> please post the full source with it. There’s literally a section called Source Code…
- kolbe 4y agoI see now. I looked twice. I think most people stop after a section called "Conclusion" that ends with "Thanks for reading." It doesn't help that the formatting then leaves a large gap between sections that doesn't indicate there's more after that.
- forrestthewoods 4y agoFair!
- kllrnohj 4y agoFor 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.
- kolbe 4y agoWhenever I don't want to endure that annoyance, i just copy this single file header only microbenchmarking code: https://github.com/sheredom/ubench.h https://github.com/sheredom/ubench.h
- redox99 4y agoI'm surprised he tested MSVC and Clang, and not GCC which usually generates faster code than those two.
- 3836293648 4y agoWell, they are the two easily available compilers on Windows. And rustc vs clang should be the fair comparison as they both use llvm
- rnestler 4y agoWhen comparing Rust and C++ it makes sense to use Clang IMO, since both, the Rust compiler and Clang, are based on LLVM. But nevertheless: I agree it would have been interesting to test with GCC as well.
- kibwen 4y agoNote that this is from 2019, so it's probably worth re-benchmarking to see if anything has changed in the interim. Can we get the year added to the title?
- zamalek 4y agoThe benchmarks lack the standard deviation, so the results may well be equivalent. Don't roll your own micro-benchmark runners. References may get optimized to copies where possible and sound (i.e. blittable and const), a common heuristic involves the size of a cache line (64b on most modern ISAs, including x86_64). Using a Vector4 would have pushed the structure size beyond the 64b heuristic. You would also need to disable inlining for the measured methods.
- cogman10 4y agoIt was also (needlessly) using 2 different compilers, MSVC and LLVM. This is just a bad way to compare things all around. And, for simple operations like this, you really should just look at the assembly output. If you are only generating 20ish instructions, then look at those 20 instructions rather than trying to heuristically guess what is happening.
- cmrdporcupine 4y agoThere is no single answer to this question because it's going to depend completely on call patterns further up. Especially in regards to how much of the rest of the running program's data fits in L1 cache, and most especially in regards to what's going on in terms of concurrency. The benchmark made here could completely fall apart once more threads are added. Modern computer architectures are non-uniform in terms of any kind of memory accesses. The same logical operations can have extremely varied costs depending on how the whole program flow goes.
- amelius 4y agoThis 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.
- matheusmoreira 4y agoWell, it is a systems programming language. Thinking about exactly how the language passes bits around is the whole point. Rust should specify a stable ABI already so that everyone can form a good mental model of what their code becomes once compiled.
- amelius 4y agoTrue, I probably was using Rust for the wrong type of problem, i.e. was hoping to write a user-level application with a graphical UI at the time. Rust is probably better used for writing fast low-level libraries that you call from higher level languages, possibly with a garbage collector, so you don't waste time thinking about memory management while you design/write your high-level application.
- throw10920 4y agoMy (brief) experience with Rust was that, while I had to struggle to learn the borrow-checker, I didn't have lots of "mundane details" to worry about - if any, less than C(++). What did you have in mind?
- mlindner 4y agoHaving written a lot of C, you spend basically all your time thinking about "mundane details", and worse, if you make a mistake, you often don't know you made a mistake until it's running on some customer's servers and you just got a ticket escalated 3 times up to you with vague information about crashes rarely happening. Good luck remembering which bit of code you wrote 6 months ago that may be causing the problem. I'll take Rust shouting at me for missing "mundane details" any day of the week.
- scotty79 4y ago
- datafulman 4y agoYou are comparing two completely different compilers; I wouldn't worry all that much about the difference between rust and C++. If you do want to compare them directly, why not use LLVM for C++ as well? That will highlight any language-specific differences.
- arcticbull 4y ago> Blech! Having to explicitly borrow temporary values is super gross. I don’t think you ever have to write code like this. Implement your math traits in terms for both value and reference types like the standard library does. Go down to Trait Implementations for scalar types, for instance i32 [1] impl Add<&i32> for &i32 impl Add<&i32> for i32 impl Add<i32> for &i32 impl Add<i32> for i32 Once you do that your ergonomics should be exactly the same as with built in scalar types. [1] https://doc.rust-lang.org/std/primitive.i32.html https://doc.rust-lang.org/std/primitive.i32.html
- forrestthewoods 4y agoOh neat, that’s my blog. My old posts don’t resurface on HN that often. Lots of criticism of my methodology in the comments here. That’s fine. That post was more of a self nerd snipe that went way deeper than I expected. I hoped that my post would lead to a more definitive answer from some actual experts in the field. Unfortunately that never happened, afaik. Bummer.
- brundolf 4y agoMaybe it'll happen here! :)
- the_mitsuhiko 4y agoMy only criticism is the “ugly mess” part. You can implement the traits on references too.
- forrestthewoods 4y agoTrue, that does work for traits. But it's super annoying if you have to write multiple copies of the same thing. That can get out of control quick if you need to implement every combination. And that doesn't help at all if you're writing a "free function" like 3D primitive intersection functions. I suppose you could change that simple function into a generic function that takes AsDeref? Bleh.
- lukaszwojtow 4y agoI 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.
- carlmr 4y agoExactly, and if performance at some point matters: benchmark! And I would bet 9 times out of 10 it won't be the bottleneck or even make a measurable difference.
- QuadDamaged 4y agoExactly why IMHO the rust stdlib is so easy to understand. Ownership only when required as a design principle tends to make the design of the overall system more consistent / easier to approach.
- theptip 4y agoRust noob here - is it common to see a struct lose Copy as things grow?
- zozbot234 4y agoIf a struct might lose Copy you shouldn't implement Copy at all, to preserve forward compatibility. You can still derive Clone in most cases; using .clone() does not per se add any overhead.
- eterevsky 4y agoIf it's a 3D real-valued vector, or similarly basic structure, you can be fairly certain, that it will stay copyable.
- josephg 4y agoI agree. Being copyable is part of the signature for something like this. Explicitly so in rust.
- 29athrowaway 4y agoA more direct comparison would have been a r-value reference.
- adham01 4y ago[dead]
- FpUser 4y agoI 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
- jeffbee 4y agoHow did you build it? It doesn't build with either gcc-12 or clang-15 on linux.
- FpUser 4y agoI built it on Windows, Visual C++ 2022. Did not check Linux as I do not think it matters much. Now comes big surprise: I 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
- bjackman 4y agoA 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_.
- Dobbs 4y agoedit: I misread the previous post. Ignore this. How are you using the word simpler? Because to me that implies a combination of more obvious and number of lines of code. Something that a benchmark shouldn't be involved in. For example asking someone to delete 10 lines of code and instead use go's ` net.SplitHostPort` would be an example of "simpler".
- 2OEH8eoCRo0 4y agoI've read that good generals worry about tactics and great generals worry about logistics. Good programmers play code golf, great programmers write readable and maintainable code. Your example seems reasonable but programmers also like to act like the smartest one in the room. I often come across tricky and borderline obfuscated code because somebody wanted to look clever. This is a logistical nightmare.
- tested23 4y agoUgh, 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
- aboelez 4y ago[dead]
- lowbloodsugar 4y agoI understand that this is an example for the purposes of answering the given question, but when actually doing things with 3D vertices one should be thinking in terms of structures of arrays. As someone said here already: good generals worry about strategy and great generals worry about logistics.
- BooneJS 4y agoFolks, processors continue to give smaller and smaller gains every year. Something has to give. If you have critical path code that absolutely must max out the core, then this type of analysis (as pedantic as it is) is useful in the long run.
- yobbo 4y agoThe Rust-test implements the traits Add, Sub, Mul by value. This makes the few references less important in the total test. The ergonomics argument is motivated by using these traits. Otherwise, references would have had the same ergonomics. But also, the struct is 3x32 bits, and Rust auto-implements the Copy-trait for it. It is barely larger than u64, which is the size of the reference. But life is only simpler when Copy and Clone can be auto-implemented.
- mcguire 4y agoThis is one of those questions where you really, honestly, do need to look at a very low level. Back in the ancient days, I worked at IBM doing benchmarking for an OS project that was never released. We were using PPC601 Sandalfoots (Sandalfeet?) as dev machines. A perennial fight was devs writing their own memcpy using dst++ = src++ loops rather than the one in the library, which was written by one of my coworkers and consisted of 3 pages of assembly that used at least 18 registers. The simple loop was something like X cycles/byte, while the library version was P + (Q cycles/byte) but the difference was such that the crossover point was about 8 bytes. So, scraping out the simple memcpy implementations from the code was about a weekly thing for me. At this point, we discovered that our C compiler would pass structs by value (This was the early-ish days of ANSI C and was a surprise to some of my older coworkers.) and benchmarked that. And discovered that its copy code was worse than the simple dst++ = src++ loops. By about a factor of 4. (The simple loop would be optimized to work with word-sized ints, while the compiler was generating code that copied each byte individually.) If you are doing something where this matters, something like VTune is very important. So is the ability to convince people who do stupid things to stop doing the stupid things.
- the__alchemist 4y agoInteresting! Of note, My `Vec3` and `Quaternion` types (f32 and f64) have `Copy` APIs, but I've wondered about this since their inception.
- jackmott 4y ago[dead]
- dang 4y agoDiscussed at the time: Should small Rust structs be passed by-copy or by-borrow? - https://news.ycombinator.com/item?id=20798033 https://news.ycombinator.com/item?id=20798033 - Aug 2019 (107 comments)
- ardel95 4y agoMinor nit: many of the differences in the article aren't really specific to the Rust vs C++, but rather differences between llvm vs whatever compiler backend is used by msvc.
- cuteboy19 4y agoSo c++ is less complicated than rust in some cases?
- TEP_Kim_Il_Sung 4y agoI don't know much about this, but by-copy sounds nice. I'd rather own stuff and be happy.
- ta988 4y agoIf you are not limited by memory and cpu sure.
- YesThatTom2 4y agoI covet Ada’s feature where you just specify if a parameter is in, out, or inout; the compiler’s figures out whether to copy or pass a pointer.
- cryptonector 4y agoIt's not like you can do arithmetic with references, so maybe the ergonomics of by-value vs. by-reference shouldn't really be that different. The cost of by-value lies in memory copies, while the cost of by-reference lies in dereferencing pointers where the values are needed, which might mean many more memory reads are needed than with by-value (depends on what you're doing). So it's just hard to tell which will do better in general -- there's no answer to that. For a library, maybe providing by-value and by-reference interfaces should be good (except that will bloat the library). For everything else just use by-value as it has the best ergonomics.
- francasso 4y agoThis actually bothers me. I think the rust performance here is praise worthy. What bothers me is that we piled complexity over complexity at the hardware and compiler levels, and ended up in a situation where you got no way to get a reasonable understanding of how low level code will perform. Nowadays the main reason to program in a "low level" language is that you know that on average the compiler will be able to do a better job because the language doesn't have abstractions that map poorly to the hardware model. But for much of it you can forget about "I know what the hardware is going to do"
- afdbcreid 4y agoI haven't benchmarked that, but in Rust `ScalarPair`s (i.e., structs who have up to to scalars) are passed in two registers, while bigger structs are always passed by pointer. Therefore, passing bigger structs by move will require the compiler to copy them, while with references it is not required to, so references may be faster in that case.
- jgerrish 4y agoI'm late to this discussion, sorry. But at the risk of loss of respect, I'll wait for Rust2ShinyNewLanguage to solve this. All I know is I hope I'm smart enough to understand ShinyNewLanguage's compiler. Or maybe even build it. I've got several projects that could use some additional Boxes of structures, and borrow instead of move, and maybe a few more complex reference counting mechanics. Rust forced me to understand what that meant. That's good for building a better engineer. But it's not fun to work with. I hope the next experience is better. Sorry Rustaceans.