[!Warning Pendantic!] Yes but isn’t there a caveat that if you leave out types from structs and data objects altogether it will lead to a degradation in performance where the compiler starts substituting Any
? For example this:
struct MyTypedStruct{T}
x::T
end
Compared with this:
struct MyUnTypedStruct
x
end
The second implementation will give Any
warnings when passed into @code_warntype
:
function fun(x)
return x.x^2 + 2
end
@code_warntype fun(MyTypedStruct(3.0))
Creates good code:
Variables
#self#::Core.Compiler.Const(fun, false)
x::MyTypedStruct{Float64}
Body::Float64
1 ─ %1 = Base.getproperty(x, :x)::Float64
│ %2 = Core.apply_type(Base.Val, 2)::Core.Compiler.Const(Val{2}, false)
│ %3 = (%2)()::Core.Compiler.Const(Val{2}(), false)
│ %4 = Base.literal_pow(Main.:^, %1, %3)::Float64
│ %5 = (%4 + 2)::Float64
└── return %5
@code_warntype fun(MyUnTypedStruct(3.0))
Creates code with Any
type identifiers that will lead to performance penalties:
Variables
#self#::Core.Compiler.Const(fun, false)
x::MyUnTypedStruct
Body::Any
1 ─ %1 = Base.getproperty(x, :x)::Any
│ %2 = Core.apply_type(Base.Val, 2)::Core.Compiler.Const(Val{2}, false)
│ %3 = (%2)()::Core.Compiler.Const(Val{2}(), false)
│ %4 = Base.literal_pow(Main.:^, %1, %3)::Any
│ %5 = (%4 + 2)::Any
└── return %5
To beginner programmers that may be reading, this might not look like much but when you use @code_llvm
, the well defined type creates really good efficient small code size:
@code_llvm fun(MyTypedStruct(3.0))
; @ REPL[3]:2 within `fun'
define double @julia_fun_17308([1 x double] addrspace(11)* nocapture nonnull readonly dereferenceable(8)) {
top:
; ┌ @ Base.jl:33 within `getproperty'
%1 = getelementptr inbounds [1 x double], [1 x double] addrspace(11)* %0, i64 0, i64 0
; └
; ┌ @ intfuncs.jl:261 within `literal_pow'
; │┌ @ float.jl:405 within `*'
%2 = load double, double addrspace(11)* %1, align 8
%3 = fmul double %2, %2
; └└
; ┌ @ promotion.jl:311 within `+' @ float.jl:401
%4 = fadd double %3, 2.000000e+00
; └
ret double %4
}
whereas untyped code creates a horribly inefficient monstrosity:
@code_llvm fun(MyUnTypedStruct(3.0))
; @ REPL[3]:2 within `fun'
define nonnull %jl_value_t addrspace(10)* @japi1_fun_17309(%jl_value_t addrspace(10)*, %jl_value_t addrspace(10)**, i32) #0 {
top:
%3 = alloca %jl_value_t addrspace(10)*, i32 3
%gcframe = alloca %jl_value_t addrspace(10)*, i32 3, align 16
%4 = bitcast %jl_value_t addrspace(10)** %gcframe to i8*
call void @llvm.memset.p0i8.i32(i8* align 16 %4, i8 0, i32 24, i1 false)
%5 = alloca %jl_value_t addrspace(10)**, align 8
store volatile %jl_value_t addrspace(10)** %1, %jl_value_t addrspace(10)*** %5, align 8
%thread_ptr = call i8* asm "movq %fs:0, $0", "=r"()
%ptls_i8 = getelementptr i8, i8* %thread_ptr, i64 -15712
%ptls = bitcast i8* %ptls_i8 to %jl_value_t***
%6 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 0
%7 = bitcast %jl_value_t addrspace(10)** %6 to i64*
store i64 4, i64* %7
%8 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
%9 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
%10 = bitcast %jl_value_t addrspace(10)** %9 to %jl_value_t***
%11 = load %jl_value_t**, %jl_value_t*** %8
store %jl_value_t** %11, %jl_value_t*** %10
%12 = bitcast %jl_value_t*** %8 to %jl_value_t addrspace(10)***
store %jl_value_t addrspace(10)** %gcframe, %jl_value_t addrspace(10)*** %12
%13 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %1, align 8
; ┌ @ Base.jl:33 within `getproperty'
%14 = addrspacecast %jl_value_t addrspace(10)* %13 to %jl_value_t addrspace(11)*
%15 = bitcast %jl_value_t addrspace(11)* %14 to %jl_value_t addrspace(10)* addrspace(11)*
%16 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)* addrspace(11)* %15, align 8
; └
%17 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 0
store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357750891408 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %17
%18 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 1
store %jl_value_t addrspace(10)* %16, %jl_value_t addrspace(10)** %18
%19 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 2
store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357711681584 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %19
%20 = call nonnull %jl_value_t addrspace(10)* @jl_apply_generic(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357755649744 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %3, i32 3)
%21 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 2
store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %21
%22 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 0
store %jl_value_t addrspace(10)* %20, %jl_value_t addrspace(10)** %22
%23 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %3, i32 1
store %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357616279712 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %23
%24 = call nonnull %jl_value_t addrspace(10)* @jl_apply_generic(%jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140357742243008 to %jl_value_t*) to %jl_value_t addrspace(10)*), %jl_value_t addrspace(10)** %3, i32 2)
%25 = getelementptr %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %gcframe, i32 1
%26 = load %jl_value_t addrspace(10)*, %jl_value_t addrspace(10)** %25
%27 = getelementptr %jl_value_t**, %jl_value_t*** %ptls, i32 0
%28 = bitcast %jl_value_t*** %27 to %jl_value_t addrspace(10)**
store %jl_value_t addrspace(10)* %26, %jl_value_t addrspace(10)** %28
ret %jl_value_t addrspace(10)* %24
}
One of Julia’s selling points is performance and another is the ability to write code as if you were doing so using a static programming language with all the benefits of a dynamic language so it shouldn’t come as a surprise that people want to use the type system and get all the performance benefits. My personal feeling is that there’s nothing wrong with using types it constrains the code your intent and if all your Julia code is typed you avoid performance penalties like the one described above.