Skip to content

Commit bf52a11

Browse files
committed
Optimize struct record serialization to not box
1 parent 5572c09 commit bf52a11

File tree

2 files changed

+37
-14
lines changed

2 files changed

+37
-14
lines changed

src/FSharp.SystemTextJson/Record.Reflection.fs

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@ open FSharp.Reflection
66
open System.Reflection.Emit
77
open System.Text.Json
88

9-
type internal Serializer = Action<Utf8JsonWriter, obj, JsonSerializerOptions>
9+
type internal RefobjFieldGetter<'Record, 'Field> = Func<'Record, 'Field>
10+
type internal StructFieldGetter<'Record, 'Field> = delegate of inref<'Record> -> 'Field
11+
12+
type internal RefobjSerializer<'Record> = Action<Utf8JsonWriter, 'Record, JsonSerializerOptions>
13+
type internal StructSerializer<'Record> = delegate of Utf8JsonWriter * inref<'Record> * JsonSerializerOptions -> unit
14+
15+
[<Struct>]
16+
type internal Serializer<'Record> =
17+
| SStruct of s: StructSerializer<'Record>
18+
| SRefobj of n: RefobjSerializer<'Record>
1019

1120
type internal RefobjFieldSetter<'Record, 'Field> = Action<'Record, 'Field>
1221
type internal StructFieldSetter<'Record, 'Field> = delegate of byref<'Record> * 'Field -> unit
@@ -24,7 +33,7 @@ type internal RecordField<'Record> =
2433
Name: string
2534
Type: Type
2635
Ignore: bool
27-
Serialize: Serializer
36+
Serialize: Serializer<'Record>
2837
Deserialize: Deserializer<'Record>
2938
}
3039

@@ -74,27 +83,39 @@ module internal RecordReflection =
7483
setter.Invoke(record, value))
7584
|> DRefobj
7685

77-
let private serializer<'Field> (f: FieldInfo) =
86+
let private serializer<'Record, 'Field> (f: FieldInfo) =
7887
let getter =
7988
let dynMethod =
8089
new DynamicMethod(
8190
f.Name,
8291
f.FieldType,
83-
[| typeof<obj> |],
92+
[|
93+
(if f.DeclaringType.IsValueType
94+
then typeof<'Record>.MakeByRefType()
95+
else typeof<'Record>)
96+
|],
8497
typedefof<RecordField<_>>.Module,
8598
skipVisibility = true
8699
)
87100
let gen = dynMethod.GetILGenerator()
88101
gen.Emit(OpCodes.Ldarg_0)
89-
if f.DeclaringType.IsValueType then
90-
gen.Emit(OpCodes.Unbox, f.DeclaringType)
91102
gen.Emit(OpCodes.Ldfld, f)
92103
gen.Emit(OpCodes.Ret)
93-
dynMethod.CreateDelegate(typeof<Func<obj, 'Field>>) :?> Func<obj, 'Field>
94-
Serializer(fun writer record options ->
95-
let v = getter.Invoke(record)
96-
JsonSerializer.Serialize<'Field>(writer, v, options)
97-
)
104+
dynMethod
105+
if f.DeclaringType.IsValueType then
106+
let getter = getter.CreateDelegate(typeof<StructFieldGetter<'Record, 'Field>>) :?> StructFieldGetter<'Record, 'Field>
107+
StructSerializer<'Record>(fun writer record options ->
108+
let v = getter.Invoke(&record)
109+
JsonSerializer.Serialize<'Field>(writer, v, options)
110+
)
111+
|> SStruct
112+
else
113+
let getter = getter.CreateDelegate(typeof<RefobjFieldGetter<'Record, 'Field>>) :?> RefobjFieldGetter<'Record, 'Field>
114+
RefobjSerializer<'Record>(fun writer record options ->
115+
let v = getter.Invoke(record)
116+
JsonSerializer.Serialize<'Field>(writer, v, options)
117+
)
118+
|> SRefobj
98119

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

@@ -106,9 +127,9 @@ module internal RecordReflection =
106127
||> Array.map2 (fun f p ->
107128
let serializer =
108129
thisModule.GetMethod("serializer", BindingFlags.Static ||| BindingFlags.NonPublic)
109-
.MakeGenericMethod(p.PropertyType)
130+
.MakeGenericMethod(recordTy, p.PropertyType)
110131
.Invoke(null, [|f|])
111-
:?> Serializer
132+
:?> Serializer<'Record>
112133
let deserializer =
113134
thisModule.GetMethod("deserializer", BindingFlags.Static ||| BindingFlags.NonPublic)
114135
.MakeGenericMethod(recordTy, p.PropertyType)

src/FSharp.SystemTextJson/Record.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ type JsonRecordConverter<'T>() =
7070
for p in fields do
7171
if not p.Ignore then
7272
writer.WritePropertyName(p.Name)
73-
p.Serialize.Invoke(writer, value, options)
73+
match p.Serialize with
74+
| SStruct p -> p.Invoke(writer, &value, options)
75+
| SRefobj p -> p.Invoke(writer, value, options)
7476
writer.WriteEndObject()
7577

7678
type JsonRecordConverter() =

0 commit comments

Comments
 (0)