My guiding principle of Julia type stuff is the following: a version of a 
function is compiled for every type combination it is called with. Knowing 
that, all types become "crystallized" and propagate through, eliminating 
ifs and simplifying things.
However, its been my experience with compilers in many langauges that there 
are often limits to how much is decided at compile time, normally due to a 
heuristic tradeoff between compilation time and run time of the function.

For that reason, I try and use dispatch as I think it is cleaner than the 
ifs, even if they go away in the end. But I think people are needlessly 
afraid of using eltype because it seems like it'd be a run time check 
(Python people would definitely assume this).

Best example I know is:
https://github.com/IainNZ/GraphLayout.jl/blob/master/src/spring.jl#L42

if adj_matrix[i,j] != zero(eltype(adj_matrix)) || adj_matrix[j,i] != 
zero(eltype(adj_matrix))

which compiles down to

if adj_matrix[i,j] != 0.0 || adj_matrix[j,i] != 0.0

or 

if adj_matrix[i,j] != false || adj_matrix[j,i] != false

etc. I don't think people are used to this.

Anyway, thats my 1c.


On Tuesday, August 26, 2014 4:32:15 PM UTC-4, Stefan Karpinski wrote:
>
> It's kind of a toss up. These days, as you've noticed, they tend to 
> produce identical results, which is nice. Once upon a time, the type 
> parameter versions tended to produce better code, but that's not the case 
> mostly anymore. So whichever is easier and clearer, I guess? If you need 
> the type parameter for dispatch, then I guess I'd favor using it.
>
>
> On Tue, Aug 26, 2014 at 4:23 PM, Michael Grant <[email protected] 
> <javascript:>> wrote:
>
> I'm wondering if there are any general rules out there in Julia land 
> concerning when it is better to *parametrize *a function on a type versus 
> making a more generic function that uses typeof, eltype, as needed. I've 
> done some initial cursory examination, which you can see below if you're 
> interested. Based on this little bit of investigation, it does seem like 
> the code generation does effectively inline type calls when the types are 
> fully known, which means that in many cases, the code generated by 
> parameterized functions will be identical to their generic siblings. 
> Nevertheless, I did reveal some small differences, so I'm wondering if 
> there are cases where those differences are worth considering when making 
> implementation choices.
>
> Any thoughts, whether derived from experience, knowledge of Julia 
> internals, or wild-ass guessing are appreciated.
>
> Regards,
> Michael Grant
>
> -------
>
> One very simple example is the definition of eltype in number.jl:
> eltype(x::Number)=typeof(x)
> One could also implement the same functionality as follows:
> eltype{T<:Number}(x::T)=T
> Which one is better? Well, if I'm understanding this little test 
> properly---neither!
> eltype2{T<:Number}(x::T) = T
> eltype3(x::Number) = typeof(x)
> code_llvm(eltype2,(Complex{Float32},))
> code_llvm(eltype3,(Complex{Float32},))
> The output of code_llvm looks to be identical in both cases:
> define %jl_value_t* @"julia_eltype3;39061"(%Complex.4) {
> top:
>   ret %jl_value_t* inttoptr (i64 140332346166688 to %jl_value_t*), !dbg !
> 2208
> }
> Great! Now, let's try a slightly more complex example, inspired by the 
> sign function:
> sign2{T<:Real}(x::T) = ifelse(x < 0, oftype(T,-1), ifelse(x > 0, one(T), x
> ))
> sign3(x::Real) = ifelse(x < 0, oftype(x,-1), ifelse(x > 0, one(x), x))
> code_llvm(sign2,(Float64,))
> code_llvm(sign3,(Float64,))
> Once again, identical code:
> define double @"julia_sign3;39079"(double) {
> top:
>   %1 = fcmp uge double %0, 0.000000e+00, !dbg !2208
>   %2 = fcmp oge double %0, 0x43E0000000000000, !dbg !2208
>   %3 = fcmp ogt double %0, 0.000000e+00, !dbg !2208
>   %4 = or i1 %2, %3, !dbg !2208
>   %5 = select i1 %4, double 1.000000e+00, double %0, !dbg !2208
>   %6 = select i1 %1, double %5, double -1.000000e+00, !dbg !2208
>   ret double %6, !dbg !2208
> }
> Win! And yet, both of these examples rely on the fact that types are fully 
> resolved. What if they aren't? I can look a generic version of eltype3 by 
> typing
> code_llvm(eltype3,(Number,))
> but I can't do
> code_llvm(eltype2,(Number,))
> because no such definition exists. So suppose I create a new wrapper 
> function:
> eltype4(x::Number) = eltype2(x)
> so that I can see what kind of generic dispatch code will be generated for 
> eltype2 when necessary. Now, different code is indeed generated. Here's 
> the resulting code for eltype3:
> define %jl_value_t* @"julia_eltype3;39082"(%jl_value_t*, %jl_value_t**, 
> i32) {
> top:
>   %3 = alloca [3 x %jl_value_t*], align 8
>   %.sub = getelementptr inbounds [3 x %jl_value_t*]* %3, i64 0, i64 0
>   %4 = getelementptr [3 x %jl_value_t*]* %3, i64 0, i64 2, !dbg !2208
>   store %jl_value_t* inttoptr (i64 2 to %jl_value_t*), %jl_value_t** %.sub
> , align 8
>   %5 = load %jl_value_t*** @jl_pgcstack, align 8, !dbg !2208
>   %6 = getelementptr [3 x %jl_value_t*]* %3, i64 0, i64 1, !dbg !2208
>   %.c = bitcast %jl_value_t** %5 to %jl_value_t*, !dbg !2208
>   store %jl_value_t* %.c, %jl_value_t** %6, align 8, !dbg !2208
>   store %jl_value_t** %.sub, %jl_value_t*** @jl_pgcstack, align 8, !dbg !
> 2208
>   store %jl_value_t* null, %jl_value_t** %4, align 8
>   %7 = icmp eq i32 %2, 1, !dbg !2208
>   br i1 %7, label %ifcont, label %else, !dbg !2208
>
>
> else:                                             ; preds = %top
>   call void @jl_error(i8* getelementptr inbounds ([26 x i8]* @_j_str0, 
> i64 0, i64 0)), !dbg !2208
>   unreachable, !dbg !2208
>
>
> ifcont:                                           ; preds = %top
>   %8 = load %jl_value_t** %1, align 8, !dbg !2208
>   %9 = load %jl_value_t** inttoptr (i64 140332374695360 to %jl_value_t**), 
> align 64, !dbg !2209
>   %10 = getelementptr inbounds %jl_value_t* %9, i64 1, i32 0, !dbg !2209
>   %11 = load %jl_value_t** %10, align 8, !dbg !2209
>   %12 = bitcast %jl_value_t* %11 to %jl_value_t* (%jl_value_t*, %
> jl_value_t**, i32)*, !dbg !2209
>   store %jl_value_t* %8, %jl_value_t** %4, align 8, !dbg !2209
>   %13 = call %jl_value_t* %12(%jl_value_t* %9, %jl_value_t** %4, i32 1), !dbg 
> !2209
>   %14 = load %jl_value_t** %6, align 8, !dbg !2209
>   %15 = getelementptr inbounds %jl_value_t* %14, i64 0, i32 0, !dbg !2209
>   store %jl_value_t** %15, %jl_value_t*** @jl_pgcstack, align 8, !dbg !
> 2209
>   ret %jl_value_t* %13, !dbg !2209
> }
> The code for eltype4 is identical until we get to ifcont:
> ifcont:                                           ; preds = %top
>   %8 = load %jl_value_t** %1, align 8, !dbg !2208
>   store %jl_value_t* %8,<span style="color:#
>
> ...

Reply via email to