Live data from Hacker News

Rust and C++ on Floating-Point Intensive Code

reidatcheson.com

51–60 of 95 posts

Re: Rust and C++ on Floating-Point Intensive Code

#51
post #21

Earlier quoted context omitted.

Maybe you can use chunks_exact() and chunks_exact_mut(). The _exact versions allow lifting the bounds checks out of the loop and gave me some great performance boosts in image processing code.

These blocks are non-continuous. It operates over a part of a row, where the start of the next row is the start of the current row + some stride. I mean maybe? But I probably wouldn't anyway for this case as a slice reference is actually a "fat" pointer (ie twice the size of a normal pointer) and the length of the slice won't be used (the block size is known per kernel); LLVM might delete the length part anyway. Thes…

Once const-generics are released, I feel like you should be able to create your own "fixed-size slice" type, which uses the constant parameter for "bounds checking". I imagine that would be a lot more optimiser-friendly...

Re: Rust and C++ on Floating-Point Intensive Code

#53
post #48

What's "almost" algebraic about enum? It can definitely be used to construct sum types, and you can make product types with struct or inline in an enum

my best guess is that you can't do recursive enums without explicit boxing [edit: or other forms of indirection, like &T]¹. so you can't do this:

  enum List {
    Nil,
    Cons(T, List)
  }
instead, you have to box/reference-ify the recursive occurrence:

  enum List {
    Nil,
    Cons(T, Box>)
  }
so in certain circumstances it doesn't let you "coproduct" two types together, you might need to modify one a bit, which makes it a technically-not-exactly-a-coproduct (i think). a bit of a stretch but it sort of makes sense next to a by-reference-only ML langs where you can (co)product anything as you please

(btw, it's the same for recursive products)

---

1 - https://users.rust-lang.org/t/recursive-enum-types/2938/2

Re: Rust and C++ on Floating-Point Intensive Code

#54
post #53
post #48

What's "almost" algebraic about enum? It can definitely be used to construct sum types, and you can make product types with struct or inline in an enum

my best guess is that you can't do recursive enums without explicit boxing [edit: or other forms of indirection, like &T]¹. so you can't do this: enum List { Nil, Cons(T, List ) } instead, you have to box/reference-ify the recursive occurrence: enum List { Nil, Cons(T, Box >) } so in certain circumstances it doesn't let you "coproduct" two types together, you might need to modify one a bit, which makes it a technical…

I think that's because Rust types are Sized, but I could be wrong. The first example has size = Infinity, while the second has a constant size.

Re: Rust and C++ on Floating-Point Intensive Code

#55
post #53
post #48

What's "almost" algebraic about enum? It can definitely be used to construct sum types, and you can make product types with struct or inline in an enum

my best guess is that you can't do recursive enums without explicit boxing [edit: or other forms of indirection, like &T]¹. so you can't do this: enum List { Nil, Cons(T, List ) } instead, you have to box/reference-ify the recursive occurrence: enum List { Nil, Cons(T, Box >) } so in certain circumstances it doesn't let you "coproduct" two types together, you might need to modify one a bit, which makes it a technical…

Thanks for commenting, that's probably it. I was aware of the requirement for explicit box-ing but it didn't immediately come to mind.

Re: Rust and C++ on Floating-Point Intensive Code

#56
post #50

Earlier quoted context omitted.

Hum, did the program get vectorized?

As I said, the compiler did generate FMA instructions. These are SIMD instructions, so yes, the program was vectorized.

well technically FMA doesn't necessarily imply vectorization; it depends on whether the P{S,D} vs the S{S,D} suffixed instructions were being used, but if you saw the (P)arallel variants, then yes, it was vectorized.

Re: Rust and C++ on Floating-Point Intensive Code

#57
post #53
post #48

What's "almost" algebraic about enum? It can definitely be used to construct sum types, and you can make product types with struct or inline in an enum

my best guess is that you can't do recursive enums without explicit boxing [edit: or other forms of indirection, like &T]¹. so you can't do this: enum List { Nil, Cons(T, List ) } instead, you have to box/reference-ify the recursive occurrence: enum List { Nil, Cons(T, Box >) } so in certain circumstances it doesn't let you "coproduct" two types together, you might need to modify one a bit, which makes it a technical…

You don't have to box, but you do need some sort of type to make things sized. This is usually a pointer of some kind, but any kind of pointer works. Take references, for example:

  enum List {
    Nil,
    Cons(T, &'a List)
  }
  
  fn main() {
      let list = List::Cons("hello", &List::Nil);
  }
Box is usually chosen because it's a good default choice.

Re: Rust and C++ on Floating-Point Intensive Code

#58
post #16

Earlier quoted context omitted.

> I'm also partial to exposing it per-function so the control is actually in the hands of the people writing the code that know the context As a C++ programmer who routinely uses fast-math "until something breaks" with DSP code, I would find that capability very attractive.

This should probably be exposed as separate floating point types. With relatively cheap conversions. (Mostly done error checks.)

There's some conversation about exposing fast math on this internals thread: https://internals.rust-lang.org/t/pre-pre-rfc-floating-point...

Floating point code is really difficult to do correctly. LLVM doesn't actually model IEEE 754 correctly yet, although hopefully the remaining issues with the constrained intrinsics will be fixed by the end of the year (even then, sNaN support is likely to still be broken for some time).

What makes floating point more difficult than integers is two things. First, there is an implicit dependency on the status register that greatly inhibits optimization, yet very few users actually care about that dependency. The second issue is that there is many more properties that matter for floating point. For an integer, you essentially only care about three modes: wrapping (w/ optional carry flag), saturating, and no-overflow. Exposing these as separate types exhaustively is easy. For floating point, you have orthogonal concerns of rounding mode (including dynamic), no-NaN, no-infinity, denormals (including flush-to-zero support), contraction to form FMA, reciprocal approximation, associative, acceptable precision loss (can I use an approximate inverse sqrt?), may trap, and exception sticky bits. Since they're orthogonal, that means that instead of a dozen types, you need a few thousand types to combine them, although many combinations are probably not going to be too interesting.

Re: Rust and C++ on Floating-Point Intensive Code

#59
post #53

Earlier quoted context omitted.

my best guess is that you can't do recursive enums without explicit boxing [edit: or other forms of indirection, like &T]¹. so you can't do this: enum List { Nil, Cons(T, List ) } instead, you have to box/reference-ify the recursive occurrence: enum List { Nil, Cons(T, Box >) } so in certain circumstances it doesn't let you "coproduct" two types together, you might need to modify one a bit, which makes it a technical…

You don't have to box, but you do need some sort of type to make things sized. This is usually a pointer of some kind, but any kind of pointer works. Take references, for example: enum List { Nil, Cons(T, &'a List ) } fn main() { let list = List::Cons("hello", &List::Nil); } Box is usually chosen because it's a good default choice.

you're right of course! i should've used a more generic term like "indirection" or "reference", didn't mean to put emphasis on Box

Re: Rust and C++ on Floating-Point Intensive Code

#60
post #18

It looks like what the author was looking for is [1] f64::mul_add(self, a: f64, b: f64) -> f64 Adding it to the code indeed allows the LLVM to generate the "vfma" instruction. But it didn't significantly improve performance, on my machine at least. $ ./iterators 1000 Normalized Average time = 0.0000000011943495282455513 sumb=89259.51980374461 $ ./mul_add 1000 Normalized Average time = 0.0000000011861410852805122 sumb…

Hum, did the program get vectorized?

You can see the full compiler output here:

https://rust.godbolt.org/z/FbDqye

Post reply on HN