What is the benefit of using Julia than java and python

Hi I am just starting to use Julia and need to do a small presentation about this topic. I read some introduction and watch tutorial about this language but still did not have a clue. Hope to have some advice here. Thanks.

This question is too broad without any context. Every language has its own sets of strengths and weaknesses. You may want to elaborate your use cases and this friendly community can help you rationalize whether Julia is a good fit or not.

Sorry about the confusing description. I am try to write a parser for the compressed data. I read this from the Julia home page “Julia programs are organized around multiple dispatch, which allows built-in and user-defined functions to be overloaded for different combinations of argument types.” I can not really understand this part. Thank you for your patient reply.

1 Like
julia> f(x,y) = "Hi!"
f (generic function with 1 method)

julia> f(x::Int,y) = "Hello!"
f (generic function with 2 methods)

julia> f(x::Real,y::Integer...) = fill(x, y...)
f (generic function with 3 methods)

julia> f(y::Int,x::Real) = f(x,y)
f (generic function with 4 methods)

julia> f("Howdy", exp)
"Hi!"

julia> f(4, cos)
"Hello!"

julia> f(3, 2.7)
3-element Array{Float64,1}:
 2.7
 2.7
 2.7

julia> f(2.7,4)
4-element Array{Float64,1}:
 2.7
 2.7
 2.7
 2.7

julia> f(9.2, 2, 6)
2×6 Array{Float64,2}:
 9.2  9.2  9.2  9.2  9.2  9.2
 9.2  9.2  9.2  9.2  9.2  9.2

This example looks a little silly, but multiple dispatch is extremely powerful.
It lets you make convenient APIs, by matching function names and operators to the ideas instead of the specific operations.
It also lets code be extremely fast. C++ and Fortran are fast because because the compiler gets to make optimized code for specific data types. By simply compiling a separate version of a function depending on input types, Julia gets those same benefits while remaining dynamic.
Take a look at this function:

julia> function g(x, y, z)
           output = zero(promote_type(eltype.((x,y,z))...))
           for yᵢ ∈ y
               output += x*yᵢ + z
           end
           output
       end

See what happens when we call it with two Float64s and an Int32:

julia> g(2.3, 1.2, Int32(3))
5.76

julia> @code_warntype g(2.3, 1.2, Int32(3))
Variables:
  #self# <optimized out>
  x::Float64
  y::Float64
  z::Int32
  yᵢ::Float64
  #temp#::Bool
  output::Float64

Body:
  begin 
      output::Float64 = (Base.sitofp)(Float64, 0)::Float64 # line 3:
      #temp#::Bool = false
      4: 
      unless (Base.not_int)(#temp#::Bool)::Bool goto 13
      SSAValue(2) = y::Float64
      yᵢ::Float64 = SSAValue(2)
      #temp#::Bool = true # line 4:
      output::Float64 = (Base.add_float)(output::Float64, (Base.add_float)((Base.mul_float)(x::Float64, yᵢ::Float64)::Float64, (Base.sitofp)(Float64, z::Int32)::Float64)::Float64)::Float64
      11: 
      goto 4
      13:  # line 6:
      return output::Float64
  end::Float64

Julia also realizes things like the length of a Float64 is 1, so the for loop actually disappears::

@code_llvm g(2.3, 1.2, Int32(3))

define double @julia_g_63073(double, double, i32) #0 !dbg !5 {
top:
  %3 = fmul double %0, %1
  %4 = sitofp i32 %2 to double
  %5 = fadd double %3, %4
  %6 = fadd double %5, 0.000000e+00
  ret double %6
}

This turns into almost the exact same llvm as if we were explicit about types for that exact combination, as we would be in Fortran:

julia> gcpp(x::Float64, y::Float64, z::Int32) = x*y + z
          
gcpp (generic function with 1 method)

julia> @code_llvm gcpp(2.3, 1.2, Int32(3))

define double @julia_gcpp_63288(double, double, i32) #0 !dbg !5 {
top:
  %3 = fmul double %0, %1
  %4 = sitofp i32 %2 to double
  %5 = fadd double %3, %4
  ret double %5
}

Which is almost the same code. We’re just left with an extra adding 0.0.

We can mix up the input types, and it will just keep creating optimized code for that specific version. Here, I’m calling g with an unsigned 32 bit integer z, array of two Float32s y, and the irrational pi as z:

julia> g(Cuint(9), [1.2f0 -4.2f0], π)
-20.716812f0

julia> @code_warntype g(Cuint(9), [1.2f0 -4.2f0], π)
Variables:
  #self# <optimized out>
  x::UInt32
  y::Array{Float32,2}
  z <optimized out>
  yᵢ::Float32
  #temp#::Int64
  output::Float32

