Closed jan-bar closed 1 year ago
Thanks! PR is apprecated.
Thanks! PR is apprecated.
This piece involves logic such as ast source code, I am not very familiar with it, and I will study it after the new year. Happy New Year.
Add the following code in the following file tools/goctl/model/sql/converter/types_test.go
func TestGenSortGo(t *testing.T) {
val := make(map[string]struct{})
for _, v := range unsignedTypeMap {
val[v] = struct{}{}
}
for _, v := range commonMysqlDataTypeMapInt {
val[v] = struct{}{}
}
for _, v := range commonMysqlDataTypeMapString {
val[v] = struct{}{}
}
val["sql.NullInt64"] = struct{}{}
val["sql.NullInt32"] = struct{}{}
val["sql.NullFloat64"] = struct{}{}
val["sql.NullBool"] = struct{}{}
val["sql.NullString"] = struct{}{}
val["sql.NullTime"] = struct{}{}
fw, err := os.Create("sort.go")
if err != nil {
panic(err)
}
defer fw.Close()
fw.WriteString(`package converter
import ( "database/sql" "time"
"github.com/lib/pq"
)
//go:generate fieldalignment -fix sort.go type FieldSort struct { `) i := 0 for k := range val { fmt.Fprintf(fw, "\tF%02d %s\n", i, k) i++ } fw.WriteString("}") }
2. executive command: `cd tools/goctl/model/sql/converter && go test -v -run TestGenSortGo`, Generate `sort.go`
3. Modify the `optimalOrder` method of the above file as follows `golang.org/x/tools/go/analysis/passes/fieldalignment/fieldalignment.go`
```go
func optimalOrder(str *types.Struct, sizes *gcSizes) (*types.Struct, []int) {
nf := str.NumFields()
type elem struct {
name string // add name field
index int
alignof int64
sizeof int64
ptrdata int64
}
elems := make([]elem, nf)
for i := 0; i < nf; i++ {
field := str.Field(i)
ft := field.Type()
ss := ft.String()
if si := strings.LastIndexByte(ss, '/'); si > 0 {
ss = ss[si+1:]
}
elems[i] = elem{
ss,
i,
sizes.Alignof(ft),
sizes.Sizeof(ft),
sizes.ptrdata(ft),
}
}
sort.Slice(elems, func(i, j int) bool {
ei := &elems[i]
ej := &elems[j]
// Place zero sized objects before non-zero sized objects.
zeroi := ei.sizeof == 0
zeroj := ej.sizeof == 0
if zeroi != zeroj {
return zeroi
}
// Next, place more tightly aligned objects before less tightly aligned objects.
if ei.alignof != ej.alignof {
return ei.alignof > ej.alignof
}
// Place pointerful objects before pointer-free objects.
noptrsi := ei.ptrdata == 0
noptrsj := ej.ptrdata == 0
if noptrsi != noptrsj {
return noptrsj
}
if !noptrsi {
// If both have pointers...
// ... then place objects with less trailing
// non-pointer bytes earlier. That is, place
// the field with the most trailing
// non-pointer bytes at the end of the
// pointerful section.
traili := ei.sizeof - ei.ptrdata
trailj := ej.sizeof - ej.ptrdata
if traili != trailj {
return traili < trailj
}
}
// Lastly, order by size.
if ei.sizeof != ej.sizeof {
return ei.sizeof > ej.sizeof
}
return false
})
// Save all database generation type information to file
fw, err := os.Create("typesSizeofMap.go")
if err != nil {
panic(err)
}
defer fw.Close()
fw.WriteString(`var typesSizeofMap = map[string]struct {
alignof int64
sizeof int64
ptrdata int64
}{
`)
for _, e := range elems {
fmt.Fprintf(fw, "\t\"%s\": {%d,%d,%d},\n",
e.name, e.alignof, e.sizeof, e.ptrdata)
}
fw.WriteString("}")
fields := make([]*types.Var, nf)
indexes := make([]int, nf)
for i, e := range elems {
fields[i] = str.Field(e.index)
indexes[i] = e.index
}
return types.NewStruct(fields, nil), indexes
}
cd golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment && go install
Compile the modified codecd tools/goctl/model/sql/converter && go mod tidy && fieldalignment -fix sort.go
Generate information data for each field through the sort.go
filetypesSizeofMap.go
to tools/goctl/model/sql/gen/field.go
tools/goctl/model/sql/gen/field.go
file looks like this
package gen
import ( "sort" "strings"
"github.com/zeromicro/go-zero/tools/goctl/model/sql/parser"
"github.com/zeromicro/go-zero/tools/goctl/model/sql/template"
"github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
var typesSizeofMap = map[string]struct { alignof int64 sizeof int64 ptrdata int64 }{ "time.Time": {8, 24, 24}, "sql.NullTime": {8, 32, 24}, "string": {8, 16, 8}, "[]byte": {8, 24, 8}, "pq.Int64Array": {8, 24, 8}, "sql.NullString": {8, 24, 8}, "pq.Float64Array": {8, 24, 8}, "pq.StringArray": {8, 24, 8}, "pq.BoolArray": {8, 24, 8}, "sql.NullFloat64": {8, 16, 0}, "sql.NullInt64": {8, 16, 0}, "float64": {8, 8, 0}, "uint64": {8, 8, 0}, "uint": {8, 8, 0}, "int64": {8, 8, 0}, "sql.NullInt32": {4, 8, 0}, "uint32": {4, 4, 0}, "uint16": {2, 2, 0}, "sql.NullBool": {1, 2, 0}, "uint8": {1, 1, 0}, "byte": {1, 1, 0}, "bool": {1, 1, 0}, }
func genFields(table Table, fields []*parser.Field) (string, error) { var list []string
// Refer to go/analysis/passes/fieldalignment/fieldalignment.go#optimalOrder to add the following sorting logic
sort.Slice(fields, func(i, j int) bool {
ei, ok := typesSizeofMap[fields[i].DataType]
if !ok {
return false
}
ej, ok := typesSizeofMap[fields[j].DataType]
if !ok {
return false
}
zeroi := ei.sizeof == 0
zeroj := ej.sizeof == 0
if zeroi != zeroj {
return zeroi
}
if ei.alignof != ej.alignof {
return ei.alignof > ej.alignof
}
noptrsi := ei.ptrdata == 0
noptrsj := ej.ptrdata == 0
if noptrsi != noptrsj {
return noptrsj
}
if !noptrsi {
traili := ei.sizeof - ei.ptrdata
trailj := ej.sizeof - ej.ptrdata
if traili != trailj {
return traili < trailj
}
}
if ei.sizeof != ej.sizeof {
return ei.sizeof > ej.sizeof
}
return false
})
for _, field := range fields {
result, err := genField(table, field)
if err != nil {
return "", err
}
list = append(list, result)
}
return strings.Join(list, "\n"), nil
}
func genField(table Table, field *parser.Field) (string, error) { tag, err := genTag(table, field.NameOriginal) if err != nil { return "", err }
text, err := pathx.LoadTemplate(category, fieldTemplateFile, template.Field)
if err != nil {
return "", err
}
output, err := util.With("types").
Parse(text).
Execute(map[string]interface{}{
"name": util.SafeString(field.Name.ToCamel()),
"type": field.DataType,
"tag": tag,
"hasComment": field.Comment != "",
"comment": field.Comment,
"data": table,
})
if err != nil {
return "", err
}
return output.String(), nil
}
In the end, only modifying the `tools/goctl/model/sql/gen/field.go` file to compile `goctl` can use this feature.
I researched for a half of the day and came up with the above solution. I feel that I am not able to raise PR. I am currently only using this feature in my project by modifying the goctl source code compilation. Hope everyone can add it to the project. @kevwan
The following two data depend on the `os` and `arch` and `compiler`. I think different platforms will come up with different `typesSizeofMap`
```go
wordSize := pass.TypesSizes.Sizeof(unsafePointerTyp)
maxAlign := pass.TypesSizes.Alignof(unsafePointerTyp)
Maybe the following way is more appropriate
var (
typesSizes = types.SizesFor(build.Default.Compiler, build.Default.GOARCH)
unsafePointerTyp = types.Unsafe.Scope().Lookup("Pointer").(*types.TypeName).Type()
wordSize = typesSizes.Sizeof(unsafePointerTyp)
maxAlign = typesSizes.Alignof(unsafePointerTyp)
)
I have the following table creation statement
Generate related code commands:
goctl model mysql ddl --style goZero -d . -s object.sql
Generate the following structure,Sequential source sql table creation statement
Install the official inspection tool:
go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
executive command:
fieldalignment -fix .
,The result is as followsThe final structure is changed to the following order
Memory alignment will improve performance and reduce GC consumption. I think this feature can support
We can integrate the fix code of fieldalignment into
go-zero