frugal

package module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 4, 2024 License: Apache-2.0 Imports: 10 Imported by: 13

README

Frugal

English | 中文

A very fast dynamic Thrift serializer & deserializer without generating code.

It implements a pure Go version (or reflect version) and a just-in-time(JIT) compilation version. Since the reflect version performs better than the JIT version in most cases and it works on different cpu architectures, so we plan to deprecate the JIT version starting with Go 1.24.

Before Go 1.24:

  • JIT version is the default serializer & deserializer for amd64.
  • reflect version is only enabled when running on !amd64 or windows
  • You can enable the reflect version by specifying os env FRUGAL_NO_JIT=1

Since Go 1.24:

  • reflect version is the default serializer & deserializer.
  • JIT code will be skipped by go build tags.

Features

Code Generation Free

Traditional Thrift serializer and deserializer are based on generated code which is no longer needed since we can make use of struct field tags.

High Performance

Based on the test cases in frugal/tests, Frugal's performance is 1 to 4 times better than Apache Thrift (TBinaryProtocol).

There may be variations between different test cases. Feel free to share your test cases with us.

go version go1.22.5 linux/amd64

goos: linux
goarch: amd64
pkg: github.com/cloudwego/frugal/tests
cpu: Intel(R) Xeon(R) Gold 5118 CPU @ 2.30GHz

Marshal_ApacheThrift/small-4          	 2070745	     584.0 ns/op	 998.32 MB/s	     112 B/op	       1 allocs/op
Marshal_ApacheThrift/medium-4         	   78729	     13680 ns/op	1280.57 MB/s	     112 B/op	       1 allocs/op
Marshal_ApacheThrift/large-4          	    3097	    376184 ns/op	1179.75 MB/s	     620 B/op	       1 allocs/op
Marshal_Frugal_JIT/small-4            	 4939591	     242.1 ns/op	2407.83 MB/s	      13 B/op	       0 allocs/op
Marshal_Frugal_JIT/medium-4           	  160820	      7485 ns/op	2340.29 MB/s	      54 B/op	       0 allocs/op
Marshal_Frugal_JIT/large-4            	    5370	    214258 ns/op	2071.35 MB/s	     338 B/op	       0 allocs/op
Marshal_Frugal_Reflect/small-4        	10171197	     117.3 ns/op	4970.90 MB/s	       0 B/op	       0 allocs/op
Marshal_Frugal_Reflect/medium-4       	  180207	      6644 ns/op	2636.73 MB/s	       0 B/op	       0 allocs/op
Marshal_Frugal_Reflect/large-4        	    6312	    185534 ns/op	2392.04 MB/s	       0 B/op	       0 allocs/op

Unmarshal_ApacheThrift/small-4        	  768525	      1443 ns/op	 403.94 MB/s	    1232 B/op	       5 allocs/op
Unmarshal_ApacheThrift/medium-4       	   24463	     47067 ns/op	 372.19 MB/s	   44816 B/op	     176 allocs/op
Unmarshal_ApacheThrift/large-4        	    1053	   1155725 ns/op	 384.00 MB/s	 1135540 B/op	    4433 allocs/op
Unmarshal_Frugal_JIT/small-4          	 2575767	     466.3 ns/op	1250.36 MB/s	     547 B/op	       2 allocs/op
Unmarshal_Frugal_JIT/medium-4         	   62128	     19333 ns/op	 906.12 MB/s	   19404 B/op	      89 allocs/op
Unmarshal_Frugal_JIT/large-4          	    2328	    496431 ns/op	 893.99 MB/s	  495906 B/op	    2283 allocs/op
Unmarshal_Frugal_Reflect/small-4      	 2770252	     437.2 ns/op	1333.60 MB/s	     544 B/op	       1 allocs/op
Unmarshal_Frugal_Reflect/medium-4     	   64232	     18183 ns/op	 963.45 MB/s	   19945 B/op	      57 allocs/op
Unmarshal_Frugal_Reflect/large-4      	    2325	    496415 ns/op	 894.02 MB/s	  511876 B/op	    1467 allocs/op

What can you do with Frugal ?

Use Frugal as Kitex serializer and deserializer

No more massive serialization and deserialization code, leads to a more tidy project. No more meaningless diff of generated code in code review.

Serialized and Deserialize struct generated by Thriftgo

If you have a Thrift file, and all you need is using Frugal to do serialization and deserialization. You can use thriftgo to generate Go struct, then you can use Frugal.

