我正在编写 Julia 代码,其输入是 json 文件,在(数学金融领域)中执行分析并将结果写入 json。代码是从 R 移植过来的,希望能提高性能。
我使用JSON.parsefile. 这将返回一个 Dict,其中我观察到所有向量的类型都是Array{Any,1}。碰巧的是,我知道输入文件永远不会包含混合类型的向量,例如某些Strings 和某些Numbers。因此,我编写了以下代码,它似乎运行良好并且“安全”,因为如果调用convert失败,则向量将继续具有 type 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
Run Code Online (Sandbox Code Playgroud)
我的问题是:这值得吗?Dict如果我进行这种类型缩小,我是否可以期望处理内容的代码执行得更快?我认为答案是肯定的,因为Julia 性能提示建议“注释取自无类型位置的值”,这种方法可确保没有“无类型位置”。
这个问题的答案有两个层次:
\n\n1级
\n\n是的,这将有助于代码的性能。例如,请参见以下基准:
\n\njulia> using BenchmarkTools\n\njulia> x = Any[1 for i in 1:10^6];\n\njulia> y = [1 for i in 1:10^6];\n\njulia> @btime sum($x)\n 26.507 ms (477759 allocations: 7.29 MiB)\n1000000\n\njulia> @btime sum($y)\n 226.184 \xce\xbcs (0 allocations: 0 bytes)\n1000000\nRun Code Online (Sandbox Code Playgroud)\n\n您可以typenarrow使用更简单的方法编写函数,如下所示:
typenarrow(x) = [v for v in x]\nRun Code Online (Sandbox Code Playgroud)\n\n因为使用推导式会产生一个具体类型的向量(假设你的源向量是同质的)
\n\n2级
\n\n这并不完全是最佳的。仍然存在的问题是,您有一个Dict具有抽象类型参数的容器(请参阅https://docs.julialang.org/en/latest/manual/performance-tips/#Avoid-containers-with-abstract-类型参数-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将拥有同质类型的键和值,并且所有这些都会尽可能快,但是如果我正确理解您的代码,那么您的情况下的值不是同质的。
编辑
\n\n为了解决 2 级问题,你可以转换Dict成NamedTuple这样(这是一个最小的例子,假设Dicts 只直接嵌套在Dicts 中,但如果你想要更大的灵活性,它应该很容易扩展)。
首先,执行转换的函数如下所示:
\n\nfunction typenarrow!(d::Dict)\n for k in keys(d)\n if d[k] isa Array{Any,1}\n d[k] = [v for v in d[k]]\n elseif d[k] isa Dict\n d[k] = typenarrow!(d[k])\n end\n end\n NamedTuple{Tuple(Symbol.(keys(d)))}(values(d))\nend\nRun Code Online (Sandbox Code Playgroud)\n\n现在它的用途是 MWE:
\n\njulia> using JSON\n\njulia> x = """\n {\n "name": "John",\n "age": 27,\n "values": {\n "v1": [1,2,3],\n "v2": [1.5,2.5,3.5]\n },\n "v3": [1,2,3]\n }\n """;\n\njulia> j1 = JSON.parse(x)\nDict{String,Any} with 4 entries:\n "name" => "John"\n "values" => Dict{String,Any}("v2"=>Any[1.5, 2.5, 3.5],"v1"=>Any[1, 2, 3])\n "age" => 27\n "v3" => Any[1, 2, 3]\n\njulia> j2 = typenarrow!(j1)\n(name = "John", values = (v2 = [1.5, 2.5, 3.5], v1 = [1, 2, 3]), age = 27, v3 = [1, 2, 3])\n\njulia> dump(j2)\nNamedTuple{(:name, :values, :age, :v3),Tuple{String,NamedTuple{(:v2, :v1),Tuple{Array{Float64,1},Array{Int64,1}}},Int64,Array{Int64,1}}}\n name: String "John"\n values: NamedTuple{(:v2, :v1),Tuple{Array{Float64,1},Array{Int64,1}}}\n v2: Array{Float64}((3,)) [1.5, 2.5, 3.5]\n v1: Array{Int64}((3,)) [1, 2, 3]\n age: Int64 27\n v3: Array{Int64}((3,)) [1, 2, 3]\nRun Code Online (Sandbox Code Playgroud)\n\n这种方法的优点在于 Julia 会知道 中的所有类型j2,因此如果您将j2任何函数作为参数传递给该函数,则该函数内的所有计算都会很快。
j2这种方法的缺点是必须预编译函数,如果j2结构很大(因为结果的结构NamedTuple很复杂)并且函数所做的工作量相对较小,这可能会出现问题。但对于小型 JSON(结构意义上的小型,因为其中保存的向量可能很大 - 它们的大小不会增加复杂性),这种方法已被证明在我开发的几个应用程序中是有效的。
| 归档时间: |
|
| 查看次数: |
416 次 |
| 最近记录: |