最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

allocation - How to make my julia in-place function wrapper work without allocating new memory? - Stack Overflow

programmeradmin3浏览0评论

I am having a specific problem in julia, I cannot seem to figure out. I broke down my code to some minimal working example to demonstrate the problem:

using BenchmarkTools

function f!(u::Vector)
    u[1] = 5im
    u[2] = 6
end

function g!(u::Vector)
    u = u[1:2] + u[3:4] * 1im
    f!(u)
end 

# first version, works as expected
f!(ComplexF64.(Vector([1,2]))) # for jit compilation
u = ComplexF64.(Vector([1,2]))
println(u)
allo = @allocated f!(u)
println("Allocations : $allo bytes")
println(u)

# second try, not working
g!(Float64.(Vector([1,2,3,4]))) # for jit compilation
u = Float64.(Vector([1,2,3,4]))
println(u)
allo = @allocated g!(u)
println("Allocations : $allo bytes")
println(u)

The function f! takes a 2d-Vector (note the vector must be complex to not throw an error) and simply changes the first entry to the pure imaginary number 5im and the second entry to 6.

Now I have a second function g! which is basically a wrapper for f, which allows for a Float64 vector with twice as many entries. Its use is to convert the vector to ComplexF64 and update its entries in-place via f!.

I specifically want that both functions f! and g! do not allocate additional memory. Although technically a Float64 vector with 4 entries should use the same amount of memory as a ComplexF64 vector with 2 entries, I am not sure if this is possible to do in julia.

The output to the above code is:

ComplexF64[1.0 + 0.0im, 2.0 + 0.0im]
Allocations : 0 bytes
ComplexF64[0.0 + 5.0im, 6.0 + 0.0im]
[1.0, 2.0, 3.0, 4.0]
Allocations : 352 bytes
[1.0, 2.0, 3.0, 4.0]

Problem: The output is not what I expected. While f! works just fine, applying g! doesn't produce the output I want, most likely because while locally converting u to a complex vector f! will just update this variable in local scope not modifying the original vector. I am rather new to julia and I have not figured out a way to make this wrapper work. Also I am not sure if this is possible without allocating new memory.

Is there a way to achieve with g! the same as with f!, i.e. update the entries of u in-place but without allocating additional memory?

Thanks in advance!

Edit: To have an idea what I am actually trying to achieve, see the following

function f!(u::Vector{ComplexF64})
    # takes as input a complex vector of length N
    # u = a + bim (where a and b are float64 vectors)
    # ... modify u in place
    return
end

function g!(u::Vector{Float64})
    # takes as input a float vector of length 2*N
    # u = (a,b) (where a and b are float64 vectors of length N, representing real and imag part)
    # modify u = (a,b) without new memory allocation to achieve the same as calling f!(a + bim)
    # More specifically: a = real(f(a + bim)), b = imag(f(a + bim))
    return
end

Hence, for given f!, which modifies a complex vector in place, g! should be the split of f! into real and imaginary parts (if possible without additional memory allocation).

I am having a specific problem in julia, I cannot seem to figure out. I broke down my code to some minimal working example to demonstrate the problem:

using BenchmarkTools

function f!(u::Vector)
    u[1] = 5im
    u[2] = 6
end

function g!(u::Vector)
    u = u[1:2] + u[3:4] * 1im
    f!(u)
end 

# first version, works as expected
f!(ComplexF64.(Vector([1,2]))) # for jit compilation
u = ComplexF64.(Vector([1,2]))
println(u)
allo = @allocated f!(u)
println("Allocations : $allo bytes")
println(u)

# second try, not working
g!(Float64.(Vector([1,2,3,4]))) # for jit compilation
u = Float64.(Vector([1,2,3,4]))
println(u)
allo = @allocated g!(u)
println("Allocations : $allo bytes")
println(u)

The function f! takes a 2d-Vector (note the vector must be complex to not throw an error) and simply changes the first entry to the pure imaginary number 5im and the second entry to 6.

Now I have a second function g! which is basically a wrapper for f, which allows for a Float64 vector with twice as many entries. Its use is to convert the vector to ComplexF64 and update its entries in-place via f!.

I specifically want that both functions f! and g! do not allocate additional memory. Although technically a Float64 vector with 4 entries should use the same amount of memory as a ComplexF64 vector with 2 entries, I am not sure if this is possible to do in julia.

The output to the above code is:

