Skip to content

Commit

Permalink
Optimize struct record serialization to not box
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarmil committed Oct 4, 2019
1 parent 5572c09 commit bf52a11
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 14 deletions.
47 changes: 34 additions & 13 deletions src/FSharp.SystemTextJson/Record.Reflection.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ open FSharp.Reflection
open System.Reflection.Emit
open System.Text.Json

type internal Serializer = Action<Utf8JsonWriter, obj, JsonSerializerOptions>
type internal RefobjFieldGetter<'Record, 'Field> = Func<'Record, 'Field>
type internal StructFieldGetter<'Record, 'Field> = delegate of inref<'Record> -> 'Field

type internal RefobjSerializer<'Record> = Action<Utf8JsonWriter, 'Record, JsonSerializerOptions>
type internal StructSerializer<'Record> = delegate of Utf8JsonWriter * inref<'Record> * JsonSerializerOptions -> unit

[<Struct>]
type internal Serializer<'Record> =
| SStruct of s: StructSerializer<'Record>
| SRefobj of n: RefobjSerializer<'Record>

type internal RefobjFieldSetter<'Record, 'Field> = Action<'Record, 'Field>
type internal StructFieldSetter<'Record, 'Field> = delegate of byref<'Record> * 'Field -> unit
Expand All @@ -24,7 +33,7 @@ type internal RecordField<'Record> =
Name: string
Type: Type
Ignore: bool
Serialize: Serializer
Serialize: Serializer<'Record>
Deserialize: Deserializer<'Record>
}

Expand Down Expand Up @@ -74,27 +83,39 @@ module internal RecordReflection =
setter.Invoke(record, value))
|> DRefobj

let private serializer<'Field> (f: FieldInfo) =
let private serializer<'Record, 'Field> (f: FieldInfo) =
let getter =
let dynMethod =
new DynamicMethod(
f.Name,
f.FieldType,
[| typeof<obj> |],
[|
(if f.DeclaringType.IsValueType
then typeof<'Record>.MakeByRefType()
else typeof<'Record>)
|],
typedefof<RecordField<_>>.Module,
skipVisibility = true
)
let gen = dynMethod.GetILGenerator()
gen.Emit(OpCodes.Ldarg_0)
if f.DeclaringType.IsValueType then
gen.Emit(OpCodes.Unbox, f.DeclaringType)
gen.Emit(OpCodes.Ldfld, f)
gen.Emit(OpCodes.Ret)
dynMethod.CreateDelegate(typeof<Func<obj, 'Field>>) :?> Func<obj, 'Field>
Serializer(fun writer record options ->
let v = getter.Invoke(record)
JsonSerializer.Serialize<'Field>(writer, v, options)
)
dynMethod
if f.DeclaringType.IsValueType then
let getter = getter.CreateDelegate(typeof<StructFieldGetter<'Record, 'Field>>) :?> StructFieldGetter<'Record, 'Field>
StructSerializer<'Record>(fun writer record options ->
let v = getter.Invoke(&record)
JsonSerializer.Serialize<'Field>(writer, v, options)
)
|> SStruct
else
let getter = getter.CreateDelegate(typeof<RefobjFieldGetter<'Record, 'Field>>) :?> RefobjFieldGetter<'Record, 'Field>
RefobjSerializer<'Record>(fun writer record options ->
let v = getter.Invoke(record)
JsonSerializer.Serialize<'Field>(writer, v, options)
)
|> SRefobj

let private thisModule = typedefof<RecordField<_>>.Assembly.GetType("System.Text.Json.Serialization.RecordReflection")

Expand All @@ -106,9 +127,9 @@ module internal RecordReflection =
||> Array.map2 (fun f p ->
let serializer =
thisModule.GetMethod("serializer", BindingFlags.Static ||| BindingFlags.NonPublic)
.MakeGenericMethod(p.PropertyType)
.MakeGenericMethod(recordTy, p.PropertyType)
.Invoke(null, [|f|])
:?> Serializer
:?> Serializer<'Record>
let deserializer =
thisModule.GetMethod("deserializer", BindingFlags.Static ||| BindingFlags.NonPublic)
.MakeGenericMethod(recordTy, p.PropertyType)
Expand Down
4 changes: 3 additions & 1 deletion src/FSharp.SystemTextJson/Record.fs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ type JsonRecordConverter<'T>() =
for p in fields do
if not p.Ignore then
writer.WritePropertyName(p.Name)
p.Serialize.Invoke(writer, value, options)
match p.Serialize with
| SStruct p -> p.Invoke(writer, &value, options)
| SRefobj p -> p.Invoke(writer, value, options)
writer.WriteEndObject()

type JsonRecordConverter() =
Expand Down

0 comments on commit bf52a11

Please sign in to comment.