Live data from Hacker News

Some fun with π in Julia

julialang.org

61–69 of 69 posts

Re: Some fun with π in Julia

#61
post #52
post #47

Earlier quoted context omitted.

When was the last time you used it? Module loading times improved significantly with precompilation in 0.4.

Yeah, it was 0.3. So that would address the importing problem, if the modules I'm importing use the precompilation system. How is string performance doing?

I also stopped using after 0.3 and tried it recently again to speed up some NLP code in Python3 and it hasn't been faster than Python3. Back then I didn't like the poor support for pyjulia, slow string processing, people using unicode symbols as variables, module import system, lack of libraries for more obscure NLP algos, and startup time. I know these aren't major issues for the core users of Julia, but these were my concerns.

The way I wrote the current code in Python was abusing sets and dicts a lot to take advantage of those fast data structs. Rewriting in Julia was fun because it was different and because multiple dispatch is fun.

However, it was roughly the same speed as Python. Ended up sprinkling some Cython on top of Python and resulted in 10x speedup. Didn't take much time to add types/pass pointers instead of strings to functions. I am not at all familiar with C++/Cython.

I think even if they speed strings/dicts up by a lot, there seem to be lots of breaking changes between releases so I wouldn't try it for something big.

I think if Julia is to succeed in the near future in the same way Python is successful for data science, it needs to be more usable for general tasks. Things like web servers, fast JSON parsing, maybe static binaries or easy parallelism. Basically, some more selling points. So far, for me personally Python is faster and easier to read for most of the things I write. At least given comparable amount of work.

Re: Some fun with π in Julia

#62
post #6

I have always been skeptical about 'scientific' languages. Why do you need a special language when any general language + some libraries will do? This is a good example of a feature that only really makes sense in a scientific language.

Numerical programming takes more expressive power than one might imagine. In most languages, numerical primitives like integers and floating-point numbers, and numerical operators like `+` and `[]` (array indexing), are very special and are endowed with enough magic to be usable. E.g. in C, `+` is too polymorphic to be defined as a function; in Python, `+` has special `__radd__` methods to (hackily) emulate multiple…

I like this quote from the 'motivation' link above:

"A lot of Julia's design fell directly out of making it easy to generate LLVM IR – in some ways, Julia is just a really great DSL for LLVM's JIT."

Excited to learn more

Re: Some fun with π in Julia

#63
post #52
post #47

Earlier quoted context omitted.

When was the last time you used it? Module loading times improved significantly with precompilation in 0.4.

Yeah, it was 0.3. So that would address the importing problem, if the modules I'm importing use the precompilation system. How is string performance doing?

String performance is better in 0.6. It is in alpha, so should get released fairly soon.

Re: Some fun with π in Julia

#64
post #50

Earlier quoted context omitted.

It would be interesting to see what happens if each language is compiled to the benchmark server, linked to the same BLAS and an expert implementation of the tests in each language was allowed; a scientifically valuable experiment. Just to remove all doubt about the relativities of performance :-) Wonderful. Insightful :)

You're right, using a doubly-recursive algorithm [1] for `fib` is a terribly naive and uncharacteristic way to write it in any language, including Julia. But it's a wonderful proxy for the cost of a function call. It's also quite scientific — there's an absolute truth for the correctness of an implementation. All languages must use a doubly-recursive scheme. It all depends on what you want to measure. The whole point…

The issue of primitive performance seems to be in the background of how algorithms are implemented, which BLAS is running, compilation to server architecture, etc. One might measure the performance of 'very specific language primitives' directly for those language primitives. Stripping out confounding factors feels fundamental.

Of course, such a benchmark it might not have the same marketing hue as claiming that Julia is 553 times faster than Matlab at parsing an integer, for example.

Re: Some fun with π in Julia

#65
post #43

Earlier quoted context omitted.

Julia startup time is 0.5 seconds on a very weak laptop. That's comparable to the JVM, which is nevertheless fairly popular. You may not want to write command line tools in Julia, but there's not much else that's a serious problem since process startup time is rarely that big of a performance issue. Disregarding JIT time, Julia is as fast as compiled languages – we benchmark against C and Fortran and don't grade on a…

When I last used it, the time to import even two modules was many seconds. I know that's JIT time, but that breaks the flow of developing with Julia like it's a dynamic language. And the JVM is popular, yes, but nobody would call it "extremely fast", particularly for interactive use, because of its startup time. The time to parse the data I needed was many minutes, slower than Python. Restarting a Jupyter kernel woul…

