【问题标题】:why Seq.iter is 2x faster than for loop if target is for x64?如果目标是 x64,为什么 Seq.iter 比 for 循环快 2 倍?
【发布时间】:2012-11-08 00:04:23
【问题描述】:

声明:这是微基准,如果您对该主题感到不满意,请不要评论诸如“过早优化是邪恶的”之类的引用。

示例是针对 x64 的版本、.Net4.5 Visual Studio 2012 F# 3.0 并在 windows 7 x64 中运行

分析后,我缩小了我的一个应用程序的瓶颈,所以我想提出这个问题:

观察

如果for in 循环或Seq.iter 内部没有循环,那么很明显它们的速度相似。 (更新 2 与更新 4)

如果for in 循环或Seq.iter 中存在循环,则Seq.iter 似乎是for in 的2 倍。 (更新与更新3)奇怪吗? (如果在 fsi 中运行,它们会相似)

如果是针对anycpu,运行在x64,时间上没有区别。所以问题变成了:如果目标是 x64,Seq.iter (update3) 将提高 2 倍速度

花费时间:

update:   00:00:11.4250483 // 2x as much as update3, why?
updatae2: 00:00:01.4447233
updatae3: 00:00:06.0863791
updatae4: 00:00:01.4939535

源代码:

open System.Diagnostics
open System

[<EntryPoint>]
let main argv = 
    let pool = seq {1 .. 1000000}

    let ret = Array.zeroCreate 100

    let update pool =
        for x in pool do
            for y in 1 .. 200 do
                ret.[2] <- x + y

    let update2 pool =
        for x in pool do
            //for y in 1 .. 100 do
                ret.[2] <- x


    let update3 pool =
        pool
            |> Seq.iter (fun x ->
                                  for y in 1 .. 200 do
                                      ret.[2] <- x + y)

    let update4 pool =
        pool
            |> Seq.iter (fun x ->
                                  //for y in 1 .. 100 do
                                      ret.[2] <- x)


    let test n =
        let run = match n with
                  | 1 -> update
                  | 2 -> update2
                  | 3 -> update3
                  | 4 -> update4
        for i in 1 .. 50 do
            run pool

    let sw = new Stopwatch()
    sw.Start()
    test(1)
    sw.Stop()
    Console.WriteLine(sw.Elapsed);

    sw.Restart()
    test(2)
    sw.Stop()
    Console.WriteLine(sw.Elapsed)

    sw.Restart()
    test(3)
    sw.Stop()
    Console.WriteLine(sw.Elapsed)

    sw.Restart()
    test(4)
    sw.Stop()
    Console.WriteLine(sw.Elapsed)
    0 // return an integer exit code

【问题讨论】:

  • 请检查您的缩进。
  • 对此类问题的标准答案是 seq.iter 具有函数调用开销
  • @JohnPalmer 我在问为什么 seq.iter 更快,而不是更慢......
  • @RamonSnir 我的缩进有什么问题?
  • 反汇编显示没有差异,据我所知,这应该会导致差异 - 唯一的可能是一些启发式不同,因为Seq.iter 版本的代码要少得多,因为方法只是内部循环 - 也许 JIT 摆脱了一些计算?

标签: f#


【解决方案1】:

这不是一个完整的答案,但希望它能帮助你更进一步。

我可以使用相同的配置重现该行为。这是一个更简单的分析示例:

open System

let test1() =
    let ret = Array.zeroCreate 100
    let pool = {1 .. 1000000}    
    for x in pool do
        for _ in 1..50 do
            for y in 1..200 do
                ret.[2] <- x + y

let test2() =
    let ret = Array.zeroCreate 100
    let pool = {1 .. 1000000}    
    Seq.iter (fun x -> 
        for _ in 1..50 do
            for y in 1..200 do
                ret.[2] <- x + y) pool

let time f =
    let sw = new Diagnostics.Stopwatch()
    sw.Start()
    let result = f() 
    sw.Stop()
    Console.WriteLine(sw.Elapsed)
    result

[<EntryPoint>]
let main argv =
    time test1
    time test2
    0

在本例中,Seq.iterfor x in pool 执行一次,但 test1test2 之间仍有 2 倍的时间差:

00:00:06.9264843
00:00:03.6834886

它们的 IL 非常相似,因此编译器优化不是问题。 x64 抖动似乎无法优化test1,尽管它可以使用test2 进行优化。有趣的是,如果我将test1 中的嵌套for 循环重构为一个函数,JIT 优化又会成功:

let body (ret: _ []) x =
    for _ in 1..50 do
        for y in 1..200 do
            ret.[2] <- x + y

let test3() =
    let ret = Array.zeroCreate 100
    let pool = {1..1000000}    
    for x in pool do
        body ret x

// 00:00:03.7012302

当我使用described here 技术禁用 JIT 优化时,这些函数的执行时间是相当的。

我不知道为什么 x64 抖动在特定示例中会失败。你可以disassemble optimized jitted code逐行比较ASM指令。也许具有良好 ASM 知识的人可以找出他们之间的差异。

【讨论】:

    【解决方案2】:

    当我在我的机器上运行实验时(在发布模式下使用 VS 2012 中的 F# 3.0),我没有得到你描述的时间。当您重复运行它时,您是否始终得到相同的数字?

    我尝试了大约 4 次,我总是得到非常相似的数字。带有Seq.iter 的版本往往会稍微快一些,但这可能在统计上并不显着。类似的东西(使用Stopwatch):

    test(1) = 15321ms
    test(2) = 5149ms
    test(3) = 14290ms
    test(4) = 4999ms
    

    我正在使用 64 位 Windows 7 的 Intel Core2 Duo (2.26Ghz) 笔记本电脑上运行测试。

    【讨论】:

    • 我更新了完整的测试代码。只有当我编译到 x64 并在 x64 中运行时才会出现这种症状。如果编译到 anycpu 并在 x64 中运行,我会得到类似的结果。奇怪的是在 x64 或 anycpu 中编译的函数 IL 是相同的。我不明白速度会有什么不同。现在问题变成了:如果在 x64 中编译,为什么 update3 会提高 2 倍速度
    猜你喜欢
    • 2017-11-29
    • 2021-10-01
    • 2019-06-07
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2022-06-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多