Build_function performance issues

When using functions generated with build_function with expression = Val{false}, I’m getting performance issues compared to manually written functions. Am I using build_function incorrectly or are there some additional settings I should be using?

using Symbolics

@syms x y z a b c

expr = a*x^2 + b*y + c - z

# build_function generated function with expression = Val{true}
clipboard(build_function(expr, x, y, z, a, b, c, expression = Val{true}))
# Paste function code and give function name
function build_fun_valtrue(x, y, z, a, b, c)
    (+)((+)((+)(c, (*)(-1, z)), (*)(b, y)), (*)(a, (^)(x, 2)))
end

# build_function generated function with expression = Val{false}
build_fun_valfalse = build_function(expr, x, y, z, a, b, c, expression = Val{false})

#manually written function
function manual_fun(x,y,z,a,b,c)
    return a*x^2 + b*y + c - z
end

# Running build_fun_valfalse() takes much longer and allocates memory
@time for _ in 1:1e6 build_fun_valtrue(3,2,1,1,2,3) end
@time for _ in 1:1e6 build_fun_valfalse(3,2,1,1,2,3) end
@time for _ in 1:1e6 manual_fun(3,2,1,1,2,3) end
# When running the tests again however, it will no longer allocate memory and
# take less time, but still take much longer to evaluate than the other functions

Using the returned function code from build_function(expression = Val{true}) gives near identical results to using the manually written function, but using the build_function(expression = Val{false}) functions take ~3 orders of magnitude longer to execute 1e6 times.

This is just a benchmarking artifact. Generic functions are always set as const by defualt. Variables in Julia are generally not const. So only one thing here is not a constant global. So this is simply an artifact of benchmarking in the global scope that would go away in any function scope. To see this, just mark the one as const:

using Symbolics

@syms x y z a b c

expr = a*x^2 + b*y + c - z

# build_function generated function with expression = Val{true}
clipboard(build_function(expr, x, y, z, a, b, c, expression = Val{true}))
# Paste function code and give function name
function build_fun_valtrue(x, y, z, a, b, c)
    (+)((+)((+)(c, (*)(-1, z)), (*)(b, y)), (*)(a, (^)(x, 2)))
end

# build_function generated function with expression = Val{false}
const build_fun_valfalse = build_function(expr, x, y, z, a, b, c, expression = Val{false})

#manually written function
function manual_fun(x,y,z,a,b,c)
    return a*x^2 + b*y + c - z
end

# Running build_fun_valfalse() takes much longer and allocates memory
@time for _ in 1:1e6 build_fun_valtrue(3,2,1,1,2,3) end
@time for _ in 1:1e6 build_fun_valfalse(3,2,1,1,2,3) end
@time for _ in 1:1e6 manual_fun(3,2,1,1,2,3) end

and now it goes away. Or use @btime with $.

1 Like