Serialization and deserialization on a customized Go struct

If you don't want any Thrift files, and you want serialize or deserialize a customized Go struct. You can add some struct field tag to the Go struct, then you can use Frugal.

Usage

Using with Kitex
1. Update Kitex to v0.4.2 or higher version
go get github.com/cloudwego/kitex@latest
2. Generate code with -thrift frugal_tag option

Example:

kitex -thrift frugal_tag -service a.b.c my.thrift

If you don't need codec code, you can use -thrift template=slim option.

kitex -thrift frugal_tag,template=slim -service a.b.c my.thrift
3. Init clients and servers with WithPayloadCodec(thrift.NewThriftFrugalCodec()) option

Client example:

package client

import (
    "context"

    "example.com/kitex_test/client/kitex_gen/a/b/c/echo"
    "github.com/cloudwego/kitex/client"
    "github.com/cloudwego/kitex/pkg/remote/codec/thrift"
)

func Echo() {
    code := thrift.NewThriftCodecWithConfig(thrift.FastRead | thrift.FastWrite | thrift.FrugalRead | thrift.FrugalWrite)
    cli := echo.MustNewClient("a.b.c", client.WithPayloadCodec(codec))
    ...
}

Server example:

package main

import (
    "log"

    "github.com/cloudwego/kitex/server"
    c "example.com/kitex_test/kitex_gen/a/b/c/echo"
    "github.com/cloudwego/kitex/pkg/remote/codec/thrift"
)

func main() {
    code := thrift.NewThriftCodecWithConfig(thrift.FastRead | thrift.FastWrite | thrift.FrugalRead | thrift.FrugalWrite)
    svr := c.NewServer(new(EchoImpl), server.WithPayloadCodec(code))

    err := svr.Run()
    if err != nil {
        log.Println(err.Error())
    }
}
Using with Thrift IDL
Prepare Thrift file

We can define a struct in Thrift file like below:

my.thrift:

struct MyStruct {
    1: string msg
    2: i64 code
}
Use Thriftgo to generate code

Now we have thrift file, we can use Thriftgo with frugal_tag option to generate Go code.

Example:

thriftgo -r -o thrift -g go:frugal_tag,package_prefix=example.com/kitex_test/thrift my.thrift

If you don't need codec code, you can use template=slim option

thriftgo -r -o thrift -g go:frugal_tag,template=slim,package_prefix=example.com/kitex_test/thrift my.thrift
Use Frugal to serialize or deserialize

Now we can use Frugal to serialize or deserialize the struct defined in thrift file.

Example:

package main

import (
    "github.com/cloudwego/frugal"

    "example.com/kitex_test/thrift"
)

func main() {
    ms := &thrift.MyStruct{
        Msg: "my message",
        Code: 1024,
    }
    ...
    buf := make([]byte, frugal.EncodedSize(ms))
    frugal.EncodeObject(buf, nil, ms)
    ...
    got := &thrift.MyStruct{}
    frugal.DecodeObject(buf, got)
    ...
}
Serialization and deserialization on a customized Go struct
Define a Go struct

We can define a struct like this:

type MyStruct struct {
    Msg     string
    Code    int64
    Numbers []int64 
}
Add Frugal tag to struct fields

Frugal tag is like frugal:"1,default,string", 1 is field ID, default is field requiredness, string is field type. Field ID and requiredness is always required, but field type is only required for list, set and enum.

You can add Frugal tag to MyStruct like below:

type MyStruct struct {
    Msg     string  `frugal:"1,default"`
    Code    int64   `frugal:"2,default"`
    Numbers []int64 `frugal:"3,default,list<i64>"`
}

All types example:

type MyEnum int64

type Example struct {
 MyOptBool         *bool            `frugal:"1,optional"`
 MyReqBool         bool             `frugal:"2,required"`
 MyOptByte         *int8            `frugal:"3,optional"`
 MyReqByte         int8             `frugal:"4,required"`
 MyOptI16          *int16           `frugal:"5,optional"`
 MyReqI16          int16            `frugal:"6,required"`
 MyOptI32          *int32           `frugal:"7,optional"`
 MyReqI32          int32            `frugal:"8,required"`
 MyOptI64          *int64           `frugal:"9,optional"`
 MyReqI64          int64            `frugal:"10,required"`
 MyOptString       *string          `frugal:"11,optional"`
 MyReqString       string           `frugal:"12,required"`
 MyOptBinary       []byte           `frugal:"13,optional"`
 MyReqBinary       []byte           `frugal:"14,required"`
 MyOptI64Set       []int64          `frugal:"15,optional,set<i64>"`
 MyReqI64Set       []int64          `frugal:"16,required,set<i64>"`
 MyOptI64List      []int64          `frugal:"17,optional,list<i64>"`
 MyReqI64List      []int64          `frugal:"18,required,list<i64>"`
 MyOptI64StringMap map[int64]string `frugal:"19,optional"`
 MyReqI64StringMap map[int64]string `frugal:"20,required"`
 MyOptEnum         *MyEnum          `frugal:"21,optional,i64"`
 MyReqEnum         *MyEnum          `frugal:"22,optional,i64"`
}
Use Frugal to serialize or deserialize

