Hello World
Spiga

F#版本的CodeTimer(已支持CPU时钟周期统计)

2009-11-13 10:49 by 老赵, 16978 visits

CodeTimer很好用,自从在今年三月在.NET技术大会上看到Jeffrey Richter用类似的东西之后,我就自己写了一个。不过,当时是用C#写的,现在我需要在F#里做相同的事情就不那么方便了。当然,F#与.NET本是无缝集成,因此C#写的CodeTimer也应该可以被F#使用。不过,我平时在使用CodeTimer时并不是通过程序集引用,而是使用代码复制的方式,因此如果有个F#版本那么应该使用起来更加方便。

代码如下:

#light

module CodeTimer

open System
open System.Diagnostics
open System.Threading
open System.Runtime.InteropServices

[<DllImport("kernel32.dll")>]
extern int QueryThreadCycleTime(IntPtr threadHandle, uint64* cycleTime)

[<DllImport("kernel32.dll")>]
extern IntPtr GetCurrentThread();

let private getCycleCount() = 
    let mutable cycle = 0UL
    let threadHandle = GetCurrentThread()
    QueryThreadCycleTime(threadHandle, &&cycle) |> ignore
    cycle

let time name iteration action =

    if (String.IsNullOrEmpty(name)) then ignore 0 else

    // keep current color
    let currentForeColor = Console.ForegroundColor
    Console.ForegroundColor <- ConsoleColor.Yellow
    printfn "%s" name

    // keep current gc count
    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    let gcCounts =
        [0 .. GC.MaxGeneration]
        |> List.map (fun i -> (i, GC.CollectionCount(i)))
        |> List.fold (fun acc i -> i :: acc) []
        |> List.rev

    // keep cycle count and start watch
    let threadPtr = GetCurrentThread()
    let cycle = getCycleCount()
    let watch = Stopwatch.StartNew()
    
    // run
    for i = 1 to iteration do action();
    
    let cycleUsed = getCycleCount() - cycle
    watch.Stop()

    // restore the color
    Console.ForegroundColor <- currentForeColor;

    // print
    watch.ElapsedMilliseconds.ToString("N0") |> printfn "\tTime Elapsed:\t%sms"
    cycle.ToString("N0") |> printfn "\tCPU Cycles:\t%s"
    gcCounts |> List.iter (fun (i, c) -> 
        printfn "\tGen%i:\t\t%i" i (GC.CollectionCount(i) - c))

    printfn ""

let initialize() =
    Process.GetCurrentProcess().PriorityClass <- ProcessPriorityClass.High
    Thread.CurrentThread.Priority <- ThreadPriority.Highest
    time "" 0 (fun() -> ignore 0)

结果是:

Wait
        Time Elapsed:   684ms
        CPU Cycles:     372,709,908
        Gen0:           0
        Gen1:           0
        Gen2:           0

与C#版本的CodeTimer相比,第一版的F# CodeTimer少算了CPU使用周期的消耗——不是我不想,而是遇到了问题。我当时这样引入P/Invoke的签名:

open System.Runtime.InteropServices

[<DllImport("kernel32.dll")>]
extern int QueryThreadCycleTime(IntPtr threadHandle, uint32* cycleTime)

[<DllImport("kernel32.dll")>]
extern IntPtr GetCurrentThread();

F#在P/Invoke签名中使用*来标记out参数,但是在自定义方法时使用的是byref,这点与C#不同,后者都是使用ref。这个引入看似没有问题,而且普通调用也能得到正常结果:

[<EntryPoint>]
let main args =

    let mutable cycle = 0u
    let threadHandle = CodeTimer.GetCurrentThread()
    CodeTimer.QueryThreadCycleTime(threadHandle, &&cycle) |> ignore

    Console.ReadLine() |> ignore
    0

但是,一旦我把它加为CodeTimer的一个方法,如getCycleCount:

let getCycleCount() = 
    let mutable cycle = 0u
    let threadHandle = GetCurrentThread()
    QueryThreadCycleTime(threadHandle, &&cycle) |> ignore
    cycle

这样调用的时候就会抛出异常:

后经alonesail同学指出,引入QueryThreadCycleTime的时候,第二个参数应该是64位而不是32位无符号整数——我将PULONG64看作PULONG了。改成uint64便没有问题了。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

12 条回复

  1. fix[未注册用户]
    *.*.*.*
    链接

    fix[未注册用户] 2009-11-13 11:04:00

    “因此C#写的CodeTimer也应该可以被C#使用”
    bug!!

  2. 老赵
    admin
    链接

    老赵 2009-11-13 11:11:00

    @fix
    谢谢

  3. kivenhou
    *.*.*.*
    链接

    kivenhou 2009-11-13 11:17:00

    现在F#方面的官方的中文资料是不是很少,或者没有啊?

  4. 老赵
    admin
    链接

    老赵 2009-11-13 11:18:00

    @kivenhou
    是没有。

  5. alonesail[未注册用户]
    *.*.*.*
    链接

    alonesail[未注册用户] 2009-11-13 11:19:00

    QueryThreadCycleTime(IntPtr threadHandle, uint32* cycleTime)
    cycleTime是一个64位整数

  6. 老赵
    admin
    链接

    老赵 2009-11-13 11:24:00

    @alonesail
    果然是LONG64,一会儿用uint64试试看。

  7. 老赵
    admin
    链接

    老赵 2009-11-13 11:26:00

    @alonesail
    果然好了,谢谢,真是个低级错误。
    难道“直接运行”没有问题只是因为没有超过uint32的范围?

  8. GUO Xingwang
    *.*.*.*
    链接

    GUO Xingwang 2009-11-13 12:48:00

    老赵的这个确实很好用,我也在使用。

  9. 左右期限
    *.*.*.*
    链接

    左右期限 2009-11-13 23:09:00

    学习了,楼主很强大

  10. breeze
    *.*.*.*
    链接

    breeze 2009-11-15 19:07:00

    QueryThreadCycleTime
    vista以上版本才能使用的函数

  11. 老赵
    admin
    链接

    老赵 2009-11-15 19:18:00

    @breeze
    没错。

  12. 王洪剑
    *.*.*.*
    链接

    王洪剑 2009-11-23 19:03:00

    牛人都喜欢用“Jeffrey”,改日我也改个"Jeffrey Wang"

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我