3 ms·
While 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 ha
by staticfloat 6y ago
While 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