# I translated Peter Shirley's Raytracer to Julia. C++ 1min -\> Julia 2m30s -\> 15s (@threads)

**URL:** <https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671>\
**Category:** Visualization\
**Tags:** images, graphics\
**Created:** [February 2, 2022, 4:04pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671 "2022-02-02T16:04:07Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 4:04pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/1 "2022-02-02T16:04:07Z")

</div>

Hi,

I like raytracing and there are a nice set of books

[https://github.com/RayTracing/raytracing.github.io](https://github.com/RayTracing/raytracing.github.io)

so now I’m a bit better at Julia, I transcoded the C++ into Julia and got some nice speedups.

The single threaded version went from 9m in C++ to 2m30 in Julia (with no tricks - I don’t know any!)

(The C++ had no compiler flags, after using -O 3 and -march=native it was down to about 1 minute)

And when I added Threads (40 of them 🙂 )I got it down to 15s, including compilation.

[Here’s my julia code](https://github.com/lawless-m/raytracing.github.io/tree/master/Julia)

And here’s the [Book’s Github Discussion page](https://github.com/RayTracing/raytracing.github.io/discussions/987) where I made a similar post to this

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [February 2, 2022, 4:11pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/2 "2022-02-02T16:11:16Z")

</div>

Sorry for the noob question, does your Julia code produce images [like the one](https://raytracing.github.io/images/img-1.21-book1-final.jpg) in the “Ray-tracing in one weekend” book?

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 4:11pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/3 "2022-02-02T16:11:50Z")

</div>

yes, that exact one (modulo the rand()s)

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [February 2, 2022, 4:13pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/4 "2022-02-02T16:13:10Z")

</div>

Wow, I’m speechless.

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 4:19pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/5 "2022-02-02T16:19:48Z")

</div>

Yes, I’m quite pleased. I wasn’t doing it to make it faster, I just wanted it to produce results 🙂

But then I used a bit of `@code_warntypes` and @time and I reduced the allocations from 10s of millions to 10s of 1000s

---

<div class="post-metadata">

**Author:** ![rafael.guerra](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/rafael.guerra/32/216610_2.png) [@rafael.guerra](https://discourse.julialang.org/u/rafael.guerra)\
**Post date:** [February 2, 2022, 4:23pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/6 "2022-02-02T16:23:30Z")

</div>

FYI, searching through Discourse, there seem to be a few related threads. [I’m linking one of them.](https://discourse.julialang.org/t/ray-tracing-in-a-week-end-julia-vs-simd-optimized-c/72958)

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 4:32pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/7 "2022-02-02T16:32:41Z")

</div>

4 hours !

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [February 2, 2022, 4:33pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/8 "2022-02-02T16:33:54Z")

</div>

Cool! Some small styling things:

```julia
const Vec3 = SVector{3,Float64}

```

is the same as `SArray{Tuple{3}, Float64, 1, 3}`.

and note that you don’t need to define `magnitude`, actually you have:

```julia
using LinearAlgebra: norm
x = rand(Vec3)
norm(x)

```

and also you could do:

```julia
julia> randf(::Type{T},fmin,fmax) where T = fmin .+ (fmax-fmin) .* rand(T)   
randf (generic function with 1 method)

julia> randf(Vec3,-1,1)
3-element SVector{3, Float64} with indices SOneTo(3):
 -0.3009982992786695
 -0.15959842094608057
  0.27936134566733006

julia> randf(SVector{2,Float64},-1,1)
2-element SVector{2, Float64} with indices SOneTo(2):
 -0.8267854719262475
 -0.9675131426873447

julia> randf(Float32,-1,1)
0.52987814f0

```

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 8:40pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/9 "2022-02-02T20:40:13Z")

</div>

`magnitude(x,y,z)` is quicker than `norm([x,y,z])` because of the extra allocation

`magnitude(v)` and `norm(v)` are the same

`norm` is a strange name too, Shirley had `length` and I don’t like that either 🙂

I did swap out `unit_vector` for `LinearAlgebra.normalize`

that randf is cool too, but again, it creates [x,y,z] not x,y,z  
and this is all about reducing those allocations

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 8:47pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/10 "2022-02-02T20:47:29Z")

</div>

I moved the code, git was being annoying with the fork

[https://github.com/lawless-m/ShirleyRenderer.jl](https://github.com/lawless-m/ShirleyRenderer.jl)

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [February 2, 2022, 8:57pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/11 "2022-02-02T20:57:51Z")

</div>

If you use StaticArrays none of these allocate.

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 9:00pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/12 "2022-02-02T21:00:23Z")

</div>

it was still slightly slower

perhaps I shall rework it all the way through and see properly

---

<div class="post-metadata">

**Author:** ![jling](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jling/32/212909_2.png) [@jling](https://discourse.julialang.org/u/jling)\
**Post date:** [February 2, 2022, 9:07pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/13 "2022-02-02T21:07:15Z")

</div>

There’s no reason why Julia is so much slower, (not GC, not String, not I/O, not byte wrangling), well I know it’s easy for me to say it… But I hope we could make it within 125% time of C++. This is a micro benchmark big enough that people can point to as “composite” test for suitability blah blah

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 2, 2022, 9:55pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/14 "2022-02-02T21:55:23Z")

</div>

I added support for Images instead of PPM so now I can upload this

It uses more samples per pixel (150) and more depth (200), the defaults are 10 & 50

Again with 40 threads. I’m not going to chase more performance for the moment - that’s not the purpose of this - although I hope to experiment with some GPU offloading perhaps. I can’t use any CPU specialisations - Julia complains about a missing cx-16 instruction when I try and use -C

```julia
matt@pox:~/GitHub/ShirleyRenderer.jl/src$ julia --project=.. -L RandomScene.jl -e "@time main(;samples_per_pixel=150, max_depth=200)"
123.419846 seconds (328.84 M allocations: 24.327 GiB, 15.28% gc time, 1.76% compilation time)

```

 ![render](https://global.discourse-cdn.com/julialang/original/3X/6/0/60fa070bc6cae4159d2166e4b969ac10cd2421db.jpeg)

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 3, 2022, 10:06am UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/15 "2022-02-03T10:06:24Z")

</div>

150% is the best I have managed

matt@pox:~/GitHub/ShirleyRenderer.jl$ time julia -t 1 --project=. -L src/RandomScene.jl -e “main()”

real 1m13.689s  
user 1m12.059s  
sys 0m1.360s

matt@pox:~/GitHub/raytracing.github.io/src/$ time ./a.out \> a.out.ppm  
Scanlines remaining: 0  
Done.

real 0m49.599s  
user 0m49.570s  
sys 0m0.028s

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [February 3, 2022, 10:43am UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/16 "2022-02-03T10:43:55Z")

</div>

Your example has 24GB allocations and 15% GC time. Thats probably the problem. You could preallocate all the arrays used for each thread and reuse them. That much allocation will probably reduce your cache performance too.

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 3, 2022, 11:01am UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/17 "2022-02-03T11:01:45Z")

</div>

yep, but I think that’s quite a bit of re-work

I’ll spend some more time on it once I have the other code ported

---

<div class="post-metadata">

**Author:** ![lawless-m](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lawless-m/32/30869_2.png) [@lawless-m](https://discourse.julialang.org/u/lawless-m)\
**Post date:** [February 8, 2022, 4:22pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/18 "2022-02-08T16:22:37Z")

</div>

Turned out the design needed me to do the work of per thread anyway.

I used 2 muitable structs to hold the intermediate results, this reduced allocations significantly.

e.g.

```julia
mutable struct Hit 
	p::Point3
	normal::Vec3
	t::Float64
	front_face::Bool
	material::Material
	Hit() = new(zero(Point3), zero(Vec3), 0, true)
end

```

However the effect on the time was a few seconds at the most

```julia
$time julia --project=. -t 1 -L src/RandomScene.jl -e "@time main()"
 62.763695 seconds (6.64 M allocations: 330.171 MiB, 0.53% gc time, 3.15% compilation time)

real 1m10.922s
user 1m9.625s
sys 0m1.029s

```

and only 1 second wall for the 40 threaded version

```julia
matt@pox:~/GitHub/ShirleyRenderer.jl$ time julia --project=. -t 40 -L src/RandomScene.jl -e "@time main()"
  5.969525 seconds (6.64 M allocations: 330.225 MiB, 3.03% gc time, 35.72% compilation time)

real 0m14.056s
user 2m48.924s
sys 0m0.988s

```

I should take my own advice and try it but are mutable structs significantly slower than if I had a set of these and updated them ?

```julia
ps = Vector{Point3}(undef, nthreads())
normals = Vector{Point3}(undef, nthreads())
...

```

---

<div class="post-metadata">

**Author:** ![Raf](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raf/32/3383_2.png) [@Raf](https://discourse.julialang.org/u/Raf)\
**Post date:** [February 8, 2022, 5:29pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/19 "2022-02-08T17:29:25Z")

</div>

Immutable is usually better, but more importantly, type-stable is best. You need to run Cthulhu.jl and ProfileView.jl on everything and get rid of instabilities. At a glance, `Material` is an abstract type, but you have it as a field type. Instead maybe use a type parameter M, or if you need to mix materials in a vector, dont use a type to specify them at all.

---

<div class="post-metadata">

**Author:** ![cdsousa](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cdsousa/32/215553_2.png) [@cdsousa](https://discourse.julialang.org/u/cdsousa)\
**Post date:** [February 8, 2022, 5:50pm UTC](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671/20 "2022-02-08T17:50:39Z")

</div>

Just to let you know, I’ve been playing with your code too: [https://github.com/cdsousa/ShirleyRenderer.jl](https://github.com/cdsousa/ShirleyRenderer.jl)

One thing I noticed is that having 1) Materials as an abstract element of Sphere and 2) a vector of abstract Hitables in Scene, kind of hits a sweet spot in the compiler which produces a well-optimized code.

[Next page](https://discourse.julialang.org/t/i-translated-peter-shirleys-raytracer-to-julia-c-1min-julia-2m30s-15s-threads/75671.md?page=2)
