Obtuya7
November 22, 2020, 5:46pm
1
I noticed cases where the pipe operator (e.g. |> ) is used over function composition (e.g. f(g(x))). I see composition used throughout the base code; so I assume that it’s the preferred convention.
Are there any underlying performance or algorithmic differences between the two? Or, is it only syntactic sugar?
This is just syntax. It shouldn’t make any difference in generated code.
1 Like
jules
November 22, 2020, 6:32pm
3
I think I’ve read that it’s not just syntax and affects stack traces etc. Might be wrong though
aaowens
November 22, 2020, 6:39pm
4
julia> test1(x) = exp(cos(log(x)))
test1 (generic function with 1 method)
julia> test2(x) = x |> log |> cos |> exp
test2 (generic function with 1 method)
julia> @code_typed test1(2.)
CodeInfo(
1 ─ %1 = invoke Main.log(_2::Float64)::Float64
│ %2 = invoke Main.cos(%1::Float64)::Float64
│ %3 = invoke Main.exp(%2::Float64)::Float64
└── return %3
) => Float64
julia> @code_typed test2(2.)
CodeInfo(
1 ─ %1 = Main.log::Core.Compiler.Const(log, false)
│ %2 = invoke %1(_2::Float64)::Float64
│ %3 = Main.cos::Core.Compiler.Const(cos, false)
│ %4 = invoke %3(%2::Float64)::Float64
│ %5 = Main.exp::Core.Compiler.Const(exp, false)
│ %6 = invoke %5(%4::Float64)::Float64
└── return %6
) => Float64
Piping does seem to make a longer stack trace, but if you make the substitution it looks exactly the same.
The LLVM looks about the same
julia> @code_llvm test1(2.)
; @ REPL[1]:1 within `test1'
define double @julia_test1_264(double) {
top:
%1 = call double @j_log_265(double %0)
%2 = call double @j_cos_266(double %1)
%3 = call double @j_exp_267(double %2)
ret double %3
}
julia> @code_llvm test2(2.)
; @ REPL[2]:1 within `test2'
define double @julia_test2_268(double) {
top:
; ┌ @ operators.jl:834 within `|>'
%1 = call double @j_log_269(double %0)
%2 = call double @j_cos_270(double %1)
%3 = call double @j_exp_271(double %2)
; └
ret double %3
}
7 Likes