3 ms·
> when `foo`'s argument is not easily known at compile time? There's no such thing. In Julia, all types are computed at compile time. The catch is types such
by thisrod 6y ago
> when `foo`'s argument is not easily known at compile time?
There's no such thing. In Julia, all types are computed at compile time.
The catch is types such as `Union{Int, Float64}`. If fwrdt returns that, the compiler will generate a specialized method for `foo(::Union{Int,Float64})`. But `Union{Int,Float64} isa Int` can not be evaluated at compile time, so that specialized method will have run time type checks.
Julia provides some static analysis tools to detect this, and help you locate the `1` in fwrdt which should be a `1.0`.
- staticfloat 6y agoWhile it's true that all code is typed (e.g. the compiler knows what types to expect at different points throughout the program), it's completely possible to have essentially "worthless" types computed. E.g. if I have the following: foo(x::Int) = x + 1 foo(x::Float64) = x + 2 foo(x::String) = "$(x) $(x)" function eval_user_input() user_input = readline(stdin) user_obj = eval(Meta.parse(user_input)) return foo(user_obj) end Then clearly there is no way the compiler can know what the type of `user_obj` is, however it will do its best to infer the entire function. We can use `@code_warntype` to quickly and easily find places where Julia's type inference gives a suboptimal result (which is often a source of performance issues, since there will be runtime overhead in those cases). julia> @code_warntype eval_user_input() Variables #self#::Core.Compiler.Const(eval_user_input, false) user_input::String user_obj::Any Body::Union{Float64, Int64, String} 1 ─ (user_input = Main.readline(Main.stdin)) │ %2 = Base.Meta.parse::Core.Compiler.Const(Base.Meta.parse, false) │ %3 = (%2)(user_input)::Any │ (user_obj = Main.eval(%3)) │ %5 = Main.foo(user_obj)::Union{Float64, Int64, String} └── return %5 You can see that `user_obj` is annotated as type `Any`, and that invoking `foo()` is said to itself return a type of `Union{Float64, Int64, String}`, which is to say, it has no idea which `foo` it's going to call ahead of time. I will note that in the REPL, all the suboptimal stuff is highlighted in red, making this a very nice debugging tool for immediately finding poorly-inferred sections of code. If we ask the compiler to run this code through its optimization passes as well by passing the `optimize=true` flag to `@code_warntype`, we can even see how the compiler tries to inline `foo` by turning the call to `foo()` into a series of `isa()` conditionals, checking the type of the return from `eval()`. I'm pasting the relevant section here, for the full example you can see the screenshot linked below, showcasing the red highlighting as well: 10 ┄ %20 = φ (#8 => %12, #9 => %18)::String │ %21 = Base.Meta.parse::Core.Compiler.Const(Base.Meta.parse, false) │ %22 = invoke Base.Meta.:(var"#parse#4")(true::Bool, true::Bool, %21::typeof(Base.Meta.parse), %20::String)::Any │ %23 = Main.eval(%22)::Any │ %24 = (isa)(%23, String)::Bool └─── goto #12 if not %24 11 ─ %26 = π (%23, String) │ %27 = invoke Base.string(%26::String, " "::String, %26::Vararg{String,N} where N)::String └─── goto #17 12 ─ %29 = (isa)(%23, Float64)::Bool └─── goto #14 if not %29 13 ─ %31 = π (%23, Float64) │ %32 = Base.sitofp(Float64, 2)::Float64 │ %33 = Base.add_float(%31, %32)::Float64 └─── goto #17 14 ─ %35 = (isa)(%23, Int64)::Bool └─── goto #16 if not %35 15 ─ %37 = π (%23, Int64) │ %38 = Base.add_int(%37, 1)::Int64 └─── goto #17 [0] https://i.imgur.com/DZPWUvi.png https://i.imgur.com/DZPWUvi.png