编写可以接受currying的函数时,可以将其写为返回函数的单参数函数.例如,
let add x =
let inner y = x + y
inner
Run Code Online (Sandbox Code Playgroud)
所以你可以这样做:
add 3 4
Run Code Online (Sandbox Code Playgroud)
要么:
let add3 = add 3
add3 4
Run Code Online (Sandbox Code Playgroud)
我的问题是,因为你返回一个函数,你在概念上调用了一个函数两次(外部函数和内部函数).这比这慢吗:
let add x y = x + y
Run Code Online (Sandbox Code Playgroud)
或者编译器是否优化add 3 4
了curried定义中的调用?
Jus*_*mer 12
let f x = fun y -> x + y
let g x y = x + y
Run Code Online (Sandbox Code Playgroud)
在dnSpy中查看这些函数定义以获得优化的构建,可以看出它们是:
public static int f(int x, int y)
{
return x + y;
}
public static int g(int x, int y)
{
return x + y;
}
Run Code Online (Sandbox Code Playgroud)
这并不奇怪,因为g
实际上是一个简短的定义,f
这是一般情况.在F#类语言中,函数在概念上总是采用单个值返回单个值.值可能是函数.如果一个人为这个函数签名保留了f
,那就更容易看到了g
val f: int -> int -> int
// Actually is
// val f: int -> (int -> int)
// ie f is a function that takes a single int and returns a function that takes a single int and returns an int.
Run Code Online (Sandbox Code Playgroud)
为了让F#在.NET上更快地执行,程序集中的物理表示f
是:
public static int f(int x, int y)
Run Code Online (Sandbox Code Playgroud)
虽然这是F#功能的更自然的表示.
public static Func<int, int> f(int x)
Run Code Online (Sandbox Code Playgroud)
虽然表现不佳.
通常F#足够聪明,可以通过上面的优化和调用来避免抽象的开销.但是,有些情况下F#无法为您优化.
想象一下,你正在实施 fold
let rec fold f s vs =
match vs with
| v::vs -> fold f (f s v) vs
| [] -> s
Run Code Online (Sandbox Code Playgroud)
这里F#无法完全优化f s v
.原因是f
可能有一个比上面更复杂的实现,可能会返回一个不同的函数取决于s
.
如果你看一下dnSpy
你注意到F#正在调用函数,InvokeFast
但这会进行内部测试以查看它是否可以快速调用.在折叠中,我们然后对每个值进行此测试,即使这是相同的功能.
这就是人们有时会看到这样fold
写的原因:
let fold f s vs =
let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt f
let rec loop s vs =
match vs with
| v::vs -> loop (f.Invoke (s, v)) vs
| [] -> s
loop s vs
Run Code Online (Sandbox Code Playgroud)
Adapt
这里在循环之前测试是否f
确实可以优化,然后返回一个有效的适配器.在一般情况下,它可能仍然有点慢,但这是调用者的意图.
注意; 对于简单的函数值,这种潜在的性能下降不会发生'T -> 'U
.始终可以有效地调用它.
希望这可以帮助.
p.s*_*w.g 10
我在LINQPad 5中测试了这个.
关闭编译器优化后,F#编译器将为每个片段生成不同的IL.换句话说,如果有任何优化正在进行,那么它将由JITter决定,调用第一个表单可能会慢得多.
但是,当打开编译器优化时,两种形式都会在我想到的每个场景中产生相同的IL输出来测试它.事实上,对于这两种形式,请致电:
add 3 4
Run Code Online (Sandbox Code Playgroud)
产生IL等价的硬编码7
,整个函数调用被优化掉:
ldc.i4.7
Run Code Online (Sandbox Code Playgroud)
换句话说,在优化逻辑上相同的代码块时,F#编译器非常彻底.
当然,这不是一个详尽的答案,并且在某些情况下,编译器实际上会对它们进行不同的处理.
归档时间: |
|
查看次数: |
151 次 |
最近记录: |