F#奇妙游(7):也来整点测评
性能测试
作为一个不事生产和创造但是热爱学习和思考的编程爱好者,最喜欢看和最喜欢做的通常都是比来比去、拉拉扯扯,所以性能测试是必不可少的。
你看那些云玩家、云车神、云计算爱好者,孜孜不倦的都到处看评测报告,动手能力强的都在写评测报告。这就是我的观点:不能让人做决策,也要让人感觉自己在做决策。因为智人实在是太喜欢做决策,人均国师。
让人评测、替人决策是第一生产力。
F#作为.NET语言,当然也有高大上的性能测试工具,BenchmarkDotNet,这个工具是.NET Core官方推荐的性能测试工具,它的特点是简单易用,而且支持多种运行时,包括.NET Core、.NET Framework、Mono、CoreRT、NativeAOT等等。
帮工具吹牛我这里就不重复了,使用的步骤在这里。
F#底层大冒险
这一篇不小心涉及到了.NET的底层编程实践,感觉是没学会走就要飞奔。但是评测最重要。
.NET在机器码之上精心设计了一层抽象,叫做IL,Intermediate Language,中间语言。这个中间语言是一种类似于汇编的语言,但是比汇编更高级,比如支持面向对象、支持泛型等等。这个中间语言是跨平台的,也就是说,不管你是在Windows、Linux、MacOS上编译,都会编译成这个中间语言。
但是.NET与JVM平台不同的是,它还提供了大量的直接在底层干活的手段。比如说Span<T>
,这个类型就是直接在底层干活的,它的底层实现是一个指针,指向一段连续的内存。这个类型在.NET Core 2.1中引入,是为了提高内存操作的效率,因为.NET Core 2.1中引入了内存池,这个内存池是为了减少GC的压力,但是使用内存池的时候,就需要直接操作内存,而不是通过GC来操作内存。
那么我们就来评测下Span<T>
的性能。
代码
这个评测代码是一个console的app。
dotnet new console -lang F# -o SpanBenchmark
然后添加依赖:
dotnet add package BenchmarkDotNet
然后就可以写代码了:
open System
open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Jobs
open BenchmarkDotNet.Running
module Parsing =
let getNumbers (str: string) (delim: char) =
let idx = str.IndexOf delim
let first = Int32.Parse (str.Substring (0, idx))
let second = Int32.Parse (str.Substring (idx+1))
first, second
let getNumbers2 (str: string) (delim: char) =
let sp = str.AsSpan ()
let idx = sp.IndexOf delim
let first = Int32.Parse (sp.Slice (0, idx))
let second = Int32.Parse (sp.Slice (idx+1))
first, second
[<SimpleJob(RuntimeMoniker.Net70)>]
[<SimpleJob(RuntimeMoniker.Net80)>]
[<SimpleJob(RuntimeMoniker.NativeAot70)>]
[<SimpleJob(RuntimeMoniker.NativeAot80)>]
type ParsingBench() =
let str = "123, 456"
let delim = ','
[<Benchmark>]
member _.GetNumbers() = Parsing.getNumbers str delim
[<Benchmark>]
member _.GetNumbers2() = Parsing.getNumbers2 str delim
[<EntryPoint>]
let main argv =
let summary = BenchmarkRunner.Run<ParsingBench> ()
0
然后运行:
dotnet run -c Release
就可以看到结果。
Results
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19045.3086/22H2/2022Update)
12th Gen Intel Core i7-12700H, 1 CPU, 20 logical and 14 physical cores
.NET SDK=8.0.100-preview.5.23303.2
[Host] : .NET 7.0.8 (7.0.823.31807), X64 RyuJIT AVX2 DEBUG
.NET 7.0 : .NET 7.0.8 (7.0.823.31807), X64 RyuJIT AVX2
.NET 8.0 : .NET 8.0.0 (8.0.23.28008), X64 RyuJIT AVX2
NativeAOT 7.0 : .NET 7.0.5-servicing.23174.5, X64 NativeAOT AVX2
NativeAOT 8.0 : .NET 8.0.0-preview.5.23280.8, X64 NativeAOT AVX2
Method | Job | Runtime | Mean | Error | StdDev |
---|---|---|---|---|---|
GetNumbers | .NET 7.0 | .NET 7.0 | 29.08 ns | 0.250 ns | 0.195 ns |
GetNumbers2 | .NET 7.0 | .NET 7.0 | 19.03 ns | 0.044 ns | 0.039 ns |
GetNumbers | .NET 8.0 | .NET 8.0 | 23.10 ns | 0.067 ns | 0.056 ns |
GetNumbers2 | .NET 8.0 | .NET 8.0 | 14.86 ns | 0.069 ns | 0.064 ns |
GetNumbers | NativeAOT 7.0 | NativeAOT 7.0 | 26.90 ns | 0.065 ns | 0.061 ns |
GetNumbers2 | NativeAOT 7.0 | NativeAOT 7.0 | 18.42 ns | 0.085 ns | 0.079 ns |
GetNumbers | NativeAOT 8.0 | NativeAOT 8.0 | 26.99 ns | 0.061 ns | 0.057 ns |
GetNumbers2 | NativeAOT 8.0 | NativeAOT 8.0 | 18.91 ns | 0.154 ns | 0.144 ns |
这个结果还是挺有趣的。
Span<T>
确实很快,快大概三分之一- .NET 8.0比.NET 7.0快了大概一丢丢
- .NET 8.0里面,AOT居然更慢
总结
- 这就是.NET的魅力,你可以选择性能,也可以选择开发效率(并不是)
- F#背靠着大的平台,还是可能有点实际用途的,这也是Clojure、Scala等语言比古早的Lisp、Haskell等语言更有可能被工业界采用的原因
- 这就是.NET的魅力,你可以选择性能,也可以选择开发效率(并不是)
- F#背靠着大的平台,还是可能有点实际用途的,这也是Clojure、Scala等语言比古早的Lisp、Haskell等语言更有可能被工业界采用的原因
- .NET 8.0的AOT肿么回事,果然是preview版本
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~