ComplexF64[1.0 + 0.0im, 2.0 + 0.0im]
Allocations : 0 bytes
ComplexF64[0.0 + 5.0im, 6.0 + 0.0im]
[1.0, 2.0, 3.0, 4.0]
Allocations : 352 bytes
[1.0, 2.0, 3.0, 4.0]

Problem: The output is not what I expected. While f! works just fine, applying g! doesn't produce the output I want, most likely because while locally converting u to a complex vector f! will just update this variable in local scope not modifying the original vector. I am rather new to julia and I have not figured out a way to make this wrapper work. Also I am not sure if this is possible without allocating new memory.

Is there a way to achieve with g! the same as with f!, i.e. update the entries of u in-place but without allocating additional memory?

Thanks in advance!

Edit: To have an idea what I am actually trying to achieve, see the following

function f!(u::Vector{ComplexF64})
    # takes as input a complex vector of length N
    # u = a + bim (where a and b are float64 vectors)
    # ... modify u in place
    return
end

function g!(u::Vector{Float64})
    # takes as input a float vector of length 2*N
    # u = (a,b) (where a and b are float64 vectors of length N, representing real and imag part)
    # modify u = (a,b) without new memory allocation to achieve the same as calling f!(a + bim)
    # More specifically: a = real(f(a + bim)), b = imag(f(a + bim))
    return
end

Hence, for given f!, which modifies a complex vector in place, g! should be the split of f! into real and imaginary parts (if possible without additional memory allocation).

Share Improve this question edited Mar 26 at 13:35 julian2000P asked Mar 26 at 12:49 julian2000Pjulian2000P 1676 bronze badges 7
  • Wait, you want to take a vector, and convert it to another type that takes twice as much memory, without allocating more memory? – user2357112 Commented Mar 26 at 12:54
  • Ah, yes sorry, will edit my post! – julian2000P Commented Mar 26 at 12:54
  • 1 Edited my post. While simplifying my original problem, I was not thinking of that, the length of the inputs of g! should be twice as long, hence at least theoretically no further memory allocation is necessary. – julian2000P Commented Mar 26 at 13:05
  • The problem I think is that inside g! vector u is initially a parameter Float64. Your action assigning u with a complex expression causes memory allocation to occur. This makes f! happy but it is altering the copy. – Martin Brown Commented Mar 26 at 13:28
  • Yes, I think I am just locally updating the copy via f!. I added some background. I am basically just trying to split f! into a real and imaginary part without additional memory allocation. – julian2000P Commented Mar 26 at 13:37
 |  Show 2 more comments

1 Answer 1

Reset to default 2

Yes, you can do this, but you need to make several changes to your code:

  • You must return u from f!, otherwise your function only returns 6:

  • It is also necessary to accept AbstractVector , otherwise, the reinterpret trick won't work.

function f!(u::AbstractVector{<:Complex})
    u[1] = 5im
    u[2] = 6
    return u 
end

As you already guessed, writing u = ... doesn't actually modify u , it just assigns the label to whatever is on the right hand side, and the right hand side happens to be a newly allocated array, since slices, like u[1:2] allocates. In fact the right hand side creates several allocations.

Instead, you can turn a real array into a complex one by reinterpreting the underlying data, like below. Here, v now refers to the same memory, just with a different interpretation of what those data mean. In this case, it gives you a complex vector of half the length of the real input. Then you pass it to f! .

function g!(u::AbstractVector{T}) where {T<:Real}
    v = reinterpret(Complex{T}, u)
    return f!(v)
end

Example:

julia> x = [1,2,3,4]
4-element Vector{Int64}:
 1
 2
 3
 4

julia> g!(x)
2-element reinterpret(Complex{Int64}, ::Vector{Int64}):
 0 + 5im
 6 + 0im

Note that if you print x , it looks like this

julia> x
4-element Vector{Int64}:
 0
 5
 6
 0

It is only the reinterpreted wrapper that has the right type.

BTW, since you are concerned about avoiding allocations, here are some additional remarks:

  • u = u[1:2] + u[3:4] * 1im allocates a lot. Each slice allocates, so u[1:2] is one allocation, u[3:4] is another one, the multiplication with 1im causes another allocation, and the addition of the two vectors makes another one, so 4 allocation for this expression.

  • Float64.(Vector([1,2,3,4])) first allocates a vector of integers. Then, you pass it to the Vector constructor, which allocates a new vector, and finally you convert to float64, causing another allocation. Instead, write [1.0, 2.0, 3.0, 4.0] , or alternatively, Float64[1, 2, 3, 4] to allocate just once.

发布评论

评论列表(0)

  1. 暂无评论