See Matt Pharr's very interesting blog posts on the development and open-sourcing of ISPC.
It's entirely possible that the winds have shifted and that the new management basically pointed a finger at the ISPC blog post series and said "more of that".
I'd +1 these recommendations and add it's probably valuable to anyone interested in doing GPGPU or fast parallel programming.
Also it links out to a bunch of other high-quality resources.
Matt Pharr's posts on ispc are an extremely interesting read, both from a technical perspective (programming abstraction and hardware design) and a human perspective (Intel politics, managing style, and career decisions).
For the vectorized CPU code, I found ISPC generally pleasant to use, but don't be fooled by its similarities to C. It's not a C dialect, and I got burned by assuming any implicit type conversion rules that are identical across C, C++ and Java would also hold for ISPC. The code in question was pretty simple conversion of a pair of uniformly distributed uint64_ts to a pair of normally distributed doubles (Box Muller transform). As I remember, operations between a double to an int64_t result in the double being truncated rather than the int64_t being cast. I wrote some C++ code and ported it to Java more or less without modification, but was scratching my head as to why the ISPC version was buggy. I remember the feeling of the hair standing up on the back of my neck as it dawned on me that the implicit cast rules might be different than those of C/C++/Java.
Seemingly arbitrarily deviating from C/C++ behavior in a language that's so syntactically close is a big footgun. Honestly, I think that C made a mistake. An operation between an integer and a floating point number should result in the smallest floating point type that has mantissa and exponent range both as large and the floating point operand and capable of losslessly representing every value in the range of the integer operand's type. If no such type is supported, then C should have forced the programmer to choose what sort of loss is appropriate via explicit casts. Disallowing implicit type conversion is also reasonable. However, if your language looks and feels so close to C, you really need good reasons to change these sorts of details about implicit behavior.
Similarly, I think C should have made operations between signed and unsigned integers of the same size to result in the next largest sized signed integer (uint32_t + int32_t = int64_t), or just not allow operations between signed and unsigned types without explicit casts.
error[E0277]: cannot add a float to an integer
--> src/main.rs:2:6
|
2 | 1+1.0;
| ^ no implementation for `{integer} + {float}`
Casts are historically such a massive source of bugs and unstability that the correct casting rule for every numerical computation is always: "Make the programmer explicitly choose."I think that implicit conversion from int to float is mostly harmless, as the result is a float and the float to int conversion is explicit, this is unlikely to create bugs.
intX to intY or uintX to uintY if Y>=X are two other conversions which are unlikely to create bugs but save a lot of boilerplate.
IMO, though, the only way to avoid having those rules is by requiring explicitness on the part of the programmer. All other options just substitute a different set of rules which for some cases will be expected and in other cases will be unexpected.
The ideal is to deliver the illusion of true threads to the programmer, where each thread is mapped to a SIMD unit. This is because on x86 systems, an AVX2 SIMD unit (YMM Register) can support 8x 32-bit values (256-bits). That's 16x 32-bit values for ZMM AVX512 registers. So you get a huge boost to parallelism if you treat each SIMD-unit as a separate thread.
In effect: it is far cheaper to make SIMD-"threads", than to make "real threads". So you get Vega64 (4096 Shaders) or NVidia V100 (5120 CUDA Cores). Even on Intel, you effectively get 8x SIMD cores per actual core.
The question is how to deal with this mapping, to keep things efficient and reasonable for the programmer. OpenCL, and ISPC have different strategies for dealing with this abstraction and trying to efficiently map this illusion to the programmer. The general "Thread Group" (OpenCL) and "Wavefront" (CUDA) abstraction seems to primarily capture this SIMD-width question.
But there's one additional thing on GPUs that don't exist on CPUs: and that's the LDS memory (AMD) or Shared memory (NVidia). Shared Memory is effectively a manually-managed L1 cache on GPUs.
GPUs have very weak cache hierarchies. In high-performance GPGPU compute, it is preferred for the programmer to manually manage transfers to and from global RAM to the ~64kB Shared Memory region. And of course, only Wavefronts / Threadgroups are guaranteed to share a portion of shared memory.
Intel systems don't need this abstraction: Intel CPUs have a full and proper cache which is unified with global RAM.
So I guess Intel is "taking a victory lap" with this blog post: noting that all of the complexity of Wavefronts / etc. etc. isn't really necessary on ISPC / Intel systems.
---------
Still, Intel never really achieved the performance that NVidia V100 or AMD Vega64 can achieve. Even Intel's Xeon Phi has far lower raw performance than what NVidia / AMD was able to put out.
Its certainly more difficult to write high-performance code on GPUs. But with so much more raw power on GPUs, many people have been able to extract higher performance in practice.
> more difficult to write high-performance code on GPUs. But with so much more raw power on GPUs
This seems two sides of the same coin; of course you can extract higher performance with lower coupling.
I don’t think the Xeon Phi was given a fair chance; they did two iterations on the idea before canning it. NVidia has been doing video cards forever.
Making it a niche product only available to a selected few, instead of mainstream GPGPUs, meant most of us hardly cared about it.