在Julia中值得缩小由JSON.parsefile返回的字典的类型

时间:2019-02-15 12:07:07

标签: json julia

我正在编写输入为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建议“注释从无类型位置获取的值”,并且这种方法可确保没有“无类型位置”。

1 个答案:

答案 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(在结构上来说较小,因为其中包含的向量可能很大-它们的大小不会增加复杂性),这种方法在我开发的几个应用程序中被证明是有效的。