julia> @code_llvm x.rfhwil_ccall_interface.julia_set_dB_gain(1.0)
; @ C:\mypath\src\rfhwil_ccall_interface.jl:375 within `julia_set_dB_gain`
; Function Attrs: uwtable
define void @julia_julia_set_dB_gain_1762(double %0) #0 {
top:
; @ C:\mypath\src\rfhwil_ccall_interface.jl:376 within `julia_set_dB_gain`
; ┌ @ refvalue.jl:57 within `setindex!`
; │┌ @ Base.jl:38 within `setproperty!`
store i8 0, i8* inttoptr (i64 140710129657280 to i8*), align 64
; └└
; @ C:\mypath\src\rfhwil_ccall_interface.jl:377 within `julia_set_dB_gain`
; ┌ @ refvalue.jl:56 within `getindex`
; │┌ @ Base.jl:37 within `getproperty`
%1 = load atomic {}*, {}** inttoptr (i64 140710129665216 to {}**) unordered, align 64
%.not = icmp eq {}* %1, null
br i1 %.not, label %fail, label %pass
fail: ; preds = %top
call void @ijl_throw({}* inttoptr (i64 140710407392992 to {}*))
unreachable
pass: ; preds = %top
; └└
; ┌ @ Base.jl:38 within `setproperty!`
%2 = bitcast {}* %1 to i8*
%3 = getelementptr inbounds i8, i8* %2, i64 248
%4 = bitcast i8* %3 to double*
store double %0, double* %4, align 8
; └
; @ C:\mypath\src\rfhwil_ccall_interface.jl:378 within `julia_set_dB_gain`
ret void
}
Base.@ccallable function julia_set_dB_gain(dB_gain::Cdouble)::Cvoid
isCalculationValid[]=false # Parameter Set, so invalidate current answer
inputRef[].dB_gain = dB_gain
return nothing
end
f(x) = ccall("extern julia_set_dB_gain", llvmcall, Cvoid, (Cdouble,), x)
using BenchmarkTools
julia> @benchmark f(1.0)
BenchmarkTools.Trial: 10000 samples with 998 evaluations.
Range (min … max): 16.092 ns … 230.391 ns ┊ GC (min … max): 0.00% … 83.13%
Time (median): 17.305 ns ┊ GC (median): 0.00%
Time (mean ± σ): 18.390 ns ± 6.985 ns ┊ GC (mean ± σ): 0.48% ± 2.21%
▂ ▆█▄▃▁▅ ▁▃▂▁▁ ▁ ▂
█▅▁▄▅▆███████▅▅▇▆▃▃▄▁▁▃▃▄▄▅▄▄▅▆█▇▇▆▅▅▄▄██████▇▆▅▄▅███▇▇▆▅▄▁▃ █
16.1 ns Histogram: log(frequency) by time 26.1 ns <
Memory estimate: 16 bytes, allocs estimate: 1.
That surprised me, but it seems the argument passing is indeed the culprit…
It turns out @cfunction
behaves much better here.
julia> const isCalculationValid = Ref(false)
Base.RefValue{Bool}(false)
julia> const inputRef = Ref(1.0)
Base.RefValue{Float64}(1.0)
julia> Base.@cfunction function julia_set_dB_gain(dB_gain::Cdouble)::Cvoid
isCalculationValid[]=false # Parameter Set, so invalidate current answer
inputRef[] = dB_gain
return nothing
end Cvoid (Cdouble,)
Ptr{Nothing} @0x00007fbbd0a4cff0
julia> f(x) = ccall(Ptr{Cvoid}(0x00007fbbd0a4cff0), Cvoid, (Cdouble,), x)
f (generic function with 1 method)
julia> @benchmark f(1.0)
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
Range (min … max): 5.999 ns … 41.309 ns ┊ GC (min … max): 0.00% … 0.00%
Time (median): 6.000 ns ┊ GC (median): 0.00%
Time (mean ± σ): 6.049 ns ± 0.491 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
█ ▂ ▂ ▁
█▇█▅▄▄▄▃▃▁▃▃▃▁▁▄▁▁▃▁▁▃▃▃▁▁▁▄▁▃▄▁▁▃▁▁▃▄▁▄▅▆▁▄▁▁▄▁▄▄▃▁▄▃▁▁▃█ █
6 ns Histogram: log(frequency) by time 7.11 ns <
Memory estimate: 0 bytes, allocs estimate: 0.
So instead of using @ccallable
you likely want to lookup the function pointer using cfunction
at the beginning of your program and then use those as callable pointers.
HT @jameson
That is really cool how you did that. I was trying to figure out how to do that. We need to just star this as Julia gold.
So how do I make it not heap allocate? Do I need to make a structure for everything and pass by reference?
Is it a bug in Julia?
It’s a sub-optimality, partly since the whole create a shared library from Julia story is quite rough…
I think for now the @cfunction
hack should (while annoying) get you to zero allocations.
I did open up an issue `@ccallable` allocates for it's arguments · Issue #51894 · JuliaLang/julia · GitHub
How do I get at the cfunction call from my C function since I’m dealing with a compiled package? I was trying to not require a Julia install. Just the shared libraries. Do I need to shadow it in the shared package library somehow?
Are you deleting libjulia-compiler
? If not you have everything available.
You would do somthing like:
typedef void(*set_dB_gain)(double);
void main
// ... init Julia etc
jl_value_t *jl_ptr = nullptr;
JL_GC_PUSH1(&jl_ptr);
jl_ptr = jl_eval_string("Base.@cfunction(julia_set_dB_gain, Cvoid, (Cdouble,))");
set_dB_gain func = (set_dB_gain) jl_unbox_long(jl_ptr);
JL_GC_POP();
for ... {
func(1.0);
}
This is good. I found it in the manual as well.
It seems with this way, I need a full julia install. Or do I just need to scavenge the julia.h file for the few functions I call and put them in my own header?
You don’t need a full Julia install, but you do need libjulia
, libjulia-internal
, and libjulia-codegen
+ `julia.h|
I don’t understand why you don’t have that header, but maybe that’s a question for PackageCompiler…?
I started looking at all of the includes inside julia.h and it looks like a lot of additional stuff. My thought was, would it be easier to pass pointers as inputs with the @ccallable interface instead of floats for example? That might be easier than hacking the julia.h include.
I don’t understand why you need to hack the julia.h
file?
You should just include it and don’t change it?
If PackageCompiler doesn’t easily provide it, we should open an issue to change it.
To make complete for the example. I got a segmentation fault for the lines above because the jl_ptr always came back null. This was because I neglected to include another jl_eval_string("using myPackage")
where myPackage contains the julia_set_dB_gain function.
Also, to debug the segmentation fault, open up a julia session and test all of the jl_eval_string
lines for mistakes and typos.