10

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

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

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

观察

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

for in如果循环或内部有一个循环Seq.iter,它似乎Seq.iter是 2 倍快for in。(更新与更新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
4

2 回答 2

7

这不是一个完整的答案,但希望它可以帮助您走得更远。

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

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.iterand被执行了一次,但是andfor x in pool之间仍然有 2 倍的时间差:test1test2

00:00:06.9264843
00:00:03.6834886

它们的 IL 非常相似,因此编译器优化不是问题。x64 抖动似乎无法优化,test1尽管它可以使用test2. 有趣的是,如果我将嵌套的 for 循环重构test1为一个函数,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

当我使用此处描述的技术禁用 JIT 优化时,这些函数的执行时间是相当的。

为什么 x64 抖动在特定示例中失败,我不知道。您可以反汇编优化的 jitter 代码以逐行比较 ASM 指令。也许具有良好 ASM 知识的人可以发现它们的差异。

于 2012-11-07T16:04:23.193 回答
1

当我在我的机器上运行实验时(在发布模式下使用 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) 的笔记本电脑上运行测试。

于 2012-11-04T12:48:36.803 回答