> The benchmarks are all for numerical computing, where the hard work is typically offloaded to a BLAS library anyway, and don't involve strings or dictionaries.

I built an in-memory relational query compiler in Julia which keeps up with Postgres on the Join Order Benchmark.

http://scattered-thoughts.net/blog/2016/10/11/a-practical-re...

Last I checked there were still some limitations around stack-allocation of types containing pointers, which is occasionally painful, but other than that it gave me all the tools I could possibly want.

https://github.com/JuliaLang/julia/pull/18632

The startup time is annoying, but I typically use Julia from Juno and I restart at most a couple of times per week. Ctrl-shift-enter recompiles the current module, which is usually all I want.

Re: Some fun with π in Julia

#66
post #62

Earlier quoted context omitted.

Numerical programming takes more expressive power than one might imagine. In most languages, numerical primitives like integers and floating-point numbers, and numerical operators like `+` and `[]` (array indexing), are very special and are endowed with enough magic to be usable. E.g. in C, `+` is too polymorphic to be defined as a function; in Python, `+` has special `__radd__` methods to (hackily) emulate multiple…

I like this quote from the 'motivation' link above: "A lot of Julia's design fell directly out of making it easy to generate LLVM IR – in some ways, Julia is just a really great DSL for LLVM's JIT." Excited to learn more

Julia is my language of choice for writing compilers.

Quasiquoting means that codegen is as easy as string interpolation is in other languages.

    quote 
      let
        $(index_inits...)
        $(results_inits...)
        if $(reduce((a,b) -> :($a && $b), true, index_checks))
          let 
            $(var_inits...) # declare vars local in here so they can't shadow relation names
            $body 
          end
        end
        tuple($(results...))
      end
    end
