Raytracing weekend: debugging unexpected allocations


I’m new to Julia, and am out of ideas on how to prevent my code from allocating. I’ve made a fairly naive translation of the Raytracing Weekend program into Julia. I’m aware that I’m not the first person to do this, and when comparing my code to others’ it doesn’t seem that different, but it seems significantly slower. My code runs about 10x slower than the Rust implementation I wrote. I was hoping to get it much closer than that.

Here’s the function I’m trying to understand:

@inline function hit(sphere::Sphere, ray::Ray, tMin::Float64, tMax::Float64)::Union{HitRecord,Nothing}
    oc = ray.origin .- sphere.center
    a = lengthSquared(ray.direction)
    half_b = dot(oc, ray.direction)
    c = lengthSquared(oc) - (sphere.radius * sphere.radius)
    discriminant = (half_b * half_b) - (a * c)

    if discriminant < 0
        return nothing

    sqrtd = sqrt(discriminant)
    root = (-half_b - sqrtd) / a
    if root < tMin || tMax < root
        root = (-half_b + sqrtd) / a

        if root < tMin || tMax < root
            return nothing

    t = root
    p = at(ray, root)
    outwardNormal = (p .- sphere.center) ./ sphere.radius
    frontFace, normal = getFaceNormal(ray, outwardNormal)
    HitRecord(p, normal, t, frontFace, sphere.material)

Here’s a link to the full code on github if you want to see more context.

The very last line that creates a HitRecord is allocating. (Determined by running with --track-allocation=user.) I’ve tried a variety of things, including making Sphere generic over Material, and adding tons of extra type annotations, with no luck.

Most specifically, I’m trying to understand why there’s an allocation in this function where I’m returning an immutable struct. I’m guessing it still has something to do with how I’m storing material, but in this thread about optimizing the raytracing weekend code, the code’s structs are very similar, storing an abstract Material type on the HitRecord.

How do you represent 3D coordinates? as Vector?

In this case you can gain a lot by using SVector provided by the package StaticArrays. Statically sized means that the size can be determined from the type, which allows the compiler to make more aggressive optimizations.

Thanks! Yes, I’m using StaticArrays:

const Vec3 = SVector{3,Float64}

I think that the issue is that in your HitRecord the material field is of abstract type Material, which will cause an allocation. In my own version (here) I use a union: const Material = Union{Lambertian, Metal, Dielectric}.