My day job is software for industrial X-ray computed tomography. The scanner takes thousands of X-ray images of a rotating object, and the reconstruction engine turns them into a 3D volume. I spent a year making that engine on the GPU much faster.
The code belongs to my employer, so I name no products, customers or data. All results are relative: same data, same machine, before and after.
The problem#
The volumes are bigger than GPU memory, so the engine builds them in slabs. For every slab it uploaded the whole input again. In total it sent about 35 times the input size to the GPU. The GPU also waited a lot, and the writer blocked it at the end of each run.
What I did#
- Upload only the detector rows a slab needs, and keep the rest cached on the GPU.
- Overlap copies with computation, so the GPU does not wait.
- Check memory before the run. A machine that cannot do the job says so at once, not after an hour.
- Write the volume in parallel.
Results#
| What | Change |
|---|---|
| End-to-end reconstruction | more than 2× faster |
| Data sent to the GPU | 89% to 99.8% less |
| Volume writing | 2.3× faster |
| Run start-up | 110× faster |
| Largest volumes | from “does not fit” to a normal run |
Not every idea worked. A CPU vectorisation made single loops 19× faster and changed nothing for the whole run, so I did not ship it.
Same image, bit for bit#
Speed means nothing if the image changes. Every change had to reproduce reference volumes exactly. A test harness compares them slice by slice. It also found an old bug: a small drift between slabs. After the fix, a volume built in one slab or in eight is identical.
Tools around it#
I also built a live viewer that shows what happens inside the GPU pipeline, in a separate process, so it can never stop a reconstruction.
In the same year I moved a scientific charting library from .NET Framework to .NET 9. With 100 charts open it runs about 12× faster and uses 8.6× less memory.