Great introspection into the inference and compilation pipeline, directly from the repl.

    julia> function double(xs)
             [2*x for x in xs]
           end
    double (generic function with 1 method)

    julia> double([1,2,3])
    3-element Array{Int64,1}:
     2
     4
     6

    julia> @code_warntype double([1,2,3])
    Variables:
      xs::Array{Int64,1}
      #s1::Int64
      #s2::Int64
      #s3::Int64
      x::Int64
      #s4::Int64

    Body:
      begin  # none, line 2:
          GenSym(1) = (Base.arraylen)(xs::Array{Int64,1})::Int64
          0: 
          GenSym(3) = (top(ccall))(:jl_alloc_array_1d,(top(apply_type))(Base.Array,Int64,1)::Type{Array{Int64,1}},(top(svec))(Base.Any,Base.Int)::SimpleVector,Array{Int64,1},0,GenSym(1),0)::Array{Int64,1}
          #s1 = 1
          #s2 = 1
          #s3 = 0
          unless (Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)) goto 2
          3: 
          #s3 = (Base.box)(Base.Int,(Base.add_int)(#s3::Int64,1))
          GenSym(9) = (Base.arrayref)(xs::Array{Int64,1},#s2::Int64)::Int64
          GenSym(10) = (Base.box)(Base.Int,(Base.add_int)(#s2::Int64,1))
          #s4 = 1
          GenSym(11) = GenSym(9)
          GenSym(12) = (Base.box)(Base.Int,(Base.add_int)(1,1))
          x = GenSym(11)
          #s4 = GenSym(12)
          GenSym(13) = GenSym(10)
          GenSym(14) = (Base.box)(Base.Int,(Base.add_int)(2,1))
          #s2 = GenSym(13)
          #s4 = GenSym(14)
          GenSym(4) = (Base.box)(Int64,(Base.mul_int)(2,x::Int64))
          $(Expr(:type_goto, 0, GenSym(4)))
          $(Expr(:boundscheck, false))
          (Base.arrayset)(GenSym(3),GenSym(4),#s1::Int64)::Array{Int64,1}
          $(Expr(:boundscheck, :(Main.pop)))
          #s1 = (Base.box)(Base.Int,(Base.add_int)(#s1::Int64,1))
          4: 
          unless (Base.box)(Base.Bool,(Base.not_int)((Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)))) goto 3
          2: 
          1: 
          return GenSym(3)
      end::Array{Int64,1}

    julia> @code_llvm double([1,2,3])

    define %jl_value_t* @julia_double_21481(%jl_value_t*, %jl_value_t**, i32) {
    top:
      %3 = alloca [4 x %jl_value_t*], align 8
      %.sub = getelementptr inbounds [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 0
      %4 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 2
      %5 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 3
      %6 = bitcast [4 x %jl_value_t*]* %3 to i64*
      store i64 4, i64* %6, align 8
      %7 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 1
      %8 = load i64, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
      %9 = bitcast %jl_value_t** %7 to i64*
      store i64 %8, i64* %9, align 8
      store %jl_value_t** %.sub, %jl_value_t*** @jl_pgcstack, align 8
      store %jl_value_t* null, %jl_value_t** %4, align 8
      store %jl_value_t* null, %jl_value_t** %5, align 8
      %10 = load %jl_value_t*, %jl_value_t** %1, align 8
      %11 = getelementptr inbounds %jl_value_t, %jl_value_t* %10, i64 1
      %12 = bitcast %jl_value_t* %11 to i64*
      %13 = load i64, i64* %12, align 8
      store %jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), %jl_value_t** %5, align 8
      %14 = call %jl_value_t* inttoptr (i64 140149385306688 to %jl_value_t* (%jl_value_t*, i64)*)(%jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), i64 inreg %13)
      store %jl_value_t* %14, %jl_value_t** %4, align 8
      %15 = icmp eq i64 %13, 0
      br i1 %15, label %L4, label %L1.preheader

    L1.preheader:                                     ; preds = %top
      %16 = load i64, i64* %12, align 8
      %17 = bitcast %jl_value_t* %10 to i64**
      %18 = bitcast %jl_value_t* %14 to i64**
      br label %L1

    L1:                                               ; preds = %idxend, %L1.preheader
      %"#s3.0" = phi i64 [ %22, %idxend ], [ 0, %L1.preheader ]
      %"#s2.0" = phi i64 [ %26, %idxend ], [ 1, %L1.preheader ]
      %19 = add i64 %"#s2.0", -1
      %20 = icmp ult i64 %19, %16
      br i1 %20, label %idxend, label %oob

    oob:                                              ; preds = %L1
      %21 = alloca i64, align 8
      store i64 %"#s2.0", i64* %21, align 8
      call void @jl_bounds_error_ints(%jl_value_t* %10, i64* nonnull %21, i64 1)
      unreachable

    idxend:                                           ; preds = %L1
      %22 = add i64 %"#s3.0", 1
      %23 = load i64*, i64** %17, align 8
      %24 = getelementptr i64, i64* %23, i64 %19
      %25 = load i64, i64* %24, align 8
      %26 = add i64 %"#s2.0", 1
      %27 = shl i64 %25, 1
      %28 = load i64*, i64** %18, align 8
      %29 = getelementptr i64, i64* %28, i64 %19
      store i64 %27, i64* %29, align 8
      %30 = icmp eq i64 %22, %13
      br i1 %30, label %L4.loopexit, label %L1

    L4.loopexit:                                      ; preds = %idxend
      br label %L4

    L4:                                               ; preds = %L4.loopexit, %top
      %31 = load i64, i64* %9, align 8
      store i64 %31, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
      ret %jl_value_t* %14
    }

    julia> @code_native double([1,2,3])
    L128:L159:L177:	.text
    	pushq	%rbp
    	movq	%rsp, %rbp
    	pushq	%r15
    	pushq	%r14
    	pushq	%rbx
    	subq	$40, %rsp
    	movabsq	$jl_alloc_array_1d, %rax
    	movabsq	$140140655870352, %rdi  # imm = 0x7F750A030190
    	movq	$4, -56(%rbp)
    	movabsq	$jl_pgcstack, %r15
    	movq	(%r15), %rcx
    	movq	%rcx, -48(%rbp)
    	leaq	-56(%rbp), %rcx
    	movq	%rcx, (%r15)
    	movq	$0, -40(%rbp)
    	movq	$0, -32(%rbp)
    	movq	(%rsi), %r14
    	movq	8(%r14), %rbx
    	movq	%rdi, -32(%rbp)
    	movq	%rbx, %rsi
    	callq	*%rax
    	movq	%rax, -40(%rbp)
    	cmpq	$0, %rbx
    	je	L159
    	xorl	%ecx, %ecx
    	movq	8(%r14), %rdx
    	nopw	%cs:(%rax,%rax)
    	cmpq	%rdx, %rcx
    	jae	L177
    	movq	(%r14), %rsi
    	movq	(%rsi,%rcx,8), %rsi
    	shlq	$1, %rsi
    	movq	(%rax), %rdi
    	movq	%rsi, (%rdi,%rcx,8)
    	incq	%rcx
    	cmpq	%rcx, %rbx
    	jne	L128
    	movq	-48(%rbp), %rcx
    	movq	%rcx, (%r15)
    	leaq	-24(%rbp), %rsp
    	popq	%rbx
    	popq	%r14
    	popq	%r15
    	popq	%rbp
    	retq
    	movq	%rsp, %rsi
    	addq	$-16, %rsi
    	movq	%rsi, %rsp
    	addq	$1, %rcx
    	movq	%rcx, (%rsi)
    	movabsq	$jl_bounds_error_ints, %rax
    	movl	$1, %edx
    	movq	%r14, %rdi
    	callq	*%rax
It catches type errors early, thanks to the typed multiple dispatch.

    julia> xs = []
    0-element Array{Any,1}

    julia> push!(xs, 42)
    1-element Array{Any,1}:
     42

    julia> push!(xs, "foo")
    2-element Array{Any,1}:
     42     
       "foo"

    julia> ys = Int64[]
    0-element Array{Int64,1}

    julia> push!(ys, 42)
    1-element Array{Int64,1}:
     42

    julia> push!(ys, "foo")
    ERROR: MethodError: `convert` has no method matching convert(::Type{Int64}, ::ASCIIString)
    This may have arisen from a call to the constructor Int64(...),
    since type constructors fall back to convert methods.
    Closest candidates are:
      call{T}(::Type{T}, ::Any)
      convert(::Type{Int64}, ::Int8)
      convert(::Type{Int64}, ::UInt8)
      ...
     in push! at ./array.jl:432
Plus, I only have to think in one language, but I can write sloppy dynamic heap-allocating-everywhere code in the compiler and with just a bit of thinking emit zero-allocation statically-dispatched code in the output.

Re: Some fun with π in Julia

#67

Earlier quoted context omitted.

We use whatever BLAS is linked to in a commonly available official distribution. Julia was one of the first to take this seriously and bundle a high performance BLAS as the default - and I think more projects are following our lead and doing the same. Also, only one benchmark actually uses BLAS. As for co-ordination on Julia posts - there is none. We submit all our blog posts to HN, and while some do reach the front…

It would be interesting to see what happens if each language is compiled to the benchmark server, linked to the same BLAS and an expert implementation of the tests in each language was allowed; a scientifically valuable experiment. Just to remove all doubt about the relativities of performance :-) Wonderful. Insightful :)

