在julia中,对传递函数的评估要比直接评估慢。有解决方法吗?

时间:2018-09-27 12:47:37

标签: julia

说我有很多不同的非常简单的函数,分别称为f1,f2,...。 我想将所有f函数存储在fStruct中并将f函数之一传递给例如g,在我的代码中需要时。 但是,当我将函数f传递给g时,对函数g()的求值要慢得多。 有解决方法吗? 我的丑陋解决方案是使用一个整体函数,该函数通过if-else语句选择正确的f()函数。 以下是慢速计算的最小示例。

using BenchmarkTools
struct fFunction
    f1
    f2
end
f() = return 1
fStruct = fFunction(f, f)
g = fStruct.f1
@btime f() --> 0.001 ns (0 allocations: 0 bytes)
@btime g() --> 9.591 ns (0 allocations: 0 bytes)

EDIT1:

在下面的最小示例中,我还可以问为什么函数g较慢或如何使其与f一样快

using BenchmarkTools
f() = return 1
func = "f"
g = eval(Meta.parse(func))
f == g -->true
@btime f() --> 0.001 ns (0 allocations: 0 bytes)
@btime g() --> 11.907 ns (0 allocations: 0 bytes)

EDIT2:

谢谢您的回答。 我用解决方案更新了帖子。

using BenchmarkTools
f() = return 1
function g(x)
   h = f
   h()
end
const g2 = f
@btime f()
@btime g(f)
@btime g2()

f,g和g2给您相同的速度。

struct fFunctionAmbigiousType{F}
     f1::F
     f2::F
end
struct fFunctionDeclaredType{F}
     f1::F
     f2::F
end
fStructAmbigiousType = fFunctionAmbigiousType(f, f)
fStructDeclaredType = fFunctionDeclaredType(f, f)
fTuple = (f1 = f, f2 = f)
@btime $fStructAmbigiousType.f1
@btime $fStructDeclaredType.f1
@btime $fTuple.f1
fStructAmbigiousTypeFunctionPassed = fStructAmbigiousType.f1
fStructDeclaredTypeFunctionPassed = fStructDeclaredType.f1
fTupleFunctionPassed = fTuple.f1
@btime $fStructAmbigiousTypeFunctionPassed() 
@btime $fStructDeclaredTypeFunctionPassed() 
@btime $fTupleFunctionPassed() 

fFunctionAmbigiousType,fFunctionDeclaredType和fTuple给您相同的速度。 将函数的类型声明为结构不会改变任何内容。在这两种情况下,Julia都能理解typeof {f}。 参数化的结构或参数化的NamedTuple是可能的,但如果您经常应用该函数,则速度当然会慢一些。如果经常使用函数f,则应首先将其传递给g或类似的东西,以免每次都无法建立索引。

亲切的问候, 直到

2 个答案:

答案 0 :(得分:6)

您的问题中有几个问题。

如何进行基准测试

实际上,在您的代码中,两个功能都同样快。问题在于,g不是全局范围内的const,这会带来惩罚。要看到将此g声明为const或在$g调用中使用@btime来看看没有区别:

julia> using BenchmarkTools

julia> struct fFunction
           f1
           f2
       end

julia> f() = return 1
f (generic function with 1 method)

julia> fStruct = fFunction(f, f)
fFunction(f, f)

julia> const g = fStruct.f1
f (generic function with 1 method)

julia> @btime f()
  0.001 ns (0 allocations: 0 bytes)
1

julia> @btime g()
  0.001 ns (0 allocations: 0 bytes)
1

julia> using BenchmarkTools

julia> struct fFunction
           f1
           f2
       end

julia> f() = return 1
f (generic function with 1 method)

julia> fStruct = fFunction(f, f)
fFunction(f, f)

julia> g = fStruct.f1
f (generic function with 1 method)

julia> @btime f()
  0.001 ns (0 allocations: 0 bytes)
1

julia> @btime $g()
  0.001 ns (0 allocations: 0 bytes)
1

如何分析您的代码

但是,这种等效是人为的,因为您是在全局范围内从g提取fStruct的,因此在调用@btime之前会对它进行求值。一个更合适的测试是:

julia> using BenchmarkTools

julia> struct fFunction
           f1
           f2
       end

julia> f() = return 1
f (generic function with 1 method)

julia> fStruct = fFunction(f, f)
fFunction(f, f)

julia> test1() = f()
test1 (generic function with 1 method)

julia> test2(fStruct) = fStruct.f1()
test2 (generic function with 1 method)

julia> @btime test1()
  0.001 ns (0 allocations: 0 bytes)
1

julia> @btime test2($fStruct)
  14.462 ns (0 allocations: 0 bytes)
1

julia> @code_warntype test1()
Body::Int64
1 1 ─     return 1                                                                                                                   │

julia> @code_warntype test2(fStruct)
Body::Any
1 1 ─ %1 = (Base.getfield)(fStruct, :f1)::Any                                                                           │╻ getproperty
  │   %2 = (%1)()::Any                                                                                                  │
  └──      return %2

您会发现使用fFunction结构并不高效,因为其f1f2字段具有抽象类型(具体来说是Any)。

如何编写包含函数的有效结构

使用TupleNamedTuple或带有参数的结构,因为它们都提供类型信息。元组将简单定义为(f,f)NamedTuple将定义为(f1=f, f2=f)。最复杂的情​​况是参数结构,在这里向您展示(TupleNamedTuple的代码会更简单):

julia> using BenchmarkTools

julia> struct fFunction{F1,F2}
           f1::F1
           f2::F2
       end

julia> f() = return 1
f (generic function with 1 method)

julia> fStruct = fFunction(f, f)
fFunction{typeof(f),typeof(f)}(f, f)

julia> test1() = f()
test1 (generic function with 1 method)

julia> test2(fStruct) = fStruct.f1()
test2 (generic function with 1 method)

julia> @btime test1()
  0.001 ns (0 allocations: 0 bytes)
1

julia> @btime test2($fStruct)
  1.866 ns (0 allocations: 0 bytes)
1

julia> @code_warntype test1()
Body::Int64
1 1 ─     return 1                                                                                                                   │

julia> @code_warntype test2(fStruct)
Body::Int64
1 1 ─     (Base.getfield)(fStruct, :f1)                                                                                 │╻ getproperty
  └──     return 1

您会发现使用定义为参数类型的fFunction几乎没有开销(您支付的唯一费用是字段提取)。

如果不清楚,请告知我,我可以详细说明。

答案 1 :(得分:0)

如果您提前知道将要传递什么样的函数,我发现使事情变得更快的一件事是将所需的闭包手动转换为结构。因此,例如,如果您将始终传递以下形式的函数

f(x) = x + a

对于不同的a,您可以将其转换为

struct Adder
    a::Int
end

(adder::Adder)(x) = x + adder.a

然后在fFunction中使用它,如

fFunction(f::Adder, g::Adder) = rand() < 0.5 ? f(10) : g(10)

这样,您就有了fg的具体类型,并且可以对闭包进行手动控制,完全避免了所有方法调用开销(重载的调用运算符除外)。并且fFunction是完全专业且类型稳定的。