Question - for loop - variable assignments

Hi Community

I’m new to Julia (startet today), so if this question does not fit here, let me know.

My first idea was to compare Julias speed for simple calculus with other languages i know (its in this particular area much faster than Python, R and AutoIT!)

So my Julia attempt looks like this:

x = 0
for j in 1:8
    @time begin
        for i in 1:(j*10000000)
            x = i * i
            if i == (j*10000000)
                print(x, "\n")
            end
        end
        print("Time needed for " * string(j) * " * 10^7 calculations:")
    end
end
print(x)

You see at the end of the algorithm that I print the variable x.
The output tells me that x is equal to 0, that means x is just assigned within the loop.
How do i have to change my code, so that x is globaly changed?

If you’d like to se my implementations in Python, R and AutoIT, let me know.

Do you have other suggestions for Speed-Comparison-Implementations to test various Big-O notations?

I’m sure you’ll get quite a few tips from the benchmarking experts on here, but I think some of the key points are:

  1. Don’t benchmark in global scope
    Code execution in global scope slows Julia down, so if you want to benchmark stuff, be sure to wrap it in a function and time function execution. Coincidentally this will also solve your issue of x not being updated, which indeed is a scoping issues (loops introduce local scopes in Julia).

  2. Use BenchmarkTools and the @btime macro to benchmark
    If you @time a function call, the first call to this function will incur a precompilation cost as Julia compiles a version (method) of the function for the type of input arguments used. Further, there is always some noise in any function execution to do with e.g. garbage collection, so ideally you want to time the function multiple time to figure out representative runtimes. The @btime macro from the BenchmarkTools package does this for you by working out how long a function execution takes and then executing the function many times over (more often for quicker functions) and reporting the minimum runtime.

With that said there is also a wider issue in these very simplistic benchmarks, which by and large measure either compiler performance (i.e. does the compiler happen to know a neat trick to get around the calculation and just a constant, which happens for example when summing integers) or performance of an underlying linear algebra package (OpenBLAS or MKL). Because of this the insights from these benchmarks often have limited value for working out whether Julia is “fast” compared to other languages.

3 Likes

The easiest way is to put the whole thing in a function and only use the @btime macro on functions. To be fast, things need to be in a function in Julia (which would also fix the unintuitive scoping issue you are seeing).

Alternatively, for exploring that sort of code consider Jupyter which will behave the way you expect (but, similarly, the speed can’t increase unless it is in a function.

1 Like

I’d recommend reading this: Scope of Variables · The Julia Language

Long story short, using something like a for loop introduces a local scope. This means that references to the variable x default to only living inside that loop. If you want your changes to x to be reflected in the global scope, then you need to mark x as global, i.e.

x = 0
for j in 1:8
    @time begin
        for i in 1:(j*10000000)
            global x # <---------- This is necessary for what you're doing
            x = i * i
            if i == (j*10000000)
                print(x, "\n")
            end
        end
        print("Time needed for " * string(j) * " * 10^7 calculations:")
    end
end
print(x)

With all of that said, it’s best to just not do things like this in the global scope.

2 Likes

If you are just starting, then I strongly suggest starting in Jupyter notebooks. And never testing performance without wrapping things in a function (Jupyter or otherwise). It isn’t always necessary, but it is a good heuristic until you get more advanced.

1 Like

Hi, your implementation is rather slow, it takes

6400000000000000
Time needed for 8 * 10^7 calculations:
  3.360201 seconds (80.00 M allocations: 1.192 GiB, 1.65% gc time)

on my machine. If you write a function as suggested by others

function bc(j::Int, muls=Int(1e7))
	for i in 1:j*muls
		x = i*i
	end
	x
end

and then do a benchmark

using BenchmarkTools
julia> @btime bc(8)
  1.503 ns (0 allocations: 0 bytes)
6400000000000000

Wow, how is this possible?

julia> @code_native debuginfo=:none bc(8, Int(1e7))
    .section    __TEXT,__text,regular,pure_instructions
    imulq   %rsi, %rdi
    movq    %rdi, %rcx
    imulq   %rcx, %rcx
    xorl    %eax, %eax
    testq   %rdi, %rdi
    cmovgq  %rcx, %rax
    retq
    nopw    %cs:(%rax,%rax)

The compiler just generates code for multiplying 8 and 1e7 and then squares it. Then it does some tests. It skips the whole for loop thing to give the right result.

Welcome to the world of Julia.

4 Likes