diff --git a/FSharp.Json.Benchmarks/.gitignore b/FSharp.Json.Benchmarks/.gitignore new file mode 100644 index 0000000..1c2dac6 --- /dev/null +++ b/FSharp.Json.Benchmarks/.gitignore @@ -0,0 +1 @@ +BenchmarkDotNet.Artifacts \ No newline at end of file diff --git a/FSharp.Json.Benchmarks/FSharp.Json.Benchmarks.fsproj b/FSharp.Json.Benchmarks/FSharp.Json.Benchmarks.fsproj new file mode 100644 index 0000000..d372552 --- /dev/null +++ b/FSharp.Json.Benchmarks/FSharp.Json.Benchmarks.fsproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp2.0 + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/FSharp.Json.Benchmarks/Main.fs b/FSharp.Json.Benchmarks/Main.fs new file mode 100644 index 0000000..737adf7 --- /dev/null +++ b/FSharp.Json.Benchmarks/Main.fs @@ -0,0 +1,11 @@ +module FSharp.Json.Benchmarks.Main + +open System.Reflection +open BenchmarkDotNet.Running + +[] +let main args = + let assembly = Assembly.GetExecutingAssembly() + let switcher = new BenchmarkSwitcher(assembly) + let summaries = switcher.Run args + 0 \ No newline at end of file diff --git a/FSharp.Json.Benchmarks/Serializers.fs b/FSharp.Json.Benchmarks/Serializers.fs new file mode 100644 index 0000000..fd60049 --- /dev/null +++ b/FSharp.Json.Benchmarks/Serializers.fs @@ -0,0 +1,75 @@ +namespace FSharp.Json.Benchmarks + +open System.IO +open System.Threading + +open BenchmarkDotNet.Attributes +open FSharp.Json + +type ISerializer = + abstract Name : string + abstract Serialize : Stream -> 'T -> unit + abstract Deserialize : Stream -> 'T + +type JsonDotNetSerializer () = + let jdn = Newtonsoft.Json.JsonSerializer.Create() + + interface ISerializer with + member __.Name = "Json.Net" + member __.Serialize (stream : Stream) (x : 'T) = + use writer = new StreamWriter(stream) + jdn.Serialize(writer, x) + writer.Flush() + + member __.Deserialize (stream : Stream) : 'T = + use reader = new StreamReader(stream) + jdn.Deserialize(reader, typeof<'T>) :?> 'T + +type FSharpJsonSerializer() = + let serializerConfig : JsonConfig = + { + unformatted = true + serializeNone = SerializeNone.Null + deserializeOption = DeserializeOption.AllowOmit + jsonFieldNaming = id + allowUntyped = true + enumValue = EnumMode.Name + } + + interface ISerializer with + member __.Name = "FSharp.Json" + member __.Serialize (stream : Stream) (x : 'T) = + let json = FSharp.Json.Json.serializeEx serializerConfig x + let bytes = System.Text.Encoding.ASCII.GetBytes json + stream.Write(bytes, 0, bytes.Length) + + member __.Deserialize (stream : Stream) : 'T = + use sr = new StreamReader(stream) + let json = sr.ReadToEnd() + FSharp.Json.Json.deserializeEx serializerConfig json + +module Serializer = + + let memoryStream = + new ThreadLocal<_>(fun () -> + { new MemoryStream() with + override __.Close() = () }) + + let roundTrip (serializer : ISerializer) (value : 'T) = + let m = memoryStream.Value + m.Position <- 0L + serializer.Serialize m value + m.Position <- 0L + let _ = serializer.Deserialize<'T> m + () + +[] +[] +type RoundtripBenchmark<'T>(value : 'T) = + let nsj = new JsonDotNetSerializer() + let jfs = new FSharpJsonSerializer() + + [] + member __.NewtonsoftJson() = Serializer.roundTrip nsj value + [] + member __.FSharpJson() = Serializer.roundTrip jfs value \ No newline at end of file diff --git a/FSharp.Json.Benchmarks/Types.fs b/FSharp.Json.Benchmarks/Types.fs new file mode 100644 index 0000000..31799a6 --- /dev/null +++ b/FSharp.Json.Benchmarks/Types.fs @@ -0,0 +1,177 @@ +namespace FSharp.Json.Benchmarks + +open System +open FsCheck + +module Poco = + [] + type T = + { A : int ; B : string ; C : bool ; D : byte[] ; + F : DateTimeOffset ; G : TimeSpan ; H : Guid } + + let value = { A = 42 ; B = "lorem ipsum" ; C = true ; D = [|1uy .. 20uy|] + F = DateTimeOffset(2018,1,1,23,11,12,TimeSpan.Zero) ; + G = TimeSpan.FromDays 30. + H = Guid.Empty } + + type PocoRoundtrip() = + inherit RoundtripBenchmark(value) + +module FloatArray = + type T = float[] + + let value = + [| for i in 1 .. 100 -> float i / float 100. + yield Double.Epsilon + yield Double.PositiveInfinity + yield Double.NaN + yield Double.NegativeInfinity |] + + type FloatArrayRoundtrip() = + inherit RoundtripBenchmark(value) + +module BoxedArray = + type T = obj[] + + let value : T = [|box 1; box "foo" ; box true ; box(1,2) ; box (ref 42) |] + + type BoxedArrayRoundtrip() = + inherit RoundtripBenchmark(value) + +module Array3D = + type T = float [,,] + + let value : T = Array3D.init 20 20 20 (fun i j k -> 0.1 * float i + float j + 10. * float k) + + type Array3DRoundtrip() = + inherit RoundtripBenchmark(value) + +module LargeTuple = + type T = string * int * int * int * bool * string * (float * int list) option * int * int * int * string + let value : T = ("lorem ipsum dolor", 1, 2, 3, true, "", Some(3.14, [2]), 3, 2, 1, "lorem") + + type LargeTupleRoundtrip() = + inherit RoundtripBenchmark(value) + +module FSharpList = + type T = int list + let value : T = [1..1000] + + type FSharpListRoundtrip() = + inherit RoundtripBenchmark(value) + +module Dictionary = + open System.Collections.Generic + open Poco + + type T = Dictionary + + let mkDict size = + let d = new T() + for i = 1 to size do + let key = sprintf "key-%d" i + let value = { + A = i ; + B = sprintf "value-%d" i ; + C = i % 2 = 0 ; + D = [|byte i|] + F = DateTimeOffset(2018,1,1,23,11,12,TimeSpan.Zero) ; + G = TimeSpan.FromDays 30. + H = Guid.Empty + } + + d.Add(key, value) + d + + let value = mkDict 1000 + + type DictionaryRoundtrip() = + inherit RoundtripBenchmark(value) + +module ExceptionBench = + + let rec mkExn d = + if d = 0 then raise (FormatException "kaboom!") + else + 1 + mkExn(d - 1) + + let exn = try mkExn 20 |> ignore ; failwith "" with :? FormatException as e -> e + + type ExceptionRoundtrip() = + inherit RoundtripBenchmark(exn) + +module LargeObject = + type Foo = { A : int ; B : string ; C : bool } + type Bar = A of int * string | B of Foo | C + type T = Bar list list option * string list * byte [] + + let value : T [] = + Arb.from.Generator |> Gen.sampleWithSeed (Rnd 2019UL) 20 20 |> Seq.toArray + + type LargeFSharpValueRoundtrip() = + inherit RoundtripBenchmark(value) + + +module ISerializable = + open System.Runtime.Serialization + + type T(x : int, y : string, z : bool) = + interface ISerializable with + member __.GetObjectData(si, _) = + si.AddValue("x", x) + si.AddValue("y", y) + si.AddValue("z", z) + + new (si : SerializationInfo, _ : StreamingContext) = + let inline get x = si.GetValue(x, typeof<'T>) :?> 'T + new T(get "x", get "y", get "z") + + let instance = new T(42, "lorem ipsum", true) + + type ISerializableRoundtrip() = + inherit RoundtripBenchmark(instance) + + +module FSharpBinTree = + type Tree = Node | Leaf of int * Tree * Tree + + let rec mkTree d = + if d = 0 then Node + else + let t = mkTree (d-1) + Leaf(d, mkTree(d-1), mkTree(d-1)) + + let value = mkTree 10 + + type FSharpBinaryRoundtrip() = + inherit RoundtripBenchmark(value) + +module FSharpSet = + let value = set [1 .. 40] + + type FSharpSetRoundtrip() = + inherit RoundtripBenchmark>(value) + + +module FSharpMap = + let value = [1 .. 40] |> Seq.map (fun i -> string i, i) |> Map.ofSeq + + type FSharpMapRountrip() = + inherit RoundtripBenchmark>(value) + + +module MemberInfo = + open System.Reflection + open FSharp.Quotations.Patterns + + type private Foo = + static member Method(x : int) = () + static member Method<'T>(x : 'T, y : Foo) = () + + let value : MemberInfo = + match <@ Foo.Method("string", Unchecked.defaultof<_>) @> with + | Call(_,m,_) -> m :> _ + | _ -> failwith "impossible" + + type MemberInfoRoundtrip() = + inherit RoundtripBenchmark(value) \ No newline at end of file diff --git a/FSharp.Json.sln b/FSharp.Json.sln index 8222946..4e65d02 100644 --- a/FSharp.Json.sln +++ b/FSharp.Json.sln @@ -13,6 +13,8 @@ ProjectSection(SolutionItems) = preProject RELEASE_NOTES.md = RELEASE_NOTES.md EndProjectSection EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Json.Benchmarks", "FSharp.Json.Benchmarks\FSharp.Json.Benchmarks.fsproj", "{B36BCE2A-84E8-4A34-9BF5-2ECB5DC91F58}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,5 +29,9 @@ Global {5D95196B-06AF-4158-9FF0-FAF132900A21}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D95196B-06AF-4158-9FF0-FAF132900A21}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D95196B-06AF-4158-9FF0-FAF132900A21}.Release|Any CPU.Build.0 = Release|Any CPU + {B36BCE2A-84E8-4A34-9BF5-2ECB5DC91F58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B36BCE2A-84E8-4A34-9BF5-2ECB5DC91F58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B36BCE2A-84E8-4A34-9BF5-2ECB5DC91F58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B36BCE2A-84E8-4A34-9BF5-2ECB5DC91F58}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal