在 Julia 中是否值得对“JSON.parsefile”返回的字典进行类型缩小

Phi*_*ell 5 json julia

我正在编写 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 性能提示建议“注释取自无类型位置的值”,这种方法可确保没有“无类型位置”。

Bog*_*ski 4

这个问题的答案有两个层次:

\n\n

1级

\n\n

是的,这将有助于代码的性能。例如,请参见以下基准:

\n\n
julia> 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以typenarrow使用更简单的方法编写函数,如下所示:

\n\n
typenarrow(x) = [v for v in x]\n
Run Code Online (Sandbox Code Playgroud)\n\n

因为使用推导式会产生一个具体类型的向量(假设你的源向量是同质的)

\n\n

2级

\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)。

\n\n

在理想的世界中,您Dict将拥有同质类型的键和值,并且所有这些都会尽可能快,但是如果我正确理解您的代码,那么您的情况下的值不是同质的。

\n\n

编辑

\n\n

为了解决 2 级问题,你可以转换DictNamedTuple这样(这是一个最小的例子,假设Dicts 只直接嵌套在Dicts 中,但如果你想要更大的灵活性,它应该很容易扩展)。

\n\n

首先,执行转换的函数如下所示:

\n\n
function 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\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在它的用途是 MWE:

\n\n
julia> 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]\n
Run Code Online (Sandbox Code Playgroud)\n\n

这种方法的优点在于 Julia 会知道 中的所有类型j2,因此如果您将j2任何函数作为参数传递给该函数,则该函数内的所有计算都会很快。

\n\n

j2这种方法的缺点是必须预编译函数,如果j2结构很大(因为结果的结构NamedTuple很复杂)并且函数所做的工作量相对较小,这可能会出现问题。但对于小型 JSON(结构意义上的小型,因为其中保存的向量可能很大 - 它们的大小不会增加复杂性),这种方法已被证明在我开发的几个应用程序中是有效的。

\n