Search code examples
performancejuliaallocation

Julia - Is there a way to avoid using a loop without using too much memory


Consider the following example:

module structs
mutable struct testStruct
    x::Array{Int64,2}
end
end

function innerFunc(s::structs.testStruct)
    s.x[1:2] .= s.x[3:4]
end

function structTest!(s::structs.testStruct, n)
    for i = 1:n
        innerFunc(s)
    end
    println(s.x)
end

The memory allocations, as I increase n, increase as well. I think that's because in each iteration, I'm creating an allocation for s.x[3:4]. I can avoid this by using a loop:

function innerFunc(s::structs.testStruct)
    for i = 1:2
        s.x[i] .= s.x[i+2]
    end
end

function structTest!(s::structs.testStruct, n)
    for i = 1:n
        innerFunc(s)
    end
    println(s.x)
end

However, I don't like loops because the syntax is burdensome. Is there a way to avoid it? In each iteration, I want to modify the first and second elements of s.x without increasing memory allocations as I increase n since I'm not creating anything new.

UPDATE: In response to DNF, I tried to use @view:

module structs
mutable struct testStruct
    x::Array{Int64,2}
end
end

function innerfunc!(s::structs.testStruct)
    s.x[1:2] .= view(s.x, 3:4)
end

function structTest!(s::structs.testStruct, n)
    for i = 1:n
        innerfunc!(s)
    end
    println(s.x)
end

This is what I got:

@time structTest!(structs.testStruct([1 2 3 4]),33)
0.000112 seconds (202 allocations: 7.938 KiB)

@time structTest!(structs.testStruct([1 2 3 4]),330)
0.000126 seconds (1.69 k allocations: 68.266 KiB)

I want the allocations to stay invariant to n.


Solution

  • As @DNF noted, using views should have solved the problem, but because views are not immutable, then there is a small memory cost, which in usual applications is immaterial.

    So essentially, you have to use a for loop. To make it look a bit more vectorized, Dahua Lin's Devectorized.jl package comes to the rescue:

    # Pkg.add("Devectorized.jl")
    using Devectorized
    
    function innerfunc!(s::structs.testStruct)
        @devec s.x[1:2] = s.x[3:4]
    end
    

    and you are good to go:

    julia> @time structTest!(structs.testStruct([1 2 3 4]),3300)
      0.000299 seconds (40 allocations: 1.641 KiB)
    
    julia> @time structTest!(structs.testStruct([1 2 3 4]),330000)
      0.001209 seconds (40 allocations: 1.641 KiB)