在复合类型中添加所有数组的高效且简洁的方法?

时间:2015-04-24 20:29:41

标签: julia

我想出了两种方法来在一对复合类型中添加所有数组。第一种方式(add_structs_1)需要4秒才能运行,第二种方式(add_structs_2)需要0.15秒。但第二种方法需要更多代码......我必须明确提到复合类型中的每个字段。有没有办法在不明确列出每个字段的情况下获得add_structs_2的效率?

type SampleStruct
    a::Vector{Float64}
    k::Matrix{Float64}
    e_axis::Vector{Float64}
    e_dev::Vector{Float64}
    e_scale::Vector{Float64}
end

function add_structs_1(tgt::SampleStruct, src::SampleStruct)
    for n in names(SampleStruct)
        for i in 1:length(tgt.(n))
            tgt.(n)[i] += src.(n)[i]
        end
    end
end

function add_structs_2(tgt::SampleStruct, src::SampleStruct)
    for i in 1:length(tgt.a)
        tgt.a[i] += src.a[i]
    end

    for i in 1:length(tgt.k)
        tgt.k[i] += src.k[i]
    end

    for i in 1:length(tgt.e_axis)
        tgt.e_axis[i] += src.e_axis[i]
    end

    for i in 1:length(tgt.e_dev)
        tgt.e_dev[i] += src.e_dev[i]
    end

    for i in 1:length(tgt.e_scale)
        tgt.e_scale[i] += src.e_scale[i]
    end
end

function time_add_structs(f::Function)
    src = SampleStruct(ones(3), ones(3,3), [1.], [1.], [1.])
    tgt = SampleStruct(ones(3), ones(3,3), [1.], [1.], [1.])

    @time for i in 1:1000000
        f(tgt, src)
    end
end

time_add_structs(add_structs_1)
time_add_structs(add_structs_1)

time_add_structs(add_structs_2)
time_add_structs(add_structs_2)

time_add_structs(add_structs_3)
time_add_structs(add_structs_3)

3 个答案:

答案 0 :(得分:5)

add_structs_1的更多julian方法是使内循环成为一个单独的函数,这允许编译器专门化SampleStruct中每个类型的函数,并提供相当快的速度。

通过分析代码可以看出,执行names(SampleStruct)的时间非常重要,这应该在你的基准测试的每次迭代中完成,通过使它成为一个全局常量,一段时间获得并且现在的功能看起来像:

function add_array(a::AbstractArray,b::AbstractArray)
    for i in 1:length(a)
        a[i] += b[i]
    end
end
const names_in_struct = names(SampleStruct)
function add_structs_3(tgt::SampleStruct, src::SampleStruct)
    for n in names_in_struct
        add_array(tgt.(n),src.(n))
    end
end

该功能现在是add_structs_2

的四分之一

元编程方法更复杂,但提供与add_structs_2

相同的性能
ex = Any[]
for n in names(SampleStruct)
    t = Expr(:.,:tgt, QuoteNode(n))
    s = Expr(:.,:src, QuoteNode(n))
    e=quote
        for i in 1:length($t)
            $t[i] += $s[i]
        end
    end
    push!(ex,e)
end
eval(quote function add_structs_4(tgt::SampleStruct, src::SampleStruct) 
     $(Expr(:block,ex...))
    end 
end)

答案 1 :(得分:4)

这些for循环中的每一个都可以用单行替换,使得长版本就是这样:

function add_structs_3(tgt::SampleStruct, src::SampleStruct)
    tgt.a[:] += src.a
    tgt.k[:,:] += src.k
    tgt.e_axis[:] += src.e_axis
    tgt.e_dev[:] += src.e_dev
    tgt.e_scale[:] += src.e_scale
end

这与add_structs_1的长度相同,但速度较慢,因为它实际上构建了一个临时数组,然后执行赋值。您还可以使用一些元编程来生成更长的代码。

答案 2 :(得分:1)

应该获得最佳案例的所有表现的方法是结合Daniel和Stefan的答案:将添加定义为单独的函数,就像在Daniel的解决方案中一样,而不是迭代在名单列表中,每个字段都像Stefan的回答一样手动。