Someone did kind of the opposite (same language, different backends) with Rcpp and compiler flags (-O3 and the like): https://github.com/jackwasey/optimization-comparison

Re: Some fun with π in Julia

#69
post #32

Earlier quoted context omitted.

> I like Julia because it's extremely fast ...compared to Python, under some circumstances, disregarding startup time. Don't oversell it. Don't confuse what Julia aspires to be with what it is , or you'll just turn people off when they feel they've been misled. There are extremely fast ahead-of-time-compiled languages that would have finished their computation while Julia is still JIT-ing its kernel.

Julia startup time is 0.5 seconds on a very weak laptop. That's comparable to the JVM, which is nevertheless fairly popular. You may not want to write command line tools in Julia, but there's not much else that's a serious problem since process startup time is rarely that big of a performance issue. Disregarding JIT time, Julia is as fast as compiled languages – we benchmark against C and Fortran and don't grade on a…

I really like the language, and I've recently used Julia in a scientific computing project, but the experience leaves much to be desired (and I'm certain it will be improved).

Startup time isn't the biggest problem, although it is nowhere near "comparable to the JVM". The JVM will start cold, load a Hello World application from a JAR, run it and shut down in 60-80ms. A Julia Hello World app would take at least a second, even when precompiled into a native executable.

The biggest problem, IMO, is packaging and deployment. I used BuildExecutable, which works OK most of the time (although startup times are still slow), but it results in so many shared libraries, with no easy way to cull them. Without BuildExecutable, I couldn't even find documentation on how to deploy a Julia app as a self-contained program (even not precompiled, but in a way that doesn't require Julia to be installed on the user's machine).

Post reply on HN