frostfs-api-go/util/protogen/internalgengo/json.go
Evgenii Stratonikov 5e1c6a908f [#111] protogen: Emit slice of messages without a pointer
```
goos: linux
goarch: amd64
pkg: git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs
cpu: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
                                              │      old      │                 new                  │
                                              │    sec/op     │    sec/op     vs base                │
ObjectIDSlice/0_elements/to_grpc_message-8       3.193n ±  2%   3.242n ±  0%   +1.50% (p=0.034 n=10)
ObjectIDSlice/0_elements/from_grpc_message-8     3.197n ±  2%   3.343n ±  1%   +4.57% (p=0.000 n=10)
ObjectIDSlice/0_elements/marshal-8               5.666n ±  3%   5.642n ±  0%   -0.42% (p=0.000 n=10)
ObjectIDSlice/1_elements/to_grpc_message-8       53.10n ±  6%   29.78n ± 12%  -43.92% (p=0.000 n=10)
ObjectIDSlice/1_elements/from_grpc_message-8     28.99n ±  5%   29.77n ±  7%        ~ (p=0.165 n=10)
ObjectIDSlice/1_elements/marshal-8               49.08n ±  7%   50.72n ±  6%        ~ (p=0.218 n=10)
ObjectIDSlice/50_elements/to_grpc_message-8     1652.5n ±  7%   277.2n ±  1%  -83.22% (p=0.000 n=10)
ObjectIDSlice/50_elements/from_grpc_message-8    261.2n ± 11%   226.7n ± 15%  -13.19% (p=0.003 n=10)
ObjectIDSlice/50_elements/marshal-8              1.512µ ±  6%   1.514µ ±  6%        ~ (p=0.955 n=10)
geomean                                          52.15n         39.99n        -23.31%

                                              │      old       │                  new                   │
                                              │      B/op      │     B/op      vs base                  │
ObjectIDSlice/0_elements/to_grpc_message-8        0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/0_elements/from_grpc_message-8      0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/0_elements/marshal-8                0.000 ± 0%       0.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/1_elements/to_grpc_message-8        32.00 ± 0%       24.00 ± 0%  -25.00% (p=0.000 n=10)
ObjectIDSlice/1_elements/from_grpc_message-8      24.00 ± 0%       24.00 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/1_elements/marshal-8                48.00 ± 0%       48.00 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/50_elements/to_grpc_message-8     1.578Ki ± 0%     1.250Ki ± 0%  -20.79% (p=0.000 n=10)
ObjectIDSlice/50_elements/from_grpc_message-8   1.250Ki ± 0%     1.250Ki ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/50_elements/marshal-8             2.000Ki ± 0%     2.000Ki ± 0%        ~ (p=1.000 n=10) ¹
geomean                                                      ²                  -5.62%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                              │      old      │                 new                  │
                                              │   allocs/op   │ allocs/op   vs base                  │
ObjectIDSlice/0_elements/to_grpc_message-8       0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/0_elements/from_grpc_message-8     0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/0_elements/marshal-8               0.000 ± 0%     0.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/1_elements/to_grpc_message-8       2.000 ± 0%     1.000 ± 0%  -50.00% (p=0.000 n=10)
ObjectIDSlice/1_elements/from_grpc_message-8     1.000 ± 0%     1.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/1_elements/marshal-8               1.000 ± 0%     1.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/50_elements/to_grpc_message-8     51.000 ± 0%     1.000 ± 0%  -98.04% (p=0.000 n=10)
ObjectIDSlice/50_elements/from_grpc_message-8    1.000 ± 0%     1.000 ± 0%        ~ (p=1.000 n=10) ¹
ObjectIDSlice/50_elements/marshal-8              1.000 ± 0%     1.000 ± 0%        ~ (p=1.000 n=10) ¹
geomean                                                     ²               -40.18%                ²
¹ all samples are equal
² summaries must be >0 to compute geomean
```

Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2024-08-28 11:53:08 +03:00

228 lines
6.1 KiB
Go

package internalgengo
import (
"fmt"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/reflect/protoreflect"
)
var (
jwriterPackage = protogen.GoImportPath("github.com/mailru/easyjson/jwriter")
jlexerPackage = protogen.GoImportPath("github.com/mailru/easyjson/jlexer")
)
func emitJSONMethods(g *protogen.GeneratedFile, msg *protogen.Message) {
emitJSONMarshal(g, msg)
emitJSONUnmarshal(g, msg)
}
func emitJSONUnmarshal(g *protogen.GeneratedFile, msg *protogen.Message) {
g.P("// UnmarshalJSON implements the json.Unmarshaler interface.")
g.P("func (x *", msg.GoIdent.GoName, ") UnmarshalJSON(data []byte) error {")
g.P("r := ", jlexerPackage.Ident("Lexer"), "{Data: data}")
g.P("x.UnmarshalEasyJSON(&r)")
g.P("return r.Error()")
g.P("}")
g.P("func (x *", msg.GoIdent.GoName, ") UnmarshalEasyJSON(in *", jlexerPackage.Ident("Lexer"), ") {")
g.P("isTopLevel := in.IsStart()")
g.P("if in.IsNull() {")
g.P("if isTopLevel { in.Consumed() }")
g.P("in.Skip()")
g.P("return")
g.P("}")
g.P("in.Delim('{')")
g.P("for !in.IsDelim('}') {")
g.P("key := in.UnsafeFieldName(false)")
g.P("in.WantColon()")
g.P("if in.IsNull() { in.Skip(); in.WantComma(); continue }")
g.P("switch key {")
for _, f := range msg.Fields {
g.P(`case "`, fieldJSONName(f), `":`)
if f.Oneof != nil {
g.P("xx := new(", f.GoIdent, ")")
g.P("x." + f.Oneof.GoName + " = xx")
emitJSONFieldRead(g, f, "xx")
continue
}
emitJSONFieldRead(g, f, "x")
}
g.P("}")
g.P("in.WantComma()")
g.P("}")
g.P("in.Delim('}')")
g.P("if isTopLevel { in.Consumed() }")
g.P("}")
}
func emitJSONFieldRead(g *protogen.GeneratedFile, f *protogen.Field, name string) {
g.P("{")
defer g.P("}")
if f.Desc.IsList() {
g.P("var f ", fieldType(g, f)[2:])
g.P("var list ", fieldType(g, f))
g.P("in.Delim('[')")
defer g.P("in.Delim(']')")
g.P("for !in.IsDelim(']') {")
} else {
g.P("var f ", fieldType(g, f))
}
var template string
switch f.Desc.Kind() {
case protoreflect.BoolKind:
template = "%s = in.Bool()"
case protoreflect.EnumKind:
g.Import(strconvPackage)
enumType := fieldType(g, f).String()
g.P("var parsedValue " + enumType)
g.P(`switch v := in.Interface().(type) {
case string:
if vv, ok := `+enumType+`_value[v]; ok {
parsedValue = `+enumType+`(vv)
break
}
vv, err := `, strconvPackage.Ident("ParseInt"), `(v, 10, 32)
if err != nil {
in.AddError(err)
return
}
parsedValue = `+enumType+`(vv)
case float64:
parsedValue = `+enumType+`(v)
}`)
template = "%s = parsedValue"
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
template = "%s = in.Int32()"
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
template = "%s = in.Uint32()"
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
template = "%s = in.Int64()"
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
template = "%s = in.Uint64()"
case protoreflect.FloatKind:
template = "%s = in.Float32()"
case protoreflect.DoubleKind:
template = "%s = in.Float64()"
case protoreflect.StringKind:
template = "%s = in.String()"
case protoreflect.BytesKind:
template = "%s = in.Bytes()"
case protoreflect.MessageKind:
if f.Desc.IsList() {
g.P("f = ", fieldType(g, f)[2:], "{}")
} else {
g.P("f = new(", fieldType(g, f)[1:], ")")
}
template = "%s.UnmarshalEasyJSON(in)"
case protoreflect.GroupKind:
panic("unimplemented")
}
g.P(fmt.Sprintf(template, "f"))
if f.Desc.IsList() {
g.P("list = append(list, f)")
g.P("in.WantComma()")
g.P("}")
g.P(name, ".", f.GoName, " = list")
} else {
g.P(name, ".", f.GoName, " = f")
}
}
func emitJSONMarshal(g *protogen.GeneratedFile, msg *protogen.Message) {
g.P("// MarshalJSON implements the json.Marshaler interface.")
g.P("func (x *", msg.GoIdent.GoName, ") MarshalJSON() ([]byte, error) {")
g.P("w := ", jwriterPackage.Ident("Writer"), "{}")
g.P("x.MarshalEasyJSON(&w)")
g.P("return w.Buffer.BuildBytes(), w.Error")
g.P("}")
g.P("func (x *", msg.GoIdent.GoName, ") MarshalEasyJSON(out *", jwriterPackage.Ident("Writer"), ") {")
g.P(`if x == nil { out.RawString("null"); return }`)
g.P("out.RawByte('{')")
for i, f := range msg.Fields {
if f.Oneof != nil {
if f.Oneof.Fields[0] != f {
continue
}
g.P("switch xx := x.", f.Oneof.GoName, ".(type) {")
for _, ff := range f.Oneof.Fields {
g.P("case *", ff.GoIdent, ":")
emitJSONFieldWrite(g, ff, "xx", i == 0)
}
g.P("}")
continue
}
emitJSONFieldWrite(g, f, "x", i == 0)
}
g.P("out.RawByte('}')")
g.P("}")
}
func emitJSONFieldWrite(g *protogen.GeneratedFile, f *protogen.Field, name string, first bool) {
g.P("{")
defer g.P("}")
g.P("const prefix string = ", `",\"`, fieldJSONName(f), `\":"`)
if first {
g.P("out.RawString(prefix[1:])")
} else {
g.P("out.RawString(prefix)")
}
selector := name + "." + f.GoName
if f.Desc.IsList() {
selector += "[i]"
g.P("out.RawByte('[')")
defer g.P("out.RawByte(']')")
g.P("for i := range ", name, ".", f.GoName, " {")
g.P("if i != 0 { out.RawByte(',') }")
defer g.P("}")
}
var template string
switch f.Desc.Kind() {
case protoreflect.BoolKind:
template = "out.Bool(%s)"
case protoreflect.EnumKind:
template = "out.Int32(int32(%s))"
case protoreflect.Int32Kind, protoreflect.Sint32Kind, protoreflect.Sfixed32Kind:
template = "out.Int32(%s)"
case protoreflect.Uint32Kind, protoreflect.Fixed32Kind:
template = "out.Uint32(%s)"
case protoreflect.Int64Kind, protoreflect.Sint64Kind, protoreflect.Sfixed64Kind:
template = "out.Int64(%s)"
case protoreflect.Uint64Kind, protoreflect.Fixed64Kind:
template = "out.Uint64(%s)"
case protoreflect.FloatKind:
template = "out.Float32(%s)"
case protoreflect.DoubleKind:
template = "out.Float64(%s)"
case protoreflect.StringKind:
template = "out.String(%s)"
case protoreflect.BytesKind:
template = "out.Base64Bytes(%s)"
case protoreflect.MessageKind:
template = "%s.MarshalEasyJSON(out)"
case protoreflect.GroupKind:
panic("unimplemented")
}
g.P(fmt.Sprintf(template, selector))
}
func fieldJSONName(f *protogen.Field) string {
if f.Desc.HasJSONName() {
return f.Desc.JSONName()
}
return string(f.Desc.Name())
}