4

I want to pass an array of objects to a function and return another array of the same size. How should I do this ? The code should be so fast. For example consider the below code:

struct triangle
    height::Float64
    base::Float64
end

function area(height::Float64, base::Float64)
    return 0.5*base*height
end

I want to define a function which returns the area of an array of triangles. What is the fastest way to do it?

2 Answers 2

9

You can leverage Julia's broadcasting syntax for this. Consider:

struct Triangle # Note: Julia convention is to capitalize types
    height::Float64
    base::Float64
end

# Define an area method for the Triangle type
area(t::Triangle) = 0.5 * t.height * t.base

# Create an area of random triangles
triangle_array = [Triangle(rand(), rand()) for _ in 1:10]

# Now we can use dot syntax to broadcast our area function over the array of triangles
area.(triangle_array)

Note that this differs from your code in that it directly uses the Triangle object for dispatch in the call to the area function. The area function then doesn't take height and base arguments but just a single Triangle object and accesses its height and base fields (t.height and t.base).

Sign up to request clarification or add additional context in comments.

1 Comment

In general it is better to divide by 2 (an Int) instead of multiplying with 0.5 (a Float64). Consider the case where height and base would be e.g. Float32s, then the former would result in a Float32, whereas the latter would unnecessarily promote to Float64. I would thus write area(t::Triangle) = t.height * t.base / 2.
3

Here are some benchmarks for computing this with:

  • map: map(area, triangles)
  • comprehension: `[area(triangle) for triangle in triangles]
  • broadcasting: area.(triangles)

using interpolation $ on the non-local global variable triangles (based on @DNF comment).

Definitions

using Pkg
Pkg.add("BenchmarkTools")
using BenchmarkTools


struct Triangle
    height::Float64
    base::Float64
end


function area(t::Triangle)
    0.5 * t.height * t.base
end


triangles = [Triangle(rand(), rand()) for _ in 1:1000000]

Results

julia> @benchmark map(area, $triangles)
BenchmarkTools.Trial: 
  memory estimate:  7.63 MiB
  allocs estimate:  3
  --------------
  minimum time:     1.168 ms (0.00% GC)
  median time:      2.510 ms (0.00% GC)
  mean time:        2.485 ms (10.00% GC)
  maximum time:     43.540 ms (91.62% GC)
  --------------
  samples:          2008
  evals/sample:     1
julia> @benchmark [area(triangle) for triangle in $triangles]
BenchmarkTools.Trial: 
  memory estimate:  7.63 MiB
  allocs estimate:  3
  --------------
  minimum time:     1.150 ms (0.00% GC)
  median time:      1.921 ms (0.00% GC)
  mean time:        2.327 ms (10.76% GC)
  maximum time:     45.883 ms (91.42% GC)
  --------------
  samples:          2144
  evals/sample:     1
julia> @benchmark area.($triangles)
BenchmarkTools.Trial: 
  memory estimate:  7.63 MiB
  allocs estimate:  2
  --------------
  minimum time:     1.165 ms (0.00% GC)
  median time:      1.224 ms (0.00% GC)
  mean time:        1.961 ms (10.13% GC)
  maximum time:     44.156 ms (89.33% GC)
  --------------
  samples:          2544
  evals/sample:     1

This would indicate that for this input size, the broadcasting method seems to be the fastest.

For different input size, relative timings may be different, so it is probably a good idea to benchmark it yourself for your use case

1 Comment

When benchmarking with BenchmarkTools.jl, always remember to interpolate global non-const variables. If I replace triangles with $triangles above, I get similar runtimes for all three, in which case I prefer broadcasting due to the syntax.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.