gpt4 book ai didi

performance - 为什么 F# 中的函数组合比管道慢得多(60%)?

转载 作者:行者123 更新时间:2023-12-02 17:25:29 26 4
gpt4 key购买 nike

诚然,我不确定我是否正确地将苹果与苹果或苹果与梨进行比较。但我对差异之大感到特别惊讶,如果有的话,差异会更小。

管道 can often be expressed as function composition and vice versa ,我假设编译器也知道这一点,所以我尝试了一些实验:

// simplified example of some SB helpers:
let inline bcreate() = new StringBuilder(64)
let inline bget (sb: StringBuilder) = sb.ToString()
let inline appendf fmt (sb: StringBuilder) = Printf.kbprintf (fun () -> sb) sb fmt
let inline appends (s: string) (sb: StringBuilder) = sb.Append s
let inline appendi (i: int) (sb: StringBuilder) = sb.Append i
let inline appendb (b: bool) (sb: StringBuilder) = sb.Append b

// test function for composition, putting some garbage data in SB
let compose a =
(appends "START"
>> appendb true
>> appendi 10
>> appendi a
>> appends "0x"
>> appendi 65535
>> appendi 10
>> appends "test"
>> appends "END") (bcreate())

// test function for piping, putting the same garbage data in SB
let pipe a =
bcreate()
|> appends "START"
|> appendb true
|> appendi 10
|> appendi a
|> appends "0x"
|> appendi 65535
|> appendi 10
|> appends "test"
|> appends "END"

在 FSI 中测试此功能(启用 64 位,--optimize 标志打开)给出:

> for i in 1 .. 500000 do compose 123 |> ignore;;
Real: 00:00:00.390, CPU: 00:00:00.390, GC gen0: 62, gen1: 1, gen2: 0
val it : unit = ()
> for i in 1 .. 500000 do pipe 123 |> ignore;;
Real: 00:00:00.249, CPU: 00:00:00.249, GC gen0: 27, gen1: 0, gen2: 0
val it : unit = ()

微小的差异是可以理解的,但这会导致性能下降 1.6 倍 (60%)。

我实际上希望大部分工作发生在StringBuilder中,但显然组合的开销有相当大的影响。

我意识到,在大多数实际情况下,这种差异可以忽略不计,但如果您像本例一样编写大型格式文本文件(如日志文件),则会产生影响。

我正在使用最新版本的 F#。

最佳答案

我用 FSI 尝试了你的示例,没有发现明显的差异:

> #time
for i in 1 .. 500000 do compose 123 |> ignore

--> Timing now on

Real: 00:00:00.229, CPU: 00:00:00.234, GC gen0: 32, gen1: 32, gen2: 0
val it : unit = ()
> #time;;

--> Timing now off

> #time
for i in 1 .. 500000 do pipe 123 |> ignore;;;;

--> Timing now on

Real: 00:00:00.214, CPU: 00:00:00.218, GC gen0: 30, gen1: 30, gen2: 0
val it : unit = ()

BenchmarkDotNet中测量它(第一个表只是一个 compose/pipe 运行,第二个表执行了 500000 次),我发现了类似的东西:

  Method | Platform |       Jit |      Median |     StdDev |    Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
-------- |--------- |---------- |------------ |----------- |--------- |------ |------ |------------------- |
compose | X64 | RyuJit | 319.7963 ns | 5.0299 ns | 2,848.50 | - | - | 182.54 |
pipe | X64 | RyuJit | 308.5887 ns | 11.3793 ns | 2,453.82 | - | - | 155.88 |
compose | X86 | LegacyJit | 428.0141 ns | 3.6112 ns | 1,970.00 | - | - | 126.85 |
pipe | X86 | LegacyJit | 416.3469 ns | 8.0869 ns | 1,886.00 | - | - | 121.86 |

Method | Platform | Jit | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op |
-------- |--------- |---------- |------------ |---------- |--------- |------ |------ |------------------- |
compose | X64 | RyuJit | 160.8059 ms | 4.6699 ms | 3,514.75 | - | - | 56,224,980.75 |
pipe | X64 | RyuJit | 163.1026 ms | 4.9829 ms | 3,120.00 | - | - | 50,025,686.21 |
compose | X86 | LegacyJit | 215.8562 ms | 4.2769 ms | 2,292.00 | - | - | 36,820,936.68 |
pipe | X86 | LegacyJit | 209.9219 ms | 2.5605 ms | 2,220.00 | - | - | 35,554,575.32 |

您测量的差异可能与 GC 有关。尝试在您的计时之前/之后强制进行 GC 收集。

也就是说,看看 source code对于管道运算符:

let inline (|>) x f = f x

并与组合运算符进行比较:

let inline (>>) f g x = g(f x)

似乎清楚地表明组合运算符将创建 lambda 函数,这应该会导致更多的分配。这也可以在 BenchmarkDotNet 运行中看到。这也可能是您所看到的性能差异的原因。

关于performance - 为什么 F# 中的函数组合比管道慢得多(60%)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39349538/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com