Example:

package main

import (
    "github.com/cloudwego/frugal"
)

func main() {
    ms := &thrift.MyStruct{
        Msg: "my message",
        Code: 1024,
        Numbers: []int64{0, 1, 2, 3, 4},
    }
    ...
    buf := make([]byte, frugal.EncodedSize(ms))
    frugal.EncodeObject(buf, nil, ms)
    ...
    got := &thrift.MyStruct{}
    frugal.DecodeObject(buf, got)
    ...
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DecodeObject

func DecodeObject(buf []byte, val interface{}) (int, error)

DecodeObject deserializes buf into val with Thrift Binary Protocol.

func EncodeObject

func EncodeObject(buf []byte, w thrift.NocopyWriter, val interface{}) (int, error)

EncodeObject serializes val into buf with Thrift Binary Protocol, with optional Zero-Copy thrift.NocopyWriter. buf must be large enough to contain the entire serialization result.

func EncodedSize

func EncodedSize(val interface{}) int

EncodedSize measures the encoded size of val.

func NoJIT added in v0.2.2

func NoJIT(v bool)

NoJIT disables JIT encoder and decoder explicitly.

This function will be deprecated along with the JIT implementation in the future.

func Pretouch added in v0.1.4

func Pretouch(vt reflect.Type, options ...Option) error

Pretouch compiles vt ahead-of-time to avoid JIT compilation on-the-fly, in order to reduce the first-hit latency.

func SetMaxInlineDepth added in v0.1.4

func SetMaxInlineDepth(depth int) int

SetMaxInlineDepth sets the default maximum inlining depth for all types from now on.

This value can also be configured with the `FRUGAL_MAX_INLINE_DEPTH` environment variable.

The default value of this option is "2".

Returns the old opts.MaxInlineDepth value.

func SetMaxInlineILSize added in v0.1.4

func SetMaxInlineILSize(size int) int

SetMaxInlineILSize sets the default maximum inlining IL instructions for all types from now on.

This value can also be configured with the `FRUGAL_MAX_INLINE_IL_SIZE` environment variable.

The default value of this option is "50000".

Returns the old opts.MaxInlineILSize value.

Types

type Option added in v0.1.4

type Option func(*opts.Options)

Option is the property setter function for opts.Options.

func WithMaxInlineDepth added in v0.1.4

func WithMaxInlineDepth(depth int) Option

WithMaxInlineDepth sets the maximum inlining depth for the JIT compiler.

Increasing of this option makes the compiler inline more aggressively, which gives better runtime performance at the cost of a longer compilation time, and vice versa.

Set this option to "0" disables this limit, which means inlining everything.

The default value of this option is "2".

func WithMaxInlineILSize added in v0.1.4

func WithMaxInlineILSize(size int) Option

WithMaxInlineILSize sets the maximum IL instruction count before not inlining.

Increasing of this option makes the compiler inline more aggressively, which lead to more memory consumptions but better runtime performance, and vice versa.

Set this option to "0" disables this limit, which means unlimited inlining IL buffer.

The default value of this option is "50000".

func WithMaxPretouchDepth added in v0.1.4

func WithMaxPretouchDepth(depth int) Option

WithMaxPretouchDepth controls how deep the compiler goes to compile indirectly referenced types.

Larger depth means more types will be pre-compiled when pretouching, which lead to longer compilation time, but lower runtime JIT latency, and vice versa. You might want to tune this value to strike a balance between compilation time and runtime performance.

The default value "0" means unlimited, which basically turns Frugal into an AOT compiler.

This option is only available when performing pretouch, otherwise it is ignored and do not have any effect.

Directories

Path Synopsis
Package debug is only for JIT encoder/decoder.
Package debug is only for JIT encoder/decoder.
fuzz module
internal
tests module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL