Julia: Division with an element of a tuple or vector uses allocation

Created on 3 Jan 2017  路  4Comments  路  Source: JuliaLang/julia

Consider a tuple t = (1,1,1) for example. 1/t[1] and 1/1 are lowered to the same code:

julia> @code_lowered 1/t[1]
CodeInfo(:(begin
        nothing
        return (Base.float)(x) / (Base.float)(y)
    end))

julia> @code_lowered 1/1
CodeInfo(:(begin
        nothing
        return (Base.float)(x) / (Base.float)(y)
    end))

However, 1/t[1] uses an allocation, whereas 1/1 doesn't. As a result, 1/t[1] is much much slower than 1/1:

julia> @benchmark 1/t[1]
BenchmarkTools.Trial:
  memory estimate:  16.00 bytes
  allocs estimate:  1
  --------------
  minimum time:     78.511 ns (0.00% GC)
  median time:      80.084 ns (0.00% GC)
  mean time:        90.435 ns (2.50% GC)
  maximum time:     4.507 渭s (97.09% GC)
  --------------
  samples:          10000
  evals/sample:     968
  time tolerance:   5.00%
  memory tolerance: 1.00%

julia> @benchmark 1/1
BenchmarkTools.Trial:
  memory estimate:  0.00 bytes
  allocs estimate:  0
  --------------
  minimum time:     2.325 ns (0.00% GC)
  median time:      2.329 ns (0.00% GC)
  mean time:        2.669 ns (0.00% GC)
  maximum time:     2.010 渭s (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1000
  time tolerance:   5.00%
  memory tolerance: 1.00%

Among basic arithmetic operations, only division uses an allocation. Addition (1+t[1]), subtraction (1-t[1]), multiplication (1*t[1]) do not use any allocation. I don't understand why 1/t[1] needs an allocation.

Is this something that needs to be fixed, or something expected?

Here is the versioninfo() result:

julia> versioninfo()
Julia Version 0.6.0-dev.1738
Commit 309657f* (2016-12-28 18:43 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin16.0.0)
  CPU: Intel(R) Core(TM)2 Duo CPU     T9900  @ 3.06GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Penryn)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.7.1 (ORCJIT, penryn)

Most helpful comment

You can interpolate variables into the expression after @benchmark which can help with having variables in global scope, ex:

julia> a = 3

julia> @benchmark sin(a)
  memory estimate:  16.00 bytes
  allocs estimate:  1
  minimum time:     30.00 ns (0.00% GC)

julia> @benchmark sin($a)
BenchmarkTools.Trial: 
  memory estimate:  0.00 bytes
  allocs estimate:  0
  minimum time:     13.00 ns (0.00% GC)

All 4 comments

Don't benchmark in global scope.

Is there a manifestation of this within a larger computation? It can be difficult to put a single operation under the microscope sometimes.

@KristofferC Thanks. I read somewhere that to measure the correct number of allocations, we should either wrap the code within a function and call it with @time, or simply call the code with @benchmark, so I thought I didn't have to wrap the code as long as I use @benchmark...

@JeffBezanson I observed that some portion of my code was using too many allocations than I expected. A few experiments led me to believe that the extra allocation reported above was the culprit, but apparently I was wrong. I will look into the problem again and report back if I still don't understand why it uses too many allocations.

You can interpolate variables into the expression after @benchmark which can help with having variables in global scope, ex:

julia> a = 3

julia> @benchmark sin(a)
  memory estimate:  16.00 bytes
  allocs estimate:  1
  minimum time:     30.00 ns (0.00% GC)

julia> @benchmark sin($a)
BenchmarkTools.Trial: 
  memory estimate:  0.00 bytes
  allocs estimate:  0
  minimum time:     13.00 ns (0.00% GC)
Was this page helpful?
0 / 5 - 0 ratings