JuliaRheology / RHEOS.jl

RHEOS - Open Source Rheology data analysis software
MIT License
39 stars 9 forks source link

Evaluate use of cfunction closures instead of functionwrappers #159

Open moustachio-belvedere opened 1 year ago

moustachio-belvedere commented 1 year ago

I spent some time investigating today to see if there was a way of getting exactly what we currently do with functionwrappers, but with cfunctions which are part of base Julia, so we would have a backup option as FunctionWrappers is not heavily maintained.

More work is needed but initial results look promising. For full code see this file in RheoBenchAndTest.

As an example, we can convert

function DM1(p::Array{Symbol}, G::Expr)
    unpack_expr = Meta.parse(string(join(string.(p), ","), ",=params"))
    return(
    (@eval ((t, params) -> begin $unpack_expr; $G; end))
    |> FunctionWrapper{Float64, Tuple{Float64,Vector{Float64}}})
end

to

function DM3(p::Array{Symbol}, G::Expr)
    unpack_expr = Meta.parse(string(join(string.(p), ","), ",=params"))
    anonf = @eval ((t, params) -> begin $unpack_expr; $G; end)
    @cfunction(
      $anonf,
      Float64,
      (Float64, Vector{Float64}))
end

Results for cfunction call are consistently better than FunctionWrappers. However, the overhead for FunctionWrappers is probably only per-function-call so the difference is probably negligible when computing on array versions of the moduli.

A potential issue is that their usage is not quite the same hence more investigation is required on more RHEOS-like use cases.

@akabla @alebonfanti

moustachio-belvedere commented 1 year ago

I've added some more investigations to the file linked above in RheoBenchAndTest.

Looks like it is feasible to yield functions with the same interface as functionwrappers so this could be a back up option if ever needed in future. I don't think there's anything to be done now though so I'll close this issue.

akabla commented 1 year ago

That's really nice, Louis! Not the usual context to use cfunction, but that seems to work very well as a replacement for the function wrappers. Did you see this used somewhere? [edit: in fact, it looks like this is how FunctionWrappers work...]

There are significant performance improvements too on the test script. I added another cfunction wrapper (DM5) that appears to work fine using the standard Rheos calls, and is very fast indeed. No world age problem when created and called from a function.

This is definitely something to explore further. I don't see reasons to use FunctionWrappers if all can be done with base Julia, with a possible performance boost. Might be good to test how to work with arrays. Should be possible too.

akabla commented 1 year ago

I briefly tested cfunction on time arrays. Works fine too. Performance is good, although not too different from function wrappers.

akabla commented 1 year ago

These are code examples that seem to deliver what we need:

function DM5(p::Array{Symbol}, G::Expr)
    unpack_expr = Meta.parse(string(join(string.(p), ","), ",=params"))
    anonf = @eval ((t, params) -> begin $unpack_expr; $G; end)
    (t, params_numbers) -> ccall( @cfunction(  $anonf,  Float64, (Float64, Vector{Float64})).ptr , Float64, (Float64, Vector{Float64}), t, params_numbers)
end
function DM5v(p::Array{Symbol}, G::Expr)
    unpack_expr = Meta.parse(string(join(string.(p), ","), ",=params"))
    anonf = @eval ((t, params) -> begin $unpack_expr; @. $G; end)
    (t, params_numbers) -> ccall( @cfunction(  $anonf,  Vector{Float64}, (Vector{Float64}, Vector{Float64})).ptr , Vector{Float64}, (Vector{Float64}, Vector{Float64}), t, params_numbers)
end