[HN Gopher] Towards fearless SIMD, 7 years later
       ___________________________________________________________________
        
       Towards fearless SIMD, 7 years later
        
       Author : raphlinus
       Score  : 173 points
       Date   : 2025-03-29 23:52 UTC (23 hours ago)
        
 (HTM) web link (linebender.org)
 (TXT) w3m dump (linebender.org)
        
       | the__alchemist wrote:
       | Very interesting! I posted a vector and quaternion lib here a few
       | weeks ago, and got great feedback on the state of SIMD on these
       | things. I since have went on a deep dive, and implemented wrapper
       | types in a similar way to this library. Used macros to keep
       | repetition down. Split into three main sections:
       | - Floating point primitives. Like this lib. Basically, copied
       | `core::simd`'s API. Will delete this part once core::simd is
       | stable. `f32x8`, `f64x4` types etc, with standard operator
       | overloads, and utility methods like `splat`, `to_array` etc.
       | - Vec and Quaternion analogs. Same idea, similar API. Vec3x8,
       | Quaternionx8 etc.            - Code to convert slices of floating
       | point values, or non-SIMD vectors and quaternions to SIMD ones,
       | including (partial) handling of accessing valid lanes in the last
       | chunk.
       | 
       | I've incorporated these `x8` types into a WIP molecular dynamics
       | application; relatively painless after setting up the infra.
       | Would love to try `Vec3x16` etc, but 512-bit types aren't stable
       | yet. But from Github activity on Rust, it sounds like this is
       | right around the corner!
       | 
       | Of note, as others pointed out in the thread here I mentioned,
       | the other vector etc libs are using the AoS approach, where a
       | single f32x4 value etc is used to represent a Vec3 etc. While
       | with this SoA approach, a `Vec3x8` is for performing operations
       | on 8 Vec3s at once.
       | 
       | The article had interesting and surprising points on AVX-512
       | (Needed for f32x16, Vec3x16 etc). Not sure of the implications of
       | exposing this in a public library is, i.e. might be a trap if the
       | user isn't on one of the AMD Zen CPUs mentioned.
       | 
       | From a few examples, I seem to get 2-4x speedup from using the x8
       | intrinsics, over scalar (non-SIMD) operations.
        
         | camel-cdr wrote:
         | Why do the x4/x8 types seem to be the default in rust?
         | 
         | A portable SIMD feature should encurage portable SIMD and not a
         | specific vector register size.
        
           | the__alchemist wrote:
           | This sounds like a great idea. I went with this approach
           | because I'm new to SIMD, so I aped the most promising API
           | (core::simd), extending it naturally.
           | 
           | I need to think through the consequences. It might involve
           | feature gates, and/or an enum. So, for example, instead of:
           | pub struct f32x8(__m256);
           | 
           | It might be this internally, with some method to auto-choose
           | variant based on system capability?:                 pub enum
           | f32_simd {         X8(__m256),         X16(__m512),        }
           | 
           | etc. Thoughts?
        
       | dzaima wrote:
       | Seems rustc nightly does successfully vectorize the first sigmoid
       | example: https://rust.godbolt.org/z/e1WYexqWY
       | 
       | Also there's progress on making safe intrinsics safe:
       | https://github.com/rust-lang/stdarch/pull/1714
        
       | ashvardanian wrote:
       | I've said it before and I'll say it again: Rust feels like a
       | Python developer's idea of a high-performance computing language.
       | It's a great language for many kinds of applications -- just not
       | when you need to squeeze out every bit of performance from
       | advanced hardware.
       | 
       | Even before getting into SIMD, try using Rust for concurrent,
       | succinct, or external-memory data structures. It quickly becomes
       | clear where the friction is.
       | 
       | Cargo is fantastic -- clean, ergonomic, and a joy compared to
       | many toolchains. But it's much easier to keep things simple when
       | you don't have to support dozens of AVX-512 variants, AMX, SME,
       | different CUDA generations, ROCm, or any of the other modern
       | hardware capabilities.
       | 
       | Standardising SIMD in the standard library -- in Rust or C++ --
       | has always been a questionable idea. Most of these APIs cater to
       | operations that compilers already auto-vectorize reasonably well,
       | and they barely touch the recent capabilities of SIMD. Just
       | consider how hard it is to build any meaningful abstraction over
       | the predicate/register models across AVX-512, SVE, and RVV.
       | 
       | RVV aside, this should illustrate the point:
       | https://www.modular.com/blog/understanding-simd-infinite-com...
        
         | the__alchemist wrote:
         | I guess it comes down to application. If you don't attempt to
         | find the most general solution, you can dodge those pitfalls.
         | Case in point, abstracting over AVX-512, SVE, and RVV may be
         | tough, but picking one is fine (On nightly only for now), can
         | with the right abstractions can be almost as straightforward as
         | using normal scalar values. I don't have a solution on the CUDA
         | variants either; have been hard-coding that as well... (Cudarc
         | lib with CUDA-version feature gates and GPU-series-specific
         | code). Haven't hit a brick wall yet, but might... or might not.
        
         | lifthrasiir wrote:
         | > Just consider how hard it is to build any meaningful
         | abstraction over the predicate/register models across AVX-512,
         | SVE, and RVV.
         | 
         | Note that Highway mentioned in the post does take care of this,
         | which is no easy feat but also a proof that it _is_ doable.
        
         | dzaima wrote:
         | I don't think Rust is particularly problematic here. As long as
         | you don't want to do funky things like use immutable argument
         | memory as temporary scratch space (with you restoring the
         | values afterwards of course), all it means is some `unsafe`ing
         | at worst, compared to C/C++. And there are some safe
         | abstractions you can make over loads/stores (everything else
         | being safe, even if not yet marked as such).
         | 
         | Do agree that a standard SIMD type is rather pointless, if not
         | immediately, then in like 5 years. (and, seemingly, both Rust
         | and C++ are like over 10 years behind on SIMD, so they're
         | already out-of-date)
         | 
         | Maybe somewhat useful if you just want the simple ~8x speedup,
         | and not squeeze out the last 1.4x or whatever, but
         | autovectorization should be capable of covering a significant
         | amount of such.
        
           | ashvardanian wrote:
           | ... except for byte-level processing, variable-length codecs,
           | or mixed-precision numerics. That never works with
           | autovectorization and can't be solved with general-purpose
           | SIMD wrappers. For me the solution was to implement those
           | manually, and even at a scale of just 2 libraries I've eneded
           | up with somewhat different project layouts & dispatch
           | mechanisms: https://github.com/ashvardanian/SimSIMD ,
           | https://github.com/ashvardanian/StringZilla
           | 
           | One big family not covered there, is sparse data-strictures
           | and related algorithms. I've only started integrating
           | scatter/gather in AVX-512 and SVE, and on synthetic
           | benchmarks both look promising: https://github.com/ashvardani
           | an/less_slow.cpp/releases/tag/v...
           | 
           | Those should probably unlock a much wider set of applications
           | for SIMD, but designing libraries for those may benefit from
           | yet another project structure.
        
             | dzaima wrote:
             | Ah yeah, gather/scatter are indeed a rather problematic
             | thing for autovectorization. That said, with no-alias info
             | (which Rust has a lot of) it's possible:
             | https://rust.godbolt.org/z/zTfo9nxhd.
             | 
             | Unfortunately it doesn't get autovectorized without the
             | unsafes, but theoretically it should be possible-ish for
             | bounds checking to be autovectorized (most problematic
             | aspect being that it might be hard to annoying-to-
             | impossible to ensure that in the case of multiple panic/UB
             | sources the proper one happens first).
             | 
             | I'd imagine in any non-trivial situation you'd want a
             | custom layer of abstractions over whatever the language
             | provides for all languages. For that a portable-simd thing
             | is actually a rather good base, on which you could add
             | custom arch-specific abstractions/ops as desired.
             | 
             | Not sure what's problematic with mixed-precision (I know
             | SVE is rather weird for mixed-width elements, but that's
             | about it?), though I primarily don't care about float stuff
             | generally. Also no clue what's problematic with byte-level
             | stuff.
             | 
             | Indeed there are still a bunch of things that you want
             | proper manual SIMD for (hell the SIMDful project I work on
             | has an entire DSL for doing nice SIMD), but
             | autovectorization still covers a good amount.
        
             | janwas wrote:
             | > except for byte-level processing, variable-length codecs,
             | or mixed-precision numerics. That never works with
             | autovectorization and can't be solved with general-purpose
             | SIMD wrappers.
             | 
             | Counterexamples: Chromium's byte-level HTML scanning,
             | several var-len bit packing codecs, and Gemma.cpp's matmul
             | is mixed-precision (fp8->bf16->fp32->fp64). All written
             | with the Highway general-purpose SIMD wrapper. Please
             | revise your post or expand upon the structure/dispatch
             | concern.
        
               | ashvardanian wrote:
               | Interesting references! I remember that Gemma.cpp used
               | Highway, but I haven't checked the others much.
               | 
               | Here is a puzzle, then. Let's say we are checking a
               | single register of bytes for element-wise equality with
               | another register of the same size. In AVX2, the output is
               | another YMM register of 0xFF or 0x00 values. In AVX-512,
               | for full ZMM-wide comparisons, it's a 64-bit mask in the
               | K register.
               | 
               | I struggle to see a good way to abstract such things,
               | even for two consecutive SIMD generations on x86.
        
               | dzaima wrote:
               | A simple enough abstraction is to have the mask type have
               | inputs of both element size and count; so for a <=256-bit
               | product of those you do the homogeneous-bit elements, and
               | for 512-bit you do the packed-bit values. Conversion
               | methods on the mask can convert those to either an
               | explicit full vector, or a packed bit integer, as you
               | need. (and clang can optimize out unnecessary conversions
               | between the two)
        
               | ashvardanian wrote:
               | Sure, that would make sense, but in AVX-512, there is
               | also a comparison variant for 2x256-bit inputs that
               | outputs a 32-bit mask and another one for 2x128 inputs
               | and a 16-bit output mask. I would use those in different
               | ways, occasionally mixing with the old AVX2 variant,
               | depending on what I'm doing. I have tried to create a
               | generalizable SIMD framework several times in the past,
               | mostly in 2015-17, and still don't have a good
               | abstraction even between AVX2 and AVX-512.
        
               | dzaima wrote:
               | You'd just produce the <=AVX2-style result; clang will
               | switch to using the mask-returning comparison instrs if
               | the usage ends up being a mask (gcc doesn't do such fancy
               | things, so I guess getting the last bits of speedup from
               | this strategy depends on how much control over compiler
               | selection you choose to have).
               | 
               | And if you really care about doing different things for
               | <=AVX2 vs AVX-512 masks, you're definitely in the
               | "squeeze out the last 1.4x" camp and not the "simple ~8x
               | speedup" one. And, realistically, you won't care about
               | this on every single comparison, so where you really want
               | to explicitly use mask-returning comparisons you could
               | just switch to using intrinsics directly (or a different
               | abstraction over them) temporarily.
               | 
               | As a side-note, mask-returning comparisons have 2x less
               | throughput than homogeneous-bit-returning ones for
               | xmm/ymm as far as uops.info data goes[1] (and use port
               | 5!), so this strategy is kinda just what you want really.
               | 
               | (another strategy can be to just return the arch-specific
               | result type, still preferring the <=AVX2 style (with, if
               | desired, a separate set of comparison ops for the
               | AVX-512-mask output), and making mask-consuming ops
               | polymorphic over the two)
               | 
               | [1]: https://uops.info/table.html?search=cmpeqb%20ymm%29&
               | cb_lat=o...
        
               | janwas wrote:
               | Yes indeed, this is what we do :) There is an opaque Mask
               | type for which operations such as CountTrue, AllFalse etc
               | are provided. If you really want the one or other
               | representation, VecFromMask and
               | BitsFromMask/StoreMaskBits convert as required. The
               | former is a no-op on AVX2.
               | 
               | As to the AVX2 comparison, for ICL I think we can do two
               | of those per cycle, so no more than the throughput of an
               | AVX-512 mask comparison. The question is what we do with
               | it afterwards - for VQSort (one of the few applications
               | where comparisons are really the bottleneck?) we are much
               | happier to have the packed bits, because that can feed
               | into vpcompress or a LUT implementing that.
        
             | burntsushi wrote:
             | You started with this... take:
             | 
             | > Rust feels like a Python developer's idea of a high-
             | performance computing language. It's a great language for
             | many kinds of applications -- just not when you need to
             | squeeze out every bit of performance from advanced
             | hardware.
             | 
             | And went on to say that Rust in particular is problematic
             | for:
             | 
             | > byte-level processing
             | 
             | It's particularly odd for you to say this given that the
             | memchr Rust crate is just as fast as stringzilla for
             | substring search. And is generally faster in cases where
             | the needle is invariant, because stringzilla doesn't have
             | APIs for amortizing searcher construction.
             | 
             | We've had a discussion about this before where I provided
             | receipts[1] and we have not had a meeting of the minds on
             | this point. The thing I'm trying to achieve here is to
             | point out that your claims are contested and there is
             | evidence that you're wrong. And so I'd caution readers to
             | also in turn question your higher level claims about Rust
             | being a "Python developer's idea of a high-performance
             | computing language."
             | 
             | [1]: https://old.reddit.com/r/rust/comments/1ayngf6/memchr_
             | vs_str...
        
               | ashvardanian wrote:
               | Hey! I just mean that there is a very large category of
               | developers, generally coming from the Python world,
               | expecting that switching to Rust is supposed to solve
               | every performance-oriented problem, providing a State-of-
               | the-Art solution magically :)
               | 
               | Sadly, programming doesn't seem to work that way. There
               | are always tradeoffs. Rust does some things very well,
               | but it wouldn't be my first choice for others.
               | 
               | MemChr is a lovely package, and there are a few other
               | really cool SIMD projects in the Rust ecosystem.
               | Regardless, my development velocity for HPC-related
               | projects is higher in C/C++, and I get much more
               | flexibility to leverage newer hardware features, like AMX
               | and SME.
        
               | almostgotcaught wrote:
               | Don't worry his comment is obvious clickbait (complete
               | with a plug for a random blogpost written by him). People
               | that matter (people that are actually writing simd for
               | their day job) can immediately spot a poseur (especially
               | relative to you and Raph).
        
               | burntsushi wrote:
               | To be clear, I don't support this. StringZilla is a real
               | and useful project, and its performance is competitive
               | with memchr. They aren't a poser.
               | 
               | (There are reasons to use StringZilla over memchr beyond
               | performance. StringZilla provides a number of interesting
               | string operations beyond just substring search. The
               | memchr crate is far more specialized.)
        
               | ashvardanian wrote:
               | Thanks, Andrew! Love your work too - keeps me on my toes
               | ;) PS: Hoping to share a few more Rusty bits later this
               | year.
        
           | scottlamb wrote:
           | > Do agree that a standard SIMD type is rather pointless, if
           | not immediately, then in like 5 years. (and, seemingly, both
           | Rust and C++ are like over 10 years behind on SIMD, so
           | they're already out-of-date)
           | 
           | What language would you consider to have cutting-edge SIMD
           | support?
           | 
           | I've dipped my toes into SIMD with Rust, [1] on stable with
           | platform-specific intrinsics (SSE2, AVX2, NEON). I would have
           | liked to use stable `std::simd`. I learned that (particularly
           | on AVX2) getting things into the right lanes efficiently is a
           | pain. I would have liked to just use `simd_swizzle!` for that
           | part, and mix that with intrinsics calls. My approach of
           | writing a small C++ or unstable Rust program that does the
           | swizzling and then copying the intrinsics operations it chose
           | into my program's "source" code worked, but I prefer to not
           | have a manual copy'n'paste step between compilation and
           | assembly.
           | 
           | If there's something much better out there in another
           | language, well, I'd be very interested to see it.
           | 
           | [1] I wrote this: https://github.com/infiniteathlete/pixelfmt
           | /blob/main/docs/s...
        
         | creata wrote:
         | > Rust feels like a Python developer's idea of a high-
         | performance computing language.
         | 
         | I might be wrong, but I think it sounds more like Rust doesn't
         | move as far away from the C or C++ way of doing things as you
         | want it to. At the very least, Rust is no worse than C or C++
         | at any of the things you mentioned.
        
           | ashvardanian wrote:
           | In my (biased) experience, Rust is much harder to use for
           | advanced projects, than C and C++. On the bright side, it's
           | also harder to misuse :)
        
             | saagarjha wrote:
             | I don't think Rust is worse at SIMD code, though.
        
             | vlovich123 wrote:
             | I've found it much easier to use from writing a web service
             | to writing a high performance DB that outperforms RocksDB
             | and clearly people are using it for things like writing
             | operating systems as well as game engines. I'm not sure
             | what in your mind falls under "advanced projects" but I
             | suspect it's something like number crunching (although you
             | link StringZilla so not sure).
             | 
             | I'm still not seeing any description of specific challenges
             | you feel are harder in Rust than in C/C++. In my mind Rust
             | is completely equivalent in being able to accomplish the
             | same tasks.
        
               | porridgeraisin wrote:
               | They wrote 2 whole paragraphs about SIMD.
        
               | vlovich123 wrote:
               | And to me SIMD does not seem significantly harder in Rust
               | than in C++ but I've only done a little Rust SIMD so I'm
               | willing to be wrong on that (although others have said
               | it's not much harder than in C++ so not sure).
        
               | jpc0 wrote:
               | > easier to use from writing a web service to writing a
               | high performance DB
               | 
               | Have you looked at what the actual implementation code of
               | tokio / axum etc looks like?
               | 
               | I can't comment on DB because that is quite far out of my
               | wheelhouse but regarding game engines, I've almost
               | universally seen people revert to storing objects in
               | arrays and passing around "handles" which is literally
               | pointers with extra steps... But I guess at least you are
               | protected from some issues there.
               | 
               | Rust forces absurd levels of abstraction onto code very
               | early and if you are wrong you need to make sweeping
               | refractors.
               | 
               | I really want to love the language, but building bottom
               | up is extremely difficult with it, it may be Stockholm
               | syndrome and familiarity but I can generally get much
               | further into a project much quicker in C++.
               | 
               | In Rust it feels like I'm fighting the language and it's
               | not the borrow checker.
               | 
               | There are so many great ideas in Rust and I keep trying
               | it because of them, don't get me wrong I do think a safer
               | language is the future but Rust isn't there yet. Every
               | time I try it it's better though.
        
               | vlovich123 wrote:
               | I have actually looked at the interior of Tokio. While
               | there's a lot of complexity there, most of that
               | complexity is in making a highly performing multi-
               | threaded work-stealing runtime rather than anything Rust-
               | specific. Indeed, I find it easier to reason about than
               | in C++ because the ownership rules are enforced and where
               | they're violated is clearly annotated with unsafe & lots
               | of documentation explaining why it's safe. I suspect
               | you'll see similar things in axum but I can't speak to
               | that codebase specifically.
               | 
               | I don't think you'd be able to do something meaningfully
               | easier in C+. I think it is Stockholm syndrome. It took
               | me about 2 months to get familiar that it wasn't
               | uncomfortable anymore and 8 months to reach the "I can
               | generate code faster than I ever could in C++".
               | 
               | For context consider that I'd spent the prior ~11 years
               | coding on and off in C++ professionally and the prior 10
               | years before that as a kid learning C++ so C++ was
               | definitely a language I felt at home in. I spend almost
               | no time compared to C++ trying to make the build system
               | work and I can quickly pull in high quality components
               | others have written vs in C++ where I either have to
               | implement it myself, spend time on build integration, or
               | figure out a way to do without.
        
               | jpc0 wrote:
               | Oh I completely agree Tokio is a complex piece of
               | software, but my point was you need to implement a decent
               | amount of that even for trivial cases where Tokio isn't
               | needed and therefore the default response is to just pull
               | in Tokio.
               | 
               | Part of the complexity for me at least in async Rust was
               | that it is some weird mix between coroutines and
               | completion handlers. It's a pretty nice abstraction with
               | once you wrap your head around it but how many Rust devs
               | would be able to do that?
               | 
               | Should I find a performance regression and need to dive
               | in to the code it takes a lot of unknown unknown to be
               | explored because what is actually happening is hidden
               | from you by the abstraction and the Async book's response
               | is "don't worry about that just use tokio". And the
               | "advanced" section is incomplete.
               | 
               | I've almost always found the complexity of these systems
               | to be reasonable when I've dug through it, but I had to
               | manually mess with Rust code to understand the async
               | model, the book failed to explain what it is and when it
               | clicked it made a ton of sense.
               | 
               | Maybe my issue is that Rust just forces abstractions down
               | your throat and says "trust me you need it" but I would
               | much rather discover why and even find a more relevant
               | abstraction.
               | 
               | The last example I made still stands, there is so much
               | complexity caused by abstractions that the wgpu "book"
               | recommends an older version of winit because the current
               | version made API changes that requires a lot of code
               | change. Winit 0.30 released almost a year ago, I
               | understand that it is likely a volunteer maintainer there
               | but if the language allowed for easy refactoring why has
               | it been a year?
               | 
               | The fact that the webgpu standard is still in active
               | development likely adds to that.
        
               | the__alchemist wrote:
               | Commenting on two loosely-coupled things here!
               | 
               | The WGPU/Winit compatibility situation is indeed a mess.
               | (Throw EGUI in there too if you're into that...) I was
               | able to get all of them onto the latest, but it was an
               | adventure of troubleshooting, and asking for help on
               | Discord, Github etc. I think this could have been avoided
               | by adding current and/or practical examples to the WGPU
               | repo and/or docs. So, documentation problem maybe? Most
               | of the examples are too provincial or trivial to use as a
               | basis for building a practical project. (Or upgrading
               | one!)
               | 
               | Count me as someone who can't grok Async. I've tried
               | several times. It doesn't stick to my brain, and/or I'm
               | not sure how to keep it from propagating through the
               | code. I've come to the realization that I will probably
               | never get it, and/or see eye-to-eye with the rust
               | community on this. Most of the web ecosystem will remain
               | off-limits.
        
               | vlovich123 wrote:
               | Async rust is purely a state machine equivalent to c++
               | coroutines. There's not really any more magic than that
               | within the language itself. In other words, async rust is
               | syntactic sugar and nothing else.
               | 
               | Tokio is a work stealing runtime that then layers other
               | requirements on it (eg futures must be send to support
               | work stealing). This is similar to Apple's libdispatch.
               | 
               | I'm not sure what performance problems or other magic
               | you're referring to but it's hard to answer non-specific
               | gripes with information. It sounds like you're saying it
               | should be easier to roll your own runtime and there's
               | slow progress on standardizing some of the pieces. But
               | there's a lot of runtimes you can pick from (async-std,
               | snol, glommio, monoio, etc). It's fine that implementing
               | a runtime is complex because a lot of that complexity is
               | inherent (Rust adds some extra boilerplate but that's all
               | it really is)
               | 
               | I haven't really found Rust's async to be all that hard
               | to grok. It's got some annoying boilerplate in some
               | places where there's impedance mismatches in very rare
               | hyper specific cases but those don't normally come up.
        
               | jpc0 wrote:
               | From what I played around with Rust futures are not
               | entirely coroutines but a mixture between coroutines and
               | sender / receiver model. I'm pretty sure C++ will end up
               | somewhere similar in the end or at least most libraries
               | will model it that way.
               | 
               | Effectively the language has built around the idea of a
               | runtime being needed at all whereas C++ coroutines ( and
               | coroutines in general ) don't need a runtime. They are
               | just state machines with syntactic sugar.
               | 
               | In Rust something that get's awaited is expected to
               | inform the runtime of when it should be continued. You
               | cannot just have a bunch of coroutines that call each
               | other, you must build a basic runtime even if it is just
               | doing normal coroutines.
               | 
               | Once that clicked to me it made a lot more sense but it
               | was pretty annoying seeing syntax for coroutines but not
               | being able to just use them as coroutines.
               | 
               | And I have no specific performance gripes, it's more that
               | instead of just needing to understand he CS fundamentals,
               | I also need to understand how the language decided to
               | implement those CS fundamental and abstract them away.
               | 
               | I think possibly being able to grok Rust async model
               | being easy for you is slightly clouding the complexity of
               | it. If you know the complexity of what is needed to do
               | mutltithreaded async well then it's not hard to wrap your
               | head around why it was done that way, and as I said I
               | found the implementation beautiful when I understood it,
               | but there was no documentation that mentions it and the
               | only documentation is surface level.
               | 
               | As one of the other comments here said, it's like python
               | devs wanted to write a low level high performance
               | language. All the knobs are there but they decided to
               | hide them away behind abstractions that theoretically
               | make the happy path easy, but slightly off the happy path
               | you get thrown into the deep end. In C / C++ world there
               | isn't any kiddie pool, all the uglyness is on display.
        
               | vlovich123 wrote:
               | > All the knobs are there but they decided to hide them
               | away behind abstractions that theoretically make the
               | happy path easy, but slightly off the happy path you get
               | thrown into the deep end. In C / C++ world there isn't
               | any kiddie pool, all the uglyness is on display.
               | 
               | That's a perfect characterization - you can still get to
               | the ugliness when you need it (& I don't think it's such
               | a sheer cliff but YMMV). The "python devs wanted to write
               | a low level high perf language" was derogatory suggesting
               | that those authors didn't know what they were doing and
               | crippled the language. It's quite the opposite I think
               | though - they knew exactly what they were doing & made it
               | super easy for developers to write correct code with a
               | simpler mental model of the code.
               | 
               | As for "async as coroutines", you're right that it's a
               | bit more complicated than C++ coroutines, but that's why
               | there's an explicit coroutine mechanic headed your way:
               | https://doc.rust-lang.org/std/ops/trait.Coroutine.html
        
               | jpc0 wrote:
               | > The "python devs wanted to write a low level high perf
               | language" was derogatory suggesting that those authors
               | didn't know what they were doing
               | 
               | I didn't mean it as derogatory at all actually, more a
               | matter of it's being pushed to developers that have never
               | used a systems level language and had to deal with the
               | types of algorithms at the systems level, and actually
               | most of them can even do it because the abstractions are
               | great. But those same developers can't see how those
               | abstractions could be implemented and wouldn't know where
               | to look.
               | 
               | Hey at least this chain of comments has helped me
               | understand my dislike of where Rust is positioned and as
               | I said in another comment the language authors are
               | makeing the necessary changes to appease people like me,
               | I'm happy to hear about the corouting mechanic because it
               | adds another checkbox that Rust is at the very least
               | trying to position itself correctly.
               | 
               | You are the first rust "evangelist" that I've had he
               | privilege to chat with that actually didn't make me want
               | to drop the language and go back to C++.
               | 
               | You pointed out where rust is strong without just
               | shouting "you are holding it wrong". Thanks for the great
               | experience.
        
               | pcwalton wrote:
               | > I can't comment on DB because that is quite far out of
               | my wheelhouse but regarding game engines, I've almost
               | universally seen people revert to storing objects in
               | arrays and passing around "handles" which is literally
               | pointers with extra steps... But I guess at least you are
               | protected from some issues there.
               | 
               | Regarding game engines, the empirical evidence shows that
               | Bevy has been moving at least as fast as, if not faster
               | than+, comparable C++ engines. I listed all the features
               | I landed in the past year here:
               | https://news.ycombinator.com/item?id=42945730
               | 
               | We love to argue on message boards about theoretical or
               | anecdotal productivity of programming languages, but at
               | the end of the day the only thing that matters is what
               | people have _actually_ been doing with the language, and
               | for Rust the answer is  "quite a lot, actually".
               | 
               | +In my view, Bevy has been moving significantly faster
               | than comparable C++ engines, but I don't need to argue
               | that to make my point.
        
               | jpc0 wrote:
               | Don't take it as an attack on game engines in Rust. It
               | really isn't, it's more an observation that people
               | happily work around the fundamental language design and
               | that should be an indicator to the language authors.
               | 
               | And they are definitely listening though, when I last
               | took a dive into Rust I faught a lot of weird borrow
               | checker edge cases that I could easily verify as correct.
               | The borrow checker now no longer complains about some of
               | those cases.
               | 
               | As I said I think it's a language maturity thing. It will
               | get there.
        
               | vlovich123 wrote:
               | The borrow checker changes you're talking about is
               | several years old. The next gen borrow checker should
               | solve the remaining annoyances but that's years away.
               | 
               | But I disagree that Rust code breaks down to emulating
               | pointers by way of handles.
        
               | jpc0 wrote:
               | Not entirely what I was saying, it's one of the patterns
               | I've seen and those that use arrays and indexes into
               | arrays as "handles" will also vehemently disagree with me
               | when I say they just implemented "we have pointers at
               | home".
               | 
               | Those are pointers, there's slightly more protections
               | with them but depending on how you model it use after
               | free bugs are back on the table when doing that. At least
               | buffer overflows shouldn't be possible since that is
               | runtime checked.
        
               | mungaihaha wrote:
               | > I'm still not seeing any description of specific
               | challenges you feel are harder in Rust than in C/C++
               | 
               | It's harder to model domains in Rust. C has the same
               | problem and it is a big reason why the industry standard
               | for game dev is C++
        
               | tuna74 wrote:
               | Could you expand on this? What exactly doe "model
               | domains" mean?
        
               | vlovich123 wrote:
               | Care to give an example? I think GUIs remain the softest
               | domain modeling area, but that's about how do you do next
               | gen GUI toolkits that are super high performance, safe,
               | and lower overhead vs what Rust has today. But that's an
               | underserved niche anyway vs c++ toolkits or electron.
               | 
               | The other weak parts might be ecosystem immaturity (eg
               | Unreal vs Bevy) but that's not a language modelling
               | issue.
        
             | knorker wrote:
             | In my experience Rust is much easier for advanced projects.
             | Once you get into high performance code, C++ takes much
             | more time, for two reasons.
             | 
             | 1. With C++ you have to think a lot. Like, _a lot_. To
             | convince yourself that what you are doing is safe, and that
             | the lifetimes and races of various objects are safe. Rust
             | takes a huge load off, by failing to compile your mistakes.
             | You know that for the code that doesn 't use "unsafe", if
             | it compiled, then you don't even have to think about races
             | or lifetimes.
             | 
             | 2. C++ has _so_ many places where copies sneak in. Tracking
             | down needless copies in C++, for object types where it 's
             | not as simple as "just disable copy construct & copy
             | assign", can be tricky and is _extremely_ brittle to future
             | changes of code. And not just for the CPU cost of copies,
             | but RAM costs too.
             | 
             | And I say that as someone who's been coding C++ on a daily
             | basis since the 1990s.
        
         | pclmulqdq wrote:
         | Rust is easy to understand as "a language by browser writers
         | for writing browsers." That statement alone gives you most of
         | Rust's design choices:
         | 
         | * Safety over everything else
         | 
         | * Very good single threaded performance
         | 
         | * Javascript-like syntax and a Javascript-like package manager
         | 
         | I have been working on some software in Rust recently that
         | needs bit and byte manipulation, and we have "unsafe"
         | everywhere and hugely complicated spaghetti compared to the
         | equivalent code in C.
        
           | 1932812267 wrote:
           | I love your username, btw :)
        
           | pjmlp wrote:
           | Most likely because many keep forgetting that bit and byte
           | manipulation in C is a mix of implementation defined and UB,
           | depending on how it is coded.
        
             | motorest wrote:
             | What's the problem of using toolchain-specific features
             | instead of behavior defined by the standard? Isn't this the
             | bread and butter of embedded development and the reason why
             | some behavior is purposely left undefined in the standard?
        
               | eru wrote:
               | > [...] and the reason why some behavior is purposely
               | left undefined in the standard?
               | 
               | It's one of the saner reasons why there's UB in C and
               | C++. But there's lots of crazier reasons.
        
               | motorest wrote:
               | > It's one of the saner reasons why there's UB in C and
               | C++.
               | 
               | It is the reason why the standard purposely leaves some
               | areas undefined.
               | 
               | C and C++ detractors talk a lot about UB but they always
               | show their understanding on the subject is at best
               | superficial. The parrot UB as if it was this major
               | gotcha, when it is literally behavior the standardization
               | committees make a call to not define behavior to
               | purposely leave it open, so that any implementation can
               | still be conforming even if they decide to implement
               | behavior not defined in the standard. It is that simple,
               | but somehow people parrot UB behavior as if it was this
               | major gotcha. Baffling.
        
               | pjmlp wrote:
               | Baffling is the ignorance in the ways of WG14 and WG21,
               | while pretending to be a know it all, maybe update
               | yourself on the relevant papers for C2y and C++26, aimed
               | at clearing up references to UB with erranous behaviour,
               | or implementation defined.
        
               | formerly_proven wrote:
               | This subthread is kinda strange with people claiming IB
               | and UB are the same, considering that C/C++ have had
               | clearly delineated definitions of undefined behavior
               | versus implementation-defined behavior for decades in
               | their term definitions.
        
               | grandempire wrote:
               | No, we do understand the difference and the comment still
               | stands. UB is a choice by the standard committee.
               | 
               | Signed integer overflow is undefined because it's not
               | even clear if you can detect it happens in all
               | implementations. Do you want a conditional after every
               | integer add?
        
               | grandempire wrote:
               | You are upset we aren't familiar with papers and
               | proposals which are not yet agreed on as standard? And
               | for unreleased C++ versions?
        
               | jenadine wrote:
               | What's upsetting is comments that confidently state
               | inaccurate or clearly wrong statements, thereby spreading
               | misconceptions
        
               | pjmlp wrote:
               | Nah, thankfully liability legislation is finally
               | happening so folks will actually bother to learn
               | standards and how compilers approach them.
               | 
               | Or eventually face consequences.
        
               | grandempire wrote:
               | So now we are waiting for new legislation and overhaul of
               | the tech system to make your point necessary?
        
               | immibis wrote:
               | And then it was a decision on the part of _compiler
               | vendors_ to define it to do insane things like time
               | travel. Not the standards committee. Compiler vendors
               | could have just as well defined it to wrap.
        
               | jenadine wrote:
               | Some compiler decided to define overflow as wrapping.
               | Such as GCC/clang when passing the -fwrapv flag.
               | 
               | Most projects don't use that flag though, why not?
               | 
               | Nite that if you assume -fwrapv, you're not writing in C
               | anymore, you're using a vendor specific dialect.
        
               | immibis wrote:
               | Everyone writes in vendor-specific dialects of C. Both
               | POSIX and Win32 define behaviour that isn't defined in C
               | (such as rules for unaligned pointers), while also
               | undefining behaviour that is defined in C (such as what
               | happens if you call fopen when one of the functions in
               | your program is called "open"). Even on embedded
               | platforms, there's no "main" function in freestanding C -
               | your entry point is a vendor-specific extension.
        
               | grandempire wrote:
               | Exactly. The classic example is invalid pointer deref.
               | It's too costly to check every deref against every
               | allocation (outside of special debug modes). So the
               | system usually can only detect if there is a virtual
               | memory page fault, in which case it can crash.
               | 
               | In embedded systems without virtual memory there is no
               | validity checking at all. Or it could periodically check
               | (random sampling).
               | 
               | So the standard makes the reasonable choice to leave it
               | undefined. If you can detect it and crash, that's great.
               | If not and you accidentally overwrite your programs
               | instructions, anything can happen.
               | 
               | Rust only avoids UB in so far as it relies on default
               | clang behavior on modern hardware.
        
               | steveklabnik wrote:
               | Re Rust, that's simply not true. Rust relies on compile
               | time validity checks that have nothing to do with virtual
               | memory.
        
               | grandempire wrote:
               | You confused two parts. I am not saying that Rust has the
               | same pointer deref, I'm saying that if you try to specify
               | rust you will find parts that implicitly rely on clang or
               | hardware default. In other words the behavior is
               | unspecified.
        
               | steveklabnik wrote:
               | The borrow checker has a formal proof and it is
               | completely independent from hardware.
        
               | grandempire wrote:
               | Gatorade gives you electrolytes.
        
               | jenadine wrote:
               | > Rust only avoids UB in so far as it relies on default
               | clang behavior on modern hardware.
               | 
               | That's not true.
               | 
               | Rust behaviour avoids UB by not compiling with invalid
               | reference deref because it checks life time.
               | 
               | Clang's behaviour is likely a crash but anything can
               | still happen. It is literally undefined.
        
               | grandempire wrote:
               | > Clang's behaviour is likely a crash but anything can
               | still happen.
               | 
               | You act as if clang itself is random. Clang will do and
               | handful of things none of them which will melt your
               | computer.
               | 
               | A given implementation may choose to do anything. Clang
               | and gcc on major operating systems do reasonable things.
               | 
               | > It is literally undefined.
               | 
               | Ok but I'm explaining _why_ undefined is the right thing.
        
               | jenadine wrote:
               | The code generated by clang can do anything.
               | 
               | Ok, clang will not directly melt my computer because
               | clang just generates code, but imagine this:
               | if (temperature_too_high)
               | lower_temperature();
               | 
               | if somewhere else the code access invalid pointer, clang
               | may decide to remove this condition altogether because it
               | thinks it must be dead code, for example. And running
               | this code will melt the computer even though the
               | programmer thought this wouldn't be possible.
        
               | wrs wrote:
               | You're describing implementation-defined behavior, not
               | undefined behavior. IB does something but the committee
               | doesn't say what. UB does anything and the compiler can
               | assume you never intended to cause it.
        
               | pjmlp wrote:
               | Security exploits is the problem, because what common
               | people that don't read WG14 mailings think the word
               | undefined means, and what everyone else involved with
               | creating compilers understand what they are allowed to
               | do, is not the same.
        
               | motorest wrote:
               | > Security exploits is the problem, because what common
               | people that don't read WG14 mailings think the word
               | undefined means, and what everyone else involved with
               | creating compilers understand what they are allowed to
               | do, is not the same.
               | 
               | Your comment lacks credibility. Your hypothetical
               | scenario would only be conceivable if a) a team was well
               | versed enough in C to adopt a specific toolchain to
               | leverage implementetion-defined behavior that leveraged
               | behavior left undefined by the C standard, b) somehow the
               | same team decides on a whim to replace their toolset with
               | some other random toolset without eve being aware of
               | their toolset-specific code. This is far from a realistic
               | scenario, and reads more like mindless complains about UB
               | coming from a place of ignorance.
        
               | pjmlp wrote:
               | CVE database, the ongoing liability laws in cybersecurity
               | across several nations, and companies that also happen to
               | be C and C++ compiler vendors, are my credibility.
               | 
               | It is incredible how for the last 50 years we keep
               | getting ad-hominens from folks that think actually to
               | know anything at all about security, and only clueless
               | junior developers don't know what they are doing.
        
             | pclmulqdq wrote:
             | Exactly this. Most of the art of it is avoiding UB and
             | sticking to implementation-defined behavior.
        
           | pcwalton wrote:
           | > I have been working on some software in Rust recently that
           | needs bit and byte manipulation, and we have "unsafe"
           | everywhere and hugely complicated spaghetti compared to the
           | equivalent code in C.
           | 
           | I'm curious what makes this so different from my experience.
           | I rarely ever have to write "unsafe", and I'm writing quite
           | low-level engine code that certainly uses bit manipulation.
           | In fact, crates like bitflags and fixedbitset make it so easy
           | that I tend to get dinged in code reviews for using bit flags
           | when structs of booleans would be simpler :)
        
             | eru wrote:
             | > I'm curious what makes this so different from my
             | experience. I rarely ever have to write "unsafe", and I'm
             | writing quite low-level engine code that certainly uses bit
             | manipulation.
             | 
             | Perhaps your usecase is similar enough to eg JavaScript
             | engines? Because that's a usecase that browser writers
             | would at least have in mind?
        
               | Ygg2 wrote:
               | It's not. It's Bevy.
               | 
               | What effects this is separating your unsafe into unsafe
               | abstractions. If you're careful you don't have to write
               | too much unsafe. Granted it's not easy.
        
               | pclmulqdq wrote:
               | A game engine is much more similar to a browser engine
               | than you think. The operations you have discussed, using
               | bits as flags, are also the most basic forms of binary-
               | level manipulation out there. Things like tagged pointers
               | and bit flags fit nicely and neatly into an encapsulated
               | unsafe abstraction (provided you want to add an extra
               | 1000 lines of code for it).
               | 
               | As far as I can tell, any time you want to rely on the
               | exact binary layout of something in memory, you need
               | unsafe. As a corollary, any time you want to bit cast
               | from one type to another, you need unsafe. This means
               | that things like succinct data structures and building
               | network protocols need quite a bit of unsafe everywhere.
               | The former needs you to do things like "store 14 bits of
               | X here and 12 bits there..." The latter needs control of
               | bit and byte layout because you want to carefully
               | eliminate implementation-defined compiler behavior.
        
               | CryZe wrote:
               | Take a look into bytemuck or zerocopy. I haven't used
               | unsafe when doing byte level manipulation in a long time.
        
               | Ygg2 wrote:
               | And a jet engine and IC engine are engines, but putting
               | your car engine into a Boeing and vice versa would be an
               | unwise decision.
               | 
               | I'd argue you're over-abstracting the differences. They
               | have different purposes. Game engines need high
               | performance, while browsers need to enable wide selection
               | of APIs.
        
               | pcwalton wrote:
               | > As far as I can tell, any time you want to rely on the
               | exact binary layout of something in memory, you need
               | unsafe. As a corollary, any time you want to bit cast
               | from one type to another, you need unsafe.
               | 
               | No, that's what bytemuck is for. If bytemuck didn't
               | exist, sure, I'd be using a lot of unsafe.
        
           | motorest wrote:
           | > * Javascript-like syntax and a Javascript-like package
           | manager
           | 
           | I think this is not serious criticism. The "javascript-like
           | package manager" reference actually refers to fixing a major
           | problem with the developer experience playing legacy
           | programming languages such as C or C++. Java has those, .NET
           | has those, every single mainstream programming language has
           | those. Except C or C++.
           | 
           | Rust might be riddled with "the emperor has no clothes"
           | aspects, but having a package manager is not it.
        
             | pclmulqdq wrote:
             | That's not a negative aspect of Rust, and I'm not picking
             | out a list of things I dislike about Rust. Making a
             | statement that isn't unequivocally positive does not equal
             | criticizing something.
             | 
             | Cargo is, however, a similarity with JS. On the whole a
             | good one. Also, cargo works much more like npm than maven,
             | for example.
        
               | pornel wrote:
               | Cargo has been co-created by Yehuda Katz, who worked on
               | Ruby's Bundler before. Cargo has been designed after npm,
               | so it definitely took lessons from it, but it doesn't
               | make sense to just broadly attribute this to Rust being
               | JavaScript-adjacent.
               | 
               | The Rust syntax is not coming from JavaScript. It even
               | has conflict with it, using `let` and `const`
               | differently, since the `let` in Rust comes from Ocaml,
               | not JS.
               | 
               | Both JS and Rust copy from the same C/C++ roots. Rust's
               | curly brace flavor is more similar to Go and Swift. The
               | original author of Rust liked a lot of languages with
               | different syntaxes, but the C-like syntax has been a
               | pragmatic choice to avoid putting off the target audience
               | of C++ programmers:
               | 
               | http://venge.net/graydon/talks/intro-talk-2.pdf
        
         | 1932812267 wrote:
         | I've written a fair bit of SIMD code in Rust, and it definitely
         | had lots of sore spots.
         | 
         | The main advantage was that, because Rust doesn't use TBAA,
         | it's completely legal (and safe, if you use bytemuck) to freely
         | cast pointers and values around. TBAA in C++ makes it much
         | easier to hit undefined behavior.
         | 
         | But also, because of various miscompilations, Rust refuses (or
         | at least refused) to pass SIMD arguments in registers, so every
         | non-inlined function call passes arguments via the stack. There
         | were also miscompilations if you enabled a target_feature for
         | just one function, so we ended up just passing `-C target-
         | cpu=...` globally, and if we wanted to support a different
         | microarchitecture, we just recompiled the whole program. On top
         | of that, there's no good way to check to see what
         | microarchitecture you're compiling for, so we had to resort to
         | specifying the target cpu in multiple places, with comments
         | reminding us to keep the places in sync.
        
         | pcwalton wrote:
         | > Even before getting into SIMD, try using Rust for concurrent,
         | succinct, or external-memory data structures. It quickly
         | becomes clear where the friction is.
         | 
         | It's the exact opposite for me: I use concurrent data
         | structures more often in Rust than I do in C++ because I don't
         | have to worry about dumb data race bugs. If one of my Bevy
         | systems is slow, I slap par_iter() on the query and if it
         | compiles it probably works, or at least fails for a not-stupid
         | reason.
        
           | SleepyMyroslav wrote:
           | What do you think about 'par_iter' having to wait for work
           | imbalance to return execution back? With 'when_all' like
           | primitive one can continue execution on any thread without
           | losing one for waiting. ps as someone who does not have rust
           | job i would like to see an example how rust deals with task
           | based systems if you have public one at hand ofc.
        
             | pornel wrote:
             | The rayon library uses work stealing for this. Its parallel
             | iterators offer some control of splitting strategies and
             | granularity, so you can tune the trade-off between full
             | utilization and cost of moving things between threads.
             | 
             | Additionally, in Bevy, independent queries (systems) are
             | executed in parallel, so there's always something else to
             | do, except your one worst loop.
        
           | pclmulqdq wrote:
           | Concurrent data structures are rarely faster than "lock +
           | non-concurrent data structure" and if you're putting
           | constructs like par_iter() in a lot of places, there's a good
           | chance you would be better off with the "dumb" pattern than
           | the concurrent data structure.
           | 
           | The same goes with Arc - if you're using it a lot there's a
           | good chance that code with a lot of Arcs is slower than
           | equivalent GC-ed code.
        
             | 1932812267 wrote:
             | While it's true that par_iter() uses a concurrent data
             | structure under the hood, it's specifically designed to use
             | work-stealing to avoid needing threads to communicate.
             | 
             | Why would putting a lock over a global workqueue be faster
             | than per-thread workqueues that don't require inter-thread
             | communication (except in the case where work-stealing is
             | required)?
        
               | pclmulqdq wrote:
               | Atomics are very expensive operations. Lock/unlock is two
               | atomics. Many concurrent data structures will end up
               | doing many more atomic operations than you expect.
               | 
               | The general wins of concurrent data structures come when
               | you really are accessing them truly concurrently - as in
               | when many threads on many cores are heavily contending
               | for access and you need to make global progress.
        
               | 1932812267 wrote:
               | Sure! However, the work-stealing queue in rayon [1] uses
               | three atomic operations instead of the two atomic
               | operations for a mutex for a global lock. The difference,
               | however, is the three atomic operations for the thread-
               | local queue should be uncontended, whereas a global lock
               | on a global work queue would experience contention from
               | every thread trying to access it for jobs.
               | 
               | Between the choices of "single work sharing queue with a
               | big mutex on it that all threads access for work" vs
               | "per-thread work-stealing queue that's uncontended for
               | the cost of one extra atomic," in what situations would
               | the work-sharing queue with the global mutex outperform?
               | Perhaps if there's a small number of jobs, and there's
               | not enough time for the work-stealing algorithm to
               | distribute jobs to the worker threads before the work-
               | sharing algorithm has already finished.
               | 
               | [1]: https://github.com/crossbeam-
               | rs/crossbeam/blob/423e46fe20471...
        
               | pclmulqdq wrote:
               | Run a benchmark. With low contention, the lock will
               | outperform. Atomics are very expensive assembly
               | instructions.
               | 
               | Fedor Pikus has a good talk on this at cppcon 2019.
        
               | 1932812267 wrote:
               | I've seen the talk! The issue with using a global lock on
               | a global work queue is that, unless the work items have
               | drastically different compute times, there _will_ be high
               | contention on the lock.
               | 
               | I ran a benchmark [1], which shows that this is correct:
               | 
               | Results on quad-core Intel Linux box:
               | 
               | $ hyperfine target/release/testit 'env USE_RAYON=1
               | target/release/testit' Benchmark 1: target/release/testit
               | Time (mean +- s): 2.526 s +- 0.139 s [User: 4.709 s,
               | System: 11.425 s] Range (min ... max): 2.391 s ... 2.730
               | s 10 runs
               | 
               | Benchmark 2: env USE_RAYON=1 target/release/testit Time
               | (mean +- s): 174.1 ms +- 0.9 ms [User: 212.1 ms, System:
               | 121.1 ms] Range (min ... max): 173.1 ms ... 175.4 ms 16
               | runs
               | 
               | Summary env USE_RAYON=1 target/release/testit ran 14.51
               | +- 0.80 times faster than target/release/testit
               | 
               | Results on M1 Pro:
               | 
               | $ hyperfine target/release/testit 'env USE_RAYON=1
               | target/release/testit' Benchmark 1: target/release/testit
               | Time (mean +- s): 692.2 ms +- 8.3 ms [User: 491.4 ms,
               | System: 5693.6 ms] Range (min ... max): 683.2 ms ...
               | 704.5 ms 10 runs
               | 
               | Benchmark 2: env USE_RAYON=1 target/release/testit Time
               | (mean +- s): 63.0 ms +- 2.1 ms [User: 97.7 ms, System:
               | 47.0 ms] Range (min ... max): 61.0 ms ... 71.2 ms 44 runs
               | 
               | Summary env USE_RAYON=1 target/release/testit ran 10.99
               | +- 0.39 times faster than target/release/testit
               | 
               | [1]: https://play.rust-
               | lang.org/?version=stable&mode=debug&editio... (I'm just
               | using the rust playground as a pastebin; the actual
               | benchmarks were run locally)
        
               | zozbot234 wrote:
               | > Atomics are very expensive operations.
               | 
               | Atomics are very expensive when contended - which is also
               | the case where locks would introduce blocking and reduced
               | performance. Uncontended atomics are relatively cheap.
        
               | pclmulqdq wrote:
               | I suggest you run a microbenchmark. Uncontended atomics
               | are some of the most expensive assembly instructions out
               | there. They also acquire global system locks on certain
               | stages of execution.
        
               | janwas wrote:
               | Yep, including draining the store buffer.
               | 
               | We've gotten our ThreadPool barrier+wait to use only
               | acq/rel, but not yet the work stealing. Does anyone have
               | experience with that already?
        
               | jandrewrogers wrote:
               | FWIW, work stealing can be pretty expensive. It is only
               | efficient under a narrow set of assumptions about the
               | workload.
        
         | jandrewrogers wrote:
         | One of the things I find interesting about SIMD is that a lot
         | of behavior that is "undefined" for scalar types in C-derived
         | languages is explicitly fully defined when you use SIMD
         | intrinsics with the same integral types. UB exists to hide the
         | fact that major CPU architectures give different results for
         | basic ALU operations in some cases. SIMD makes no such pretense
         | of abstraction. If I am using AVX-512 I explicitly get the full
         | Intel architecture experience, the implementation details are
         | not hidden behind UB. Same with ARM, etc.
         | 
         | For example, shift overflows are masked on x86, zero-filled on
         | ARM, and undefined in C/C++. In SIMD-land, none of this is
         | hidden and so you design your code to leverage the reality that
         | those instructions behave differently, whereas in C/C++ only
         | the behavior they have in common is "defined".
         | 
         | The vector ISAs are sufficiently different from each other (and
         | normal CPUs) that it is like trying to build a compiler that
         | can automagically produce optimized code for both CPUs and GPUs
         | from the same source tree. I am not optimistic that this will
         | happen anytime soon. AVX-512 essentially started life as a GPU
         | ISA, which probably explains the interesting fact that a modern
         | x86 CPU core has more AVX-512 registers than x86 registers.
        
           | grandempire wrote:
           | Of course. Many things are UB because dictating a policy for
           | all machines doesn't make sense. Whereas AVX is a
           | specification for a specific hardware capability.
        
         | athrowaway3z wrote:
         | https://xkcd.com/378/
        
         | imtringued wrote:
         | Python doesn't have anything remotely similar to Cargo that
         | isn't written in Rust (uv is). It's exactly backwards. Python
         | is benefitting from Rust philosophy.
        
         | fulafel wrote:
         | > Rust feels like a Python developer's idea of a high-
         | performance computing language.
         | 
         | I'd say the language complexity (and it keeps piling more
         | things) means it's pretty far from Python's ideas.
         | 
         | Julia is closer.
        
           | andrepd wrote:
           | Rust is an extraordinary simple language. Compare it to the
           | complexity of C++.
        
             | fulafel wrote:
             | We can say that Mars is more similar to Earth than Jupiter
             | even though Sagittarius A* exists.
        
               | andrepd wrote:
               | Unsure which celestial body is supposed to be which in
               | this comparison :p
        
             | vacuity wrote:
             | If you only compare against C++, maybe. Otherwise, no. C++
             | and Rust are in a class of their own when it comes to
             | compile times and complexity.
        
               | andrepd wrote:
               | Well C++ is the only major language (other than C itself)
               | that occupies the same space. So it's natural that they
               | are compared. And Rust is an order of magnitude simpler
               | language than C++ almost comical web of intricacy.
        
         | beng-nl wrote:
         | I'm seriously interested; what is the best
         | language/franework/method that will squeeze out every bit of
         | performance from advanced hardware that you know of?
        
       | isusmelj wrote:
       | I've been playing around with SIMD since uni lectures about 10
       | years ago. Back then I started with OpenMP, then moved to x86
       | intrinsics with AVX. Lately I've been exploring portable SIMD for
       | a side project where I'm (re)writing a Numpy-like library in
       | Rust, mostly sticking to the standard library. Portable SIMD has
       | been super helpful so far.
       | 
       | I'm on an M-series MacBook now but still want to target x86 as
       | well, and without portable SIMD that would've been a headache.
       | 
       | If anyone's curious, the project is here:
       | https://github.com/IgorSusmelj/rustynum. It's just a learning
       | exercise for learning Rust, but I'm having a lot of fun with it.
        
       | IshKebab wrote:
       | A problem for RISC-V is going to be that there's currently no way
       | for user code to detect the presence of RVV. I have no idea how
       | you can do multiversioning with that limitation.
        
         | hmry wrote:
         | The solution is to ask the OS to detect it for you. Linux
         | offers a syscall for this (riscv_hwprobe). Has the drawback
         | that it requires OS support, of course. But RVV requires OS
         | support anyway (e.g. managing mstatus, saving vector registers
         | on context switch), so that seems fine to me.
        
           | dzaima wrote:
           | There is some work on an OS-agnostic feature detection C API:
           | https://github.com/riscv-non-isa/riscv-c-api-
           | doc/blob/main/s.... Still quite new though, and potentially
           | might change (as it did a month ago).
        
         | thomashabets2 wrote:
         | Oh? Isn't that what this does?
         | std::arch::is_riscv_feature_detected!("v")
         | 
         | Hmm... now that I actually experiment with it, I can't get it
         | to return `true` on hardware that does support it, unless I
         | also compile with -Ctarget-feature=+v. And if I do, then the
         | binary crashes with SIGILL before getting to that point on
         | hardware without rvv.
         | 
         | So if it's always equal to cfg!(target_feature="v"), then what
         | does that even mean?
         | 
         | I have created https://github.com/rust-lang/rust/issues/139139
        
         | fulafel wrote:
         | What happens there when you try to execute a missing RVV
         | instruction ? On other archs you get a SIGILL which you can
         | handle.
        
           | dzaima wrote:
           | With RISC-V being an open ISA a vendor can freely add some
           | non-RVV thing on the instruction space used by RVV if it
           | doesn't desire to support RVV. And that already exists with
           | Xtheadvector (aka RVV 0.7.1) where thead made hardware with
           | pre-ratification RVV that's rather incompatible with the
           | ratified RVV1.0 but still uses generally the same encoding
           | space.
        
           | IshKebab wrote:
           | It's "reserved" which is basically the same as C's UB -
           | anything can happen (nasal dragons etc.) so you can't rely on
           | it.
        
             | bigstrat2003 wrote:
             | Just because it's reserved doesn't mean anything can
             | happen. On x86_64, you get a clearly defined error when you
             | use reserved bits and the like. I don't know if RISC-V is
             | the same, but if it isn't it's because they chose to be
             | vague, not because that's what it means to be reserved.
        
               | dzaima wrote:
               | For what it's worth, the RISC-V unprivileged spec says:
               | 
               | > The behavior upon decoding a reserved instruction is
               | UNSPECIFIED.
               | 
               | > Some platforms may require that opcodes reserved for
               | standard use raise an illegal-instruction exception.
               | Other platforms may permit reserved opcode space be used
               | for non-conforming extensions.
               | 
               | The RVA23 says: (https://github.com/riscv/riscv-
               | profiles/blob/main/src/rva23-...)
               | 
               | > Implementations are strongly recommended to raise
               | illegal-instruction exceptions when attempting to execute
               | unimplemented opcodes or access unimplemented CSRs.
               | 
               | and has an optional extension "Ssstrict":
               | 
               | > Ssstrict No non-conforming extensions are present.
               | Attempts to execute unimplemented opcodes or access
               | unimplemented CSRs in the standard or reserved encoding
               | spaces raises an illegal instruction exception that
               | results in a contained trap to the supervisor-mode trap
               | handler.
        
       | DeathArrow wrote:
       | Using SIMD in C#:
       | 
       | https://xoofx.github.io/blog/2023/07/09/10x-performance-with...
       | 
       | https://learn.microsoft.com/en-us/dotnet/standard/simd
        
       ___________________________________________________________________
       (page generated 2025-03-30 23:02 UTC)