说我有很多不同的非常简单的函数,分别称为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或类似的东西,以免每次都无法建立索引。
亲切的问候, 直到
答案 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
结构并不高效,因为其f1
和f2
字段具有抽象类型(具体来说是Any
)。
使用Tuple
,NamedTuple
或带有参数的结构,因为它们都提供类型信息。元组将简单定义为(f,f)
,NamedTuple
将定义为(f1=f, f2=f)
。最复杂的情况是参数结构,在这里向您展示(Tuple
和NamedTuple
的代码会更简单):
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)
这样,您就有了f
和g
的具体类型,并且可以对闭包进行手动控制,完全避免了所有方法调用开销(重载的调用运算符除外)。并且fFunction
是完全专业且类型稳定的。