Don't forget that the JVM already has ways to communicate with native programs (JNI originally, but now a new FFI is in the works), so if you depended on any native code you could still use it.
Don't forget that the JVM already has ways to communicate with native programs (JNI originally, but now a new FFI is in the works), so if you depended on any native code you could still use it.
You would use it for the same thing that you can do with GraalVM Enterprise today: use native libraries written in C/Fortran/… from your other JVM code in a sandboxed way, without any possibility that the entire VM can crash due to memory corruption bugs in the C/Fortran/… code.
That counts as bad performance in my book.
As a matter of fact, you said it yourself in a neighbouring thread that all C needs is a large array - I cannot imagine how can you get any lower than that: mapping almost 1:1 to the general memory model of a CPU executing instructions with some addressable memory.
C++ has classes and templates, Rust has... a lot of things, they are definitely higher up in terms of abstractions built into the language.
C can’t control cache behavior, has no SIMD control, and no threading support (non-standard extensions doesn’t count) It maps well to PDP-11, but that has barely any similarity to modern CPUs.
For what its worth, one might argue that C++ and Rust are lower level, as they can even control some of the above listed functions.
As for “closeness to generated assembly”, as I said, at O3 the generated code doesn’t resemble the source at all — it might not even execute anything your wrote, it may have calculated the result at compile time and will just return the result/look it up in a map at runtime.
SIMD isn't really feasible to fully do in a standard way either. Without specific knowledge of the target, you can easily write SIMD-looking code that ends up slower than a scalar equivalent (e.g. if you do any non-constant shuffle on x86-64 pre-SSSE3 (i.e. the default), you'll get utterly awful results. On ARM NEON, a non-constant i32x4 shuffle is gonna be quite awful too).
Threading - <threads.h> is a standard header, no? (granted, it doesn't appear to be used much, but it does exist)
And, sure, every statement/expression/operator of yours doesn't end up always literally mapping to a single assembly instruction, but there's pretty much always a way it could, and quite often it does (and in the cases where it doesn't, it (usually) maps to something better than what you wrote). The main thing that doesn't is macros, but those are extremely trivially expandable.
SIMD is still the panacea of compiler optimizations, you won’t get significant performance improvements from trivial peephole optimizations like replacing * 2 with << 1, and the supposedly low-level C has no way of controlling that. Sure, vector sizes are different between processors, but so is many other things we already generalize/assume.
But an optimizing compiler will change up your loops all around, inline your functions, etc, so just because it could correspond to some naive assembly, it won’t unless you compile it with some toy.
My point with SIMD is that pretty much all current things do, at worst, map to one instruction (maybe more for things like large loads/stores) on modern 64-bit architectures. But with SIMD the operations supported by various architectures are so diverse, such that taking the intersection (i.e. preserving things mapping mostly-1-to-1 at worst), you'll have an unusable thing (no non-constant shuffles, 8-bit shifts, float rounding, movemask, fault-only-first loads, compress, limited conversions (for 64-bit floats x86 only has i32↔f64, but NEON only has i64↔f64; and narrowing integers takes a sequence of shuffles on pre-AVX-512 x86)). And if you take the union of architectures, a large amount of operations will just suck on any architecture that's not where it was from. Never mind architectures without SIMD.
Different vector sizes is a comparatively trivial problem to tackle (but then you get to the scalable SIMD sets, i.e. SVE & RISC-V V; and even those differ in an important way - SVE vectors are some multiple of 128 bits long, up to 2048, but rvv also guarantees it being a power of two, and can be up to 2^16 bits, at which point indexes in the vectors don't fit in 8-bit integers).
Sure, the optimizing compiler will optimize things, but that's what it does - optimize things, so its actions should be preferable, and manually writing something equivalent to what the compiler did is still very possible, and often not even that weird (with the discussed exception of vectorization).
If any of those is the one missing piece that's preventing you from vectorizing, then it's totally worthwhile to emulate it. It may sound horrible to take 4-5 instructions instead of one, but it can still be very worthwhile.
FYI for SVE, Arm has requested input and recently clarified that there will only be power of two vector lengths.
Happy to discuss.
In many cases, yeah, emulating things can be fine, but in others, very much not so. If not doing trivial (i.e. autovectorizable) things, I'd imagine you'd still have to be at least somewhat careful about what things will get emulated, and see whether the gain from everything around it makes it worth it for your desired architectures, which is still far from a state of things I'd consider worthy of being standardized.
Interesting note on SVE vector lengths though. I'll keep that in mind. Haven't gotten to exploring SVE/RVV much; don't have any personal hardware to optimize for to encourage it.
I agree it's important to benchmark the performance win in any case. Perhaps even automatically picking the best instruction set, because it's nontrivial to predict.
As to SVE, it can help on AWS Graviton3 - but it's still only 2x256 bit vs 4x128 bit, so whether it's a win depends on whether you can/do use some of the newer instructions that were not yet supported in NEON.
Maybe my use-case - implementing an array language - is unusually unfit for being done in a generic way, but I just don't see how it (or much of anything outside of very trivial things) would be reasonable to figure out how to do performantly with anything other than either manually using intrinsics, or with custom-written abstractions around such with known worst-case emulation amount (we're doing[0] the latter with a DSL[1] that's basically a basic compile-time code generator; that just leaves unsupported things undefined on any given architecture, and in most occurrences of hitting that I've found ways to write things avoiding expensive emulation).
[0]: https://github.com/dzaima/CBQN/tree/master/src/singeli/src
It is natural that C has about zero emulated operators - today's CPUs are compatible with ones likely built with that use-case in mind. That is a low bar for comparison, especially given a large speedup from SIMD :)
I agree a custom abstraction is currently the most reasonable approach for SIMD. We're basically doing the same thing, just differing a bit in how much emulation is acceptable, and the language for compile-time code generation (it's nice how compactly you can express things in Singeli).
Hmm, I disagree. C is a mid-level language, one level of abstraction up from assembly. C++ and Rust are high-level languages, one or two levels of abstraction higher than C.
I prefer the one of “what features does the language give you idiomatic control of”. In that vein, Rust is more low-level than by having simd support.
I thought everyone just went by the amount of abstraction of the machine code. So machine code is no abstraction, assembly is one level up, C is another level up, etc. The less the language "looks" like machine, the higher the "level" of the language.
The inclusion of SIMD support, in my view, falls in the mid-level category. It's abstracting the differing parallel computing implementations of different processors to make them all usable with the same code. That's not low-level in my book.
I'm not saying this to say that your stance is incorrect -- the "level" of a programming language has never been a technically well-defined thing anyway. I'm just saying this to express that my eyes have been opened to a perspective I was unaware of.