News for hackers. Stuff that matters.

nemequ1729

Re:SIMD Everywhere: 0.5.0 on Wednesday August 05, 2020 @12:22AM (#24057236)
> Not sure where you got that from... xsimd will detect your instruction set automatically. Do you mean that if you're distributing a single binary then you'll need to compile for the lowest common denominator?

No, what I mean is that since xsimd is an abstraction layer you can't really use the "full" ISA extension; you're limited to composing operations based on a simpler subset that is supported across multiple architectures.

For example, consider `_mm_maddubs_epi16`, which is a favorite example of mine because it's so specific… I honestly have no idea when this is useful, but I'm sure Intel had a particular use case in mind when they added it. It adds a 8-bit signed integer to an unsigned 8-bit integer, producing a signed 16-bit integer result for each lane. Then it performs saturated addition on each horizontal pair and returns the result.

Now I'm not that familiar with xsimd's API, but I can't imagine they have a single function that does all that. It's much more likely that you have to call a few functions in xsimd; maybe one for each input to widen to 16 bits, then at least one addition. For pairwise addition there might be a function, if not you'll need some shuffles to extract the even and odd values. Then perform saturated addition on those, which [isn't supported by xsimd](https://github.com/xtensor-stack/xsimd/issues/314), so you'll need a couple of comparisons and blends to implement that.

That's basically what we have to to in SIMDe in the fallback code; I don't have a problem with that at all. However, even if you're targeting SSSE3 xsimd it's pretty unlikely xsimd will be able to fuse that into a single `_mm_maddubs_epi16`.

OTOH, in SIMDe we can also add optimized implementations of various functions, and `_mm_maddubs_epi16` is no exception. There is already an AArch64 implementation which should be pretty fast, and a ARMv7 NEON implementation which isn't too bad.

With SIMDe what you get isn't the lowest common denominator of functionality, it's the union of everything that's available. SIMDe's `_mm_maddubs_epi16` may not be any faster than xsimd if you're not targeting SSSE3, but if you are targeting SSSE3 or greater SIMDe is going to be a lot faster.

SIMDe's approach isn't without drawbacks, of course. For one, it can be hard to know whether a particular function will be fast or slow on a given architecture, whereas lowest-common-denominator libraries will pretty much be fast everywhere but functionality will be a bit more basic. It's also a lot more work… there are around 6500 SIMD functions in x86 alone, and IIRC NEON is at around 2500.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @07:22PM (#24055567)
> I'd like to commend the authors for embarking on this.

As the crazy bastard who started SIMDe, thanks!

I'm very concerned about portability, so unfortunately just using clang isn't really an option. Most of my code has to work not only on GCC and clang, but also on MSVC (/me cries) and ICC, and I generally try to make it work on other compilers (PGI, IAR, etc.).

In my experience you're right, clang does do much better with vectors that don't match the hardware by default, but in SIMDe we actually have explicit fallbacks which call shorter functions twice and the result is pretty good on both compilers. For example, here is what `_mm256_add_ps` looks like on GCC and clang when targeting SSE2: <https://godbolt.org/z/n68Ecn>.

Length-agnostic instruction sets like SVE are definitely very interesting, but honestly I'd rather see them de-emphasize non-portable APIs like SVE and instead work on improving the compiler's ability to recognize the relevant code patterns to work with things like OpenMP SIMD (which, to be clear, does not require the OpenMP runtime). I'd also be happy to see more builtins which work cross-platform… for example, I'd love to see builtins for saturated operations which could easily be auto-vectorized by the compiler when used in an OpenMP SIMD loop.

Currently for new SIMD code, I start with an OpenMP SIMD implementation, then profile. If I see any spots which perform particularly poorly and/or are particularly hot, I'll work on some optimized implementations for those spots using intrinsics. In the future I hope to need to hand-optimize less code, but for now I think this offers a pretty good trade-off between portability, performance, and development time.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @07:05PM (#24055460)
I hadn't seen that post before, thanks.

This doesn't quite apply to SIMDe; the problem that post is talking about is really at a higher level… whether it is faster to do a bunch of shuffles or use some scalar code. Once SIMDe is called you've already made your decision, and at that level the hardware-based shuffles are much faster than scalar code. For example, see the decompression speed benchmarks for LZSSE-SIMDe (<https://github.com/nemequ/LZSSE-SIMDe>) (they're in the README).

It sounds like what that post really needs is a fast 16-bit gather operation. AVX2 has some 32-bit gather functions which you may be usable (2 gathers + a blend could emulate 16-bit gathers). For NEON, you could probably use one of the `vtbl` functions; they're all 8-bit, but that just means you have separate index entries for high and low bytes… it's a bit more code, but there shouldn't be any runtime overhead.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @06:54PM (#24055377)
> Not going to rewrite code I have already written, debugged and shipped.

I wouldn't, either. At least unless you want a WASM/AltiVec/etc. version. But if you already have good implementations SIMDe probably won't help.

OTOH, if all you have is a x86 implementation and a portable fallback, the SIMDe version of the x86 implementation will probably be faster than your portable version. That's what happened with MMseqs2 (<https://github.com/soedinglab/MMseqs2>).

> Don’t forget about MXCSR register in that suite

Yeah, rounding is definitely PITA. It's actually something I completely screwed up on in the beginning of the project and had to go back and correct :(. We do have some tests now which fiddle with the rounding mode to verify correctness, but could definitely use more, and obviously we can't always set a dedicated register to control behavior, so on some platforms `_mm_getcsr`/`_mm_setcsr`/`_MM_GET_ROUNDING_MODE`/`_MM_SET_ROUNDING_MODE` becomes `fegetround`/`fesetround`, which probably won't be a problem but still makes me uncomfortable.

The other area where we could really use more tests is replicating behavior for NaNs. By default we try to replicate the behavior of the function we're trying to emulate, but we currently only test NaN handling on a few functions :(. If you use -ffast-math or -ffinite-math-only we disable that code (compilers define __FINITE_MATH_ONLY__), though, and just use the fastest implementation we can.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @02:47PM (#24053205)
I'm not aware of anything in the works for Intel, but RISC-V seems to be going in that direction.

I'm hoping to start using SVE to implement AVX-512 and AVX in SIMDe soon.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @02:43PM (#24053176)
It does include support for AVX-512, it's just still a work in progress. AVX-512 is enormous (IIRC ~ 4k functions), so it will be a while before it is fully supported.

If you're targeting AVX-512 but don't have hardware that supports AVX-512 (like all AMD CPUs), SIMDe can be quite nice. The result is much faster than Intel's SDE and it's just native code that you can use your normal debugger on.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @02:38PM (#24053129)
Apart from the fact that you have to rewrite your code, the big disadvantage to something like this is that it's a bit slower.

With SIMDe you're still free to use functions like `_mm_maddubs_epi16` and they'll be really fast on x86, but still work everywhere.

With xsimd (and similar libraries) you're generally limited to the lowest common denominator.

FWIW, if abstraction layers are your thing you might want to look at std::experimental::simd (https://github.com/VcDevel/std-simd) instead. Google's Highway (https://github.com/google/highway) is also pretty interesting.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @02:29PM (#24053018)
(Lead developer of SIMDe here.)

It sounds like you actually know what you're doing, so in this case you're probably right, at least if all you do is compile your x86 code with SIMDe.

That said, SIMDe also provides support for other architectures, notably WASM SIMD 128 and AltiVec/VSX, as well as portable implementations which work everywhere, including on CPUs I'd never heard of until people told me SIMDe was working well on them (I'm thinking of Kalray, which supports vectors but doesn't have an API and instead relies on compiler auto-vectorization support).

One use case for SIMDe which may be interesting for you is that you can freely mix calls to different APIs. Say, for example, that you already have a bunch of x86 code written and want a NEON port. You can add SIMDe and you get a NEON port basically for free, then you can start adding some ifdefs to add optimizations for NEON without having to rewrite the whole thing. SIMDe doesn't in any way prevent you from optimizing your NEON (or whatever) port.

The way I tend to look at it is that SIMDe never makes your code slower, only more portable.

Re:SIMD Everywhere: 0.5.0 on Tuesday August 04, 2020 @02:15PM (#24052855)
I'm the lead developer of SIMDe.

I wouldn't say that portability is the main focus. The first step is to get portable implementations up and running, but a huge number of functions have optimized implementations for NEON, AltiVec/VSX, and WASM SIMD 128, and we're working on adding more. We go to a lot of trouble to get good performance on multiple architectures, basically writing each implementation several times and using ifdefs to switch depending on what the fastest version available to a given architecture will be.

Even just for the portable implementations, we use a lot of hints to help the compiler auto-vectorize. Almost every portable implementation has a loop which uses a pragma to try to get the compiler do the right thing (OpenMP SIMD, clang loop-specific pragmas, GCC ivdep, etc.). On top of that we take advantage of lots of compiler-sepecific features to speed things up where possible, including GCC-style vector extensions, __builtin_shuffle/__builtin_shufflevector, and __builtin_convertvector.

SIMDe never going to be as fast as someone who knows what they're doing writing an optimized implementation for a given target. However, it should be as fast (or faster) than someone who is just trying to do a direct port where they just try to match the existing code as closely as possible.