Body:
  begin 
      output::Float32 = (Base.sitofp)(Float32, 0)::Float32 # line 3:
      #temp#::Int64 = 1
      4: 
      unless (Base.not_int)((#temp#::Int64 === (Base.add_int)((Base.arraylen)(y::Array{Float32,2})::Int64, 1)::Int64)::Bool)::Bool goto 14
      SSAValue(2) = (Base.arrayref)(y::Array{Float32,2}, #temp#::Int64)::Float32
      SSAValue(3) = (Base.add_int)(#temp#::Int64, 1)::Int64
      yᵢ::Float32 = SSAValue(2)
      #temp#::Int64 = SSAValue(3) # line 4:
      output::Float32 = (Base.add_float)(output::Float32, (Base.add_float)((Base.mul_float)((Base.uitofp)(Float32, x::UInt32)::Float32, yᵢ::Float32)::Float32, 3.1415927f0)::Float32)::Float32
      12: 
      goto 4
      14:  # line 6:
      return output::Float32
  end::Float32

This is way easier than always explicitly listing types!
This really starts getting cool when you start considering tools like:

4 Likes

Isn’t adding 0.0 a little strange here?

This is an unfortunate combination of circumstances.

5 Likes

Julia is awesome for scientific computing! Java and Python, much less so. Compared to Python and Java, some of things you get in Julia are:

  1. Productivity in code development because of the easier syntax and because you don’t need to specify types of variables (Python offers that but not Java)
  2. Native C-like speed of programs because of type inference and specialized code compilation (Java offers native speed but not Python)
  3. Code will work for any input type that “makes sense” (thanks to multiple dispatch) unless you restrict it, so less coding actually gives you more features in Julia! This allows generic Julia functions to work for arbitrary precision inputs, forward differentiation, interval arithmetic among many other cool features.
  4. Mathematics-friendly syntax
  5. Straightforward parallel programming and GPU support
  6. Macros and generated functions, which give a ton of cool features such as: domain specific languages like JuMP, and fast static arrays using StaticArrays.jl.
  7. A nice package manager
  8. Oh and a really cool differential equations package!

I also find it really easy to optimize my code reducing memory allocations and using tricks like @inbounds which give a nice easy boost to the speed of my programs. On the flip side, if you are using some tools from other languages, you may not find their equivalent yet in Julia (depends on the field) because the language is still young so the ecosystem is still developing, but in certain fields it is already somewhat mature. But on the double flip side, you have cool tools like ccall for C and Fortran, PyCall.jl for Python, Cxx.jl for C++, RCall.jl for R, JavaCall.jl for Java, and MATLAB.jl which let you use your favourite tools from any of these languages in the middle of your Julia code! Mind-blowing right!?

That’s from my limited experience in Java, Python and Julia.

6 Likes

Yeah, I’m a little surprised that wasn’t optimized away. I thought LLVM may get rid of it later, but the adding zero version was actually slower (roughly 3ns vs 2ns).
Maybe wrapping the function body in @fastmath begin end would help.

EDIT:


function gfm(x, y, z)
    @fastmath begin
        output = zero(promote_type(eltype.((x,y,z))...))
        for yᵢ ∈ y
            output += x*yᵢ + z
        end
    end
    output
end

function g(x, y, z)
    output = zero(promote_type(eltype.((x,y,z))...))
    for yᵢ ∈ y
        output += x*yᵢ + z
    end
    output
end

Which yields:

julia> gfm(2.3, 1.2, Int32(3))
5.76

julia> @code_llvm gfm(2.3, 1.2, Int32(3))

define double @julia_gfm_62867(double, double, i32) #0 !dbg !5 {
top:
  %3 = sitofp i32 %2 to double
  %4 = fmul fast double %1, %0
  %5 = fadd fast double %3, %4
  ret double %5
}

julia> @code_llvm g(2.3, 1.2, Int32(3))

define double @julia_g_62925(double, double, i32) #0 !dbg !5 {
top:
  %3 = fmul double %0, %1
  %4 = sitofp i32 %2 to double
  %5 = fadd double %3, %4
  %6 = fadd double %5, 0.000000e+00
  ret double %6
}

Yielding almost the same code:

julia> gexplicit(x::Float64, y::Float64, z::Int32) = x*y + z
gexplicit (generic function with 1 method)

julia> @code_llvm gexplicit(2.3, 1.2, Int32(3))

define double @julia_gexplicit_63247(double, double, i32) #0 !dbg !5 {
top:
  %3 = fmul double %0, %1
  %4 = sitofp i32 %2 to double
  %5 = fadd double %3, %4
  ret double %5
}

Unfortunately – I suspect it’s because of the the fast double operations – it is the slowest version of the three, despite being almost identical to the fastest:

julia> @benchmark gfm(2.3, 1.2, Int32(3))
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     2.781 ns (0.00% GC)
  median time:      3.718 ns (0.00% GC)
  mean time:        3.626 ns (0.00% GC)
  maximum time:     23.280 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> 

julia> @benchmark g(2.3, 1.2, Int32(3))
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     3.210 ns (0.00% GC)
  median time:      3.211 ns (0.00% GC)
  mean time:        3.484 ns (0.00% GC)
  maximum time:     19.007 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

julia> @benchmark gexplicit(2.3, 1.2, Int32(3))
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.750 ns (0.00% GC)
  median time:      2.142 ns (0.00% GC)
  mean time:        2.223 ns (0.00% GC)
  maximum time:     20.829 ns (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000

Can we get the more aggressive optimizations of @fastmath, without the special “fast” functions?
Some meta compiler hint, perhaps?
https://docs.julialang.org/en/latest/devdocs/meta/

EDIT:
But I seem to have had a misunderstanding. Skimming over this:
https://github.com/JuliaLang/julia/blob/master/base/fastmath.jl
It doesn’t look like it’s actually doing anything other than substituting the expressions for the “fast” versions?