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
这样调用的时候就会抛出异常:
![o_fsharp-pinvoke-error.jpg](https://i-blog.csdnimg.cn/blog_migrate/9f69744b8f4ae37087405af4d96fb818.jpeg)
后经alonesail同学指出,引入QueryThreadCycleTime的时候,第二个参数应该是64位而不是32位无符号整数——我将PULONG64看作PULONG了。改成uint64便没有问题了。