I saw the headline and was immediately excited, and tbh it looks very cool, but it's a different sort of GPU compiler from mine, a python-native one, not a "C-style" one.
Here’s the full source of a Mandelbrot program:
#use "Stdio.xc"
#use "Par.xc"
#import "../src/include/bench_time.xc"
#define W 2048
#define SIZE (W * W)
i32 main(void)
{
// Eight runs: the first carries one-off costs (a GPU builds its kernel
// then), so the figure is the best run; the first is printed too. Auto
// decides after its fourth (two on each device), and uses the rest.
u32 total = 0;
i64 best = 0;
i64 first = 0;
for (u32 rep in 0..8)
{
total = 0;
i64 t0 = bench_now_us();
par mandel :reduce(+ total)
{
for (u32 i in 0..SIZE)
{
float cx = (float)(i % W) * (3.0f / 2048.0f) - 2.0f;
float cy = (float)(i / W) * (3.0f / 2048.0f) - 1.5f;
float x = 0.0f;
float y = 0.0f;
u32 k = 0;
while (k < 256 && x * x + y * y < 4.0f)
{
float xt = x * x - y * y + cx;
y = 2.0f * x * y + cy;
x = xt;
k = k + 1;
}
total = total + k;
}
}
i64 t1 = bench_now_us();
if (rep == 0)
first = t1 - t0;
if (rep == 0 || t1 - t0 < best)
best = t1 - t0;
}
printf("%u %lld %lld\n", (total + 500000) / 1000000, best, first);
return 0;
}
Just one line in that source code is what turns it from a bog-standard C-like program into a GPU kernel, automatically tracked, scheduled, and all data hazards transparently managed.
On my M4 max, that one line of code takes it from 481ms to 1.6ms. All ‘par’ does is say “this next block of code, call it “mandel”, is data-parallel, and for the ‘total’ variable all execution units should combine their output to that variable using +
No huge GPU boilerplate, no stream management for data transport from cpu to gpu and back, no memory barriers, no hassle. Just “hey, do this on the gpu”.
GPU blocks are bound at runtime using the best design for the hardware on the machine, obtained by measuring it (if it hasn’t been measured before). If it has, the cache is read. The user can always override, which is why ‘par’ blocks can be named.
One of the nice things about that is that if you find the bit you want to accelerate is too small to be useful, say the data-transport outweighs the computational speedup, (a) the runtime computer will figure that out and (b) you can hoist more code into the GPU if that's an option, to give it more work to do. The language is the same for both CPU and GPU so it's transparent...
The other thing is that the compiler will produce MSL, PTX for nvidia, SPIR-V and WGSL versions of the 'par' block, and since it's bound at runtime, you get support for pretty much everything.
What I'm looking at right now is multi-GPU systems. Consider a GPU to be a 'worker thread', and for each thread overlay the time spent uploading work units, the time spent computing, and the time spent downloading work results to the host. Now on Windows you only get 1 asynchronous copy engine for "consumer" GPUs like RTX {3|4|5}0x0 so there are limits, but MSL lets you have more I think and I haven't looked at AMD GPUs yet.
The thing that most people latch onto is the speed upgrade (419ms -> 1.6ms) but the real story here is the simplicity - using any dedicated language (CUDA, MSL, ...) you write a load of boilerplate and you have to worry about data hazards etc. I think of this as similar to when Apple introduced ARC (Automatic Reference Counting) - prior to that, engineers were constantly having to manage their memory manually, and there cough were a lot of errors. ARC changed the game, and I think xc is "ARC for GPUs".
I saw the headline and was immediately excited, and tbh it looks very cool, but it's a different sort of GPU compiler from mine, a python-native one, not a "C-style" one.
Here’s the full source of a Mandelbrot program:
Just one line in that source code is what turns it from a bog-standard C-like program into a GPU kernel, automatically tracked, scheduled, and all data hazards transparently managed.On my M4 max, that one line of code takes it from 481ms to 1.6ms. All ‘par’ does is say “this next block of code, call it “mandel”, is data-parallel, and for the ‘total’ variable all execution units should combine their output to that variable using +
No huge GPU boilerplate, no stream management for data transport from cpu to gpu and back, no memory barriers, no hassle. Just “hey, do this on the gpu”.
GPU blocks are bound at runtime using the best design for the hardware on the machine, obtained by measuring it (if it hasn’t been measured before). If it has, the cache is read. The user can always override, which is why ‘par’ blocks can be named.
One of the nice things about that is that if you find the bit you want to accelerate is too small to be useful, say the data-transport outweighs the computational speedup, (a) the runtime computer will figure that out and (b) you can hoist more code into the GPU if that's an option, to give it more work to do. The language is the same for both CPU and GPU so it's transparent...
The other thing is that the compiler will produce MSL, PTX for nvidia, SPIR-V and WGSL versions of the 'par' block, and since it's bound at runtime, you get support for pretty much everything.
What I'm looking at right now is multi-GPU systems. Consider a GPU to be a 'worker thread', and for each thread overlay the time spent uploading work units, the time spent computing, and the time spent downloading work results to the host. Now on Windows you only get 1 asynchronous copy engine for "consumer" GPUs like RTX {3|4|5}0x0 so there are limits, but MSL lets you have more I think and I haven't looked at AMD GPUs yet.
The thing that most people latch onto is the speed upgrade (419ms -> 1.6ms) but the real story here is the simplicity - using any dedicated language (CUDA, MSL, ...) you write a load of boilerplate and you have to worry about data hazards etc. I think of this as similar to when Apple introduced ARC (Automatic Reference Counting) - prior to that, engineers were constantly having to manage their memory manually, and there cough were a lot of errors. ARC changed the game, and I think xc is "ARC for GPUs".