Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
137 lines (106 sloc) 8.21 KB

Performance

TypeShape is intended for real-world applications, and as such performance is a significant aspect of its design. In this article I present a few benchmarks comparing generic programming approaches for common applications.

Unless otherwise stated, all benchmarks use BenchmarkDotNet running .NET Core 3.0 on Linux:

BenchmarkDotNet=v0.11.5, OS=ubuntu 18.04
Intel Xeon CPU E5-2673 v4 2.30GHz, 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.0.100
[Host]     : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT DEBUG
DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT

You can find the benchmarks project here. Without further ado, here are the results:

Pretty-Printer

Given a standard F# value, (combination of primitives, tuples, records and unions) generate a string rendering the value in F# syntax.

We'll be testing the following value:

let testValue : TestType = 
    struct(
        [  [{ A = "value" ; B = 42 ; C = false }]; []; [{ A = "A'" ; B = 0 ; C = true }] ],
        [| [] ; ["A";"B"] |], 
        [| A 42; B; B ; C("value", 0) |])

The following implementations were benchmarked:

Results

Method Mean Error StdDev Ratio RatioSD
'FSharp.Core PrettyPrinter' 2,449.132 us 146.3810 us 388.1817 us 2,034.44 179.25
'Baseline PrettyPrinter' 1.202 us 0.0237 us 0.0439 us 1.00 0.00
'TypeShape PrettyPrinter' 2.182 us 0.0423 us 0.0416 us 1.82 0.09

The bespoke implementation is twice as fast as the TypeShape program, however it is still significantly faster than the default core implementation.

Value Cloner

Given a standard F# value, (combination of primitives, tuples, records and unions) create an equal value whose object graph is completely disconnected. This somewhat artificial example is a good benchmark since it exercises most of the TypeShape surface API (it both reads and creates values).

We'll be cloning the following value:

let testValue : TestType =
    let rs = [ for i in 1 .. 20 -> { A = sprintf "lorem ipsum %d" i ; B = i ; C = i % 2 = 0 } ]
    let ss = [for i in 1 .. 100 -> string i]
    struct([rs; []], [|ss|], [|1 .. 20|])

The following implementations were benchmarked:

Results

Method Mean Error StdDev Ratio RatioSD
'Baseline Cloner' 12.92 us 0.8312 us 2.289 us 1.00 0.00
'TypeShape Cloner' 144.48 us 11.3463 us 32.372 us 11.58 3.82
'TypeShape Unquote Staged Cloner' 1,548.06 us 158.5485 us 464.995 us 126.61 38.88
'TypeShape Compiled Staged Cloner' 22.10 us 2.6079 us 7.313 us 1.77 0.73

The standard TypeShape cloner is an order of magnitude slower than the bespoke implementation, however the compiled staged cloner offers very comparable performance. It certainly demonstrates the promise of staged generic programming applications.

Empty

The empty<'T> : 'T function is a standard utility included in the TypeShape library which builds "zero" values for standard F# types. For example, the expression

empty<{| x : int ; y : {| z : int option ; w : string |} |}>

returns an instance of the supplied type argument with non-null, unpopulated fields:

val it : {| x: int; y: {| y: string; z: int option |} |} =
  { x = 0
    y = { y = ""
          z = None } }

It can be thought of as safe version of Unchecked.defaultof<'T> which can be useful for mocking values in tests:

{ empty<HugeDomainRecord> with InterestingValue1 = "x" ; InterstingValue2 = 42 }

For the purposes of this benchmark, we'll be comparing:

Results

Method Mean Error StdDev Ratio RatioSD
'Baseline Empty' 113.4 ns 3.126 ns 8.969 ns 1.00 0.00
'Reflection Empty' 3,955.1 ns 76.374 ns 71.441 ns 32.35 2.92
'TypeShape Empty' 815.3 ns 16.177 ns 21.596 ns 7.02 0.76

UnionContract

UnionContract is an implementation of a contract pattern for schemaless datastores. It is used for encoding discriminated unions into data that can be easily embedded in common database storage formats.

For this benchmark we will be comparing the following implementations:

Results

Method Mean Error StdDev Ratio RatioSD
'Baseline Union Encoder' 701.4 ns 13.95 ns 24.06 ns 1.00 0.00
'Reflection Union Encoder' 10,867.8 ns 210.06 ns 196.49 ns 15.66 0.62
'TypeShape Union Encoder' 2,363.3 ns 45.65 ns 50.74 ns 3.43 0.17

FsPickler

FsPickler serialization is driven by TypeShape. Please see the relevant performance page in that repo.

You can’t perform that action at this time.