我正在编写输入为json文件的Julia代码,该代码在(数学财务领域)进行分析并将结果写为json。该代码是R的端口,希望可以提高性能。
我使用JSON.parsefile
解析输入文件。这将返回一个Dict,在该Dict中,我观察到所有向量均为Array{Any,1}
类型。碰巧的是,我知道输入文件将永远不会包含混合类型的向量,例如某些String
和某些Number
。
因此,我编写了以下代码,在对convert
的调用失败的情况下,向量似乎仍然具有类型Array{Any,1}
。
function typenarrow!(d::Dict)
for k in keys(d)
if d[k] isa Array{Any,1}
d[k] = typenarrow(d[k])
elseif d[k] isa Dict
typenarrow!(d[k])
end
end
end
function typenarrow(v::Array{Any,1})
for T in [String,Int64,Float64,Bool,Vector{Float64}]
try
return(convert(Vector{T},v))
catch; end
end
return(v)
end
我的问题是:这值得吗?如果我缩小类型,我可以期望处理Dict
内容的代码更快地执行吗?我认为答案是肯定的,Julia performance tips建议“注释从无类型位置获取的值”,并且这种方法可确保没有“无类型位置”。
答案 0 :(得分:2)
此问题的答案分为两个级别:
1级
是的,这将有助于代码的性能。例如,请参见以下基准:
julia> using BenchmarkTools
julia> x = Any[1 for i in 1:10^6];
julia> y = [1 for i in 1:10^6];
julia> @btime sum($x)
26.507 ms (477759 allocations: 7.29 MiB)
1000000
julia> @btime sum($y)
226.184 μs (0 allocations: 0 bytes)
1000000
您可以使用以下更简单的方法编写typenarrow
函数:
typenarrow(x) = [v for v in x]
因为使用该理解将产生具体类型的向量(假设您的源向量是同质的)
2级
这不是完全最佳的。仍然存在的问题是您有一个Dict
,它是带有抽象类型参数的容器(请参见https://docs.julialang.org/en/latest/manual/performance-tips/#Avoid-containers-with-abstract-type-parameters-1)。因此,为了使计算速度更快,您必须使用障碍函数(请参阅https://docs.julialang.org/en/latest/manual/performance-tips/#kernel-functions-1)或对要引入的变量使用类型注释(请参阅https://docs.julialang.org/en/v1/manual/types/index.html#Type-Declarations-1)。
在理想情况下,您的Dict
将具有同质类型的键和值,并且所有键和值都将达到最大速度,但是如果我正确理解您的代码,则您的情况下的值就不是同质的。
编辑
为了解决2级问题,您可以像这样将Dict
转换为NamedTuple
(这是一个最小的示例,假设Dict
仅嵌套在Dict
s中直接,但是如果您想要更大的灵活性,它应该足够容易扩展)。
首先,执行转换的功能如下:
function typenarrow!(d::Dict)
for k in keys(d)
if d[k] isa Array{Any,1}
d[k] = [v for v in d[k]]
elseif d[k] isa Dict
d[k] = typenarrow!(d[k])
end
end
NamedTuple{Tuple(Symbol.(keys(d)))}(values(d))
end
现在使用它的MWE:
julia> using JSON
julia> x = """
{
"name": "John",
"age": 27,
"values": {
"v1": [1,2,3],
"v2": [1.5,2.5,3.5]
},
"v3": [1,2,3]
}
""";
julia> j1 = JSON.parse(x)
Dict{String,Any} with 4 entries:
"name" => "John"
"values" => Dict{String,Any}("v2"=>Any[1.5, 2.5, 3.5],"v1"=>Any[1, 2, 3])
"age" => 27
"v3" => Any[1, 2, 3]
julia> j2 = typenarrow!(j1)
(name = "John", values = (v2 = [1.5, 2.5, 3.5], v1 = [1, 2, 3]), age = 27, v3 = [1, 2, 3])
julia> dump(j2)
NamedTuple{(:name, :values, :age, :v3),Tuple{String,NamedTuple{(:v2, :v1),Tuple{Array{Float64,1},Array{Int64,1}}},Int64,Array{Int64,1}}}
name: String "John"
values: NamedTuple{(:v2, :v1),Tuple{Array{Float64,1},Array{Int64,1}}}
v2: Array{Float64}((3,)) [1.5, 2.5, 3.5]
v1: Array{Int64}((3,)) [1, 2, 3]
age: Int64 27
v3: Array{Int64}((3,)) [1, 2, 3]
此方法的优点是Julia会知道j2
中的所有类型,因此,如果将j2
传递给任何函数作为参数,则该函数内部的所有计算都将很快。
该方法的缺点是必须预先编译采用j2
的函数,如果j2
的结构很大(那么得到的NamedTuple
的结构则可能会出现问题)非常复杂),并且您的函数所做的工作量相对较小。但是对于小型JSON(在结构上来说较小,因为其中包含的向量可能很大-它们的大小不会增加复杂性),这种方法在我开发的几个应用程序中被证明是有效的。