Use unsafe to reduce the number of string to slice copies in templates.
Use sliced arrays to reduce the amount of null padding in template fragments. Revert a failed optimisation in templates. Remove a few more redundant branches in variant templates. Added the unsafe function StringToBytes. Added BenchmarkTopicGuestRouteParallelWithRouterAlt.
This commit is contained in:
parent
de955559d3
commit
72c92672b7
|
@ -154,6 +154,24 @@ func (list SFileList) JSTmplInit() error {
|
||||||
data[braceAt] = ' ' // Blank it
|
data[braceAt] = ' ' // Blank it
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
each("w.Write(StringToBytes(", func(index int) {
|
||||||
|
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
||||||
|
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
||||||
|
if hasEndBrace {
|
||||||
|
data[braceAt] = ' ' // Blank it
|
||||||
|
}
|
||||||
|
braceAt, hasEndBrace = skipUntilIfExists(data, braceAt, ')')
|
||||||
|
if hasEndBrace {
|
||||||
|
data[braceAt] = ' ' // Blank this one too
|
||||||
|
}
|
||||||
|
})
|
||||||
|
each(" = StringToBytes(", func(index int) {
|
||||||
|
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
||||||
|
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
||||||
|
if hasEndBrace {
|
||||||
|
data[braceAt] = ' ' // Blank it
|
||||||
|
}
|
||||||
|
})
|
||||||
each("w.Write(", func(index int) {
|
each("w.Write(", func(index int) {
|
||||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
||||||
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
// TODO: Make sure we don't go onto the next line in case someone misplaced a brace
|
||||||
|
@ -183,6 +201,7 @@ func (list SFileList) JSTmplInit() error {
|
||||||
})
|
})
|
||||||
data = replace(data, "for _, item := range ", "for(item of ")
|
data = replace(data, "for _, item := range ", "for(item of ")
|
||||||
data = replace(data, "w.Write([]byte(", "out += ")
|
data = replace(data, "w.Write([]byte(", "out += ")
|
||||||
|
data = replace(data, "w.Write(StringToBytes(", "out += ")
|
||||||
data = replace(data, "w.Write(", "out += ")
|
data = replace(data, "w.Write(", "out += ")
|
||||||
data = replace(data, "strconv.Itoa(", "")
|
data = replace(data, "strconv.Itoa(", "")
|
||||||
data = replace(data, "strconv.FormatInt(", "")
|
data = replace(data, "strconv.FormatInt(", "")
|
||||||
|
@ -194,6 +213,7 @@ func (list SFileList) JSTmplInit() error {
|
||||||
//data = replace(data, "var phrases = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let phrases = tmplPhrases[\""+tmplName+"\"];\nconsole.log('tmplName:','"+tmplName+"')\nconsole.log('phrases:', phrases);")
|
//data = replace(data, "var phrases = GetTmplPhrasesBytes("+shortName+"_tmpl_phrase_id)", "let phrases = tmplPhrases[\""+tmplName+"\"];\nconsole.log('tmplName:','"+tmplName+"')\nconsole.log('phrases:', phrases);")
|
||||||
data = replace(data, "var cached_var_", "let cached_var_")
|
data = replace(data, "var cached_var_", "let cached_var_")
|
||||||
data = replace(data, " = []byte(", " = ")
|
data = replace(data, " = []byte(", " = ")
|
||||||
|
data = replace(data, " = StringToBytes(", " = ")
|
||||||
data = replace(data, "if ", "if(")
|
data = replace(data, "if ", "if(")
|
||||||
data = replace(data, "return nil", "return out")
|
data = replace(data, "return nil", "return out")
|
||||||
data = replace(data, " )", ")")
|
data = replace(data, " )", ")")
|
||||||
|
|
|
@ -509,18 +509,29 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string)
|
||||||
}
|
}
|
||||||
getterstr += "}\nreturn nil\n}\n"
|
getterstr += "}\nreturn nil\n}\n"
|
||||||
out += "\n// nolint\nfunc init() {\n"
|
out += "\n// nolint\nfunc init() {\n"
|
||||||
var bodyMap = make(map[string]string) //map[body]fragmentPrefix
|
//var bodyMap = make(map[string]string) //map[body]fragmentPrefix
|
||||||
|
var tmpCount = 0
|
||||||
for _, frag := range c.FragOut {
|
for _, frag := range c.FragOut {
|
||||||
var fragmentPrefix string
|
|
||||||
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
|
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
|
||||||
fp, ok := bodyMap[frag.Body]
|
/*fp, ok := bodyMap[frag.Body]
|
||||||
if !ok {
|
if !ok {
|
||||||
fragmentPrefix = front + " = []byte(`" + frag.Body + "`)\n"
|
bodyMap[frag.Body] = front*/
|
||||||
bodyMap[frag.Body] = front
|
var bits string
|
||||||
|
for _, char := range []byte(frag.Body) {
|
||||||
|
if char == '\'' {
|
||||||
|
bits += "'\\" + string(char) + "',"
|
||||||
} else {
|
} else {
|
||||||
fragmentPrefix = front + " = " + fp + "\n"
|
bits += "'" + string(char) + "',"
|
||||||
}
|
}
|
||||||
out += fragmentPrefix
|
}
|
||||||
|
tmpStr := strconv.Itoa(tmpCount)
|
||||||
|
out += "arr_" + tmpStr + " := [...]byte{" + bits + "}\n"
|
||||||
|
out += front + " = arr_" + tmpStr + "[:]\n"
|
||||||
|
tmpCount++
|
||||||
|
//out += front + " = []byte(`" + frag.Body + "`)\n"
|
||||||
|
/*} else {
|
||||||
|
out += front + " = " + fp + "\n"
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
out += "\n" + getterstr + "}\n"
|
out += "\n" + getterstr + "}\n"
|
||||||
err := writeFile(prefix+"template_list.go", out)
|
err := writeFile(prefix+"template_list.go", out)
|
||||||
|
|
|
@ -425,6 +425,15 @@ func (c *CTemplateSet) rootIterate(tree *parse.Tree, con CContext) {
|
||||||
c.retCall("rootIterate")
|
c.retCall("rootIterate")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var inSlice = func(haystack []string, expr string) bool {
|
||||||
|
for _, needle := range haystack {
|
||||||
|
if needle == expr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
||||||
c.dumpCall("compileSwitch", con, node)
|
c.dumpCall("compileSwitch", con, node)
|
||||||
defer c.retCall("compileSwitch")
|
defer c.retCall("compileSwitch")
|
||||||
|
@ -451,14 +460,6 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
||||||
c.detail("Expression:", expr)
|
c.detail("Expression:", expr)
|
||||||
// Simple member / guest optimisation for now
|
// Simple member / guest optimisation for now
|
||||||
// TODO: Expand upon this
|
// TODO: Expand upon this
|
||||||
var inSlice = func(haystack []string, expr string) bool {
|
|
||||||
for _, needle := range haystack {
|
|
||||||
if needle == expr {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
var userExprs = []string{
|
var userExprs = []string{
|
||||||
con.RootHolder + ".CurrentUser.Loggedin",
|
con.RootHolder + ".CurrentUser.Loggedin",
|
||||||
con.RootHolder + ".CurrentUser.IsSuperMod",
|
con.RootHolder + ".CurrentUser.IsSuperMod",
|
||||||
|
@ -861,7 +862,7 @@ func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode)
|
||||||
c.dumpCall("compileIdentSwitch", con, node)
|
c.dumpCall("compileIdentSwitch", con, node)
|
||||||
var litString = func(inner string, bytes bool) {
|
var litString = func(inner string, bytes bool) {
|
||||||
if !bytes {
|
if !bytes {
|
||||||
inner = "[]byte(" + inner + ")"
|
inner = "StringToBytes(" + inner + ")"
|
||||||
}
|
}
|
||||||
out = "w.Write(" + inner + ")\n"
|
out = "w.Write(" + inner + ")\n"
|
||||||
literal = true
|
literal = true
|
||||||
|
@ -1219,7 +1220,7 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
||||||
|
|
||||||
// Is this a literal string?
|
// Is this a literal string?
|
||||||
if len(varname) != 0 && varname[0] == '"' {
|
if len(varname) != 0 && varname[0] == '"' {
|
||||||
con.Push("lvarsub", onEnd(assLines+"w.Write([]byte("+varname+"))\n"))
|
con.Push("lvarsub", onEnd(assLines+"w.Write(StringToBytes("+varname+"))\n"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, varItem := range c.varList {
|
for _, varItem := range c.varList {
|
||||||
|
@ -1250,9 +1251,44 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
||||||
switch val.Kind() {
|
switch val.Kind() {
|
||||||
case reflect.Int:
|
case reflect.Int:
|
||||||
c.importMap["strconv"] = "strconv"
|
c.importMap["strconv"] = "strconv"
|
||||||
base = "[]byte(strconv.Itoa(" + varname + "))"
|
base = "StringToBytes(strconv.Itoa(" + varname + "))"
|
||||||
case reflect.Bool:
|
case reflect.Bool:
|
||||||
// TODO: Take c.guestOnly / c.memberOnly into account
|
// TODO: Take c.memberOnly into account
|
||||||
|
// TODO: Make this a template fragment so more optimisations can be applied to this
|
||||||
|
// TODO: De-duplicate this logic
|
||||||
|
var userExprs = []string{
|
||||||
|
con.RootHolder + ".CurrentUser.Loggedin",
|
||||||
|
con.RootHolder + ".CurrentUser.IsSuperMod",
|
||||||
|
con.RootHolder + ".CurrentUser.IsAdmin",
|
||||||
|
}
|
||||||
|
var negUserExprs = []string{
|
||||||
|
"!" + con.RootHolder + ".CurrentUser.Loggedin",
|
||||||
|
"!" + con.RootHolder + ".CurrentUser.IsSuperMod",
|
||||||
|
"!" + con.RootHolder + ".CurrentUser.IsAdmin",
|
||||||
|
}
|
||||||
|
if c.guestOnly {
|
||||||
|
c.detail("optimising away member branch")
|
||||||
|
if inSlice(userExprs, varname) {
|
||||||
|
c.detail("positive conditional:", varname)
|
||||||
|
con.Push("varsub", "[]byte(\"false\")")
|
||||||
|
return
|
||||||
|
} else if inSlice(negUserExprs, varname) {
|
||||||
|
c.detail("negative conditional:", varname)
|
||||||
|
con.Push("varsub", "[]byte(\"true\")")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if c.memberOnly {
|
||||||
|
c.detail("optimising away guest branch")
|
||||||
|
if (con.RootHolder + ".CurrentUser.Loggedin") == varname {
|
||||||
|
c.detail("positive conditional:", varname)
|
||||||
|
con.Push("varsub", "[]byte(\"true\")")
|
||||||
|
return
|
||||||
|
} else if ("!" + con.RootHolder + ".CurrentUser.Loggedin") == varname {
|
||||||
|
c.detail("negative conditional:", varname)
|
||||||
|
con.Push("varsub", "[]byte(\"false\")")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
con.Push("startif", "if "+varname+" {\n")
|
con.Push("startif", "if "+varname+" {\n")
|
||||||
con.Push("varsub", "[]byte(\"true\")")
|
con.Push("varsub", "[]byte(\"true\")")
|
||||||
con.Push("endif", "} ")
|
con.Push("endif", "} ")
|
||||||
|
@ -1264,19 +1300,19 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
||||||
if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") {
|
if val.Type().Name() != "string" && !strings.HasPrefix(varname, "string(") {
|
||||||
varname = "string(" + varname + ")"
|
varname = "string(" + varname + ")"
|
||||||
}
|
}
|
||||||
base = "[]byte(" + varname + ")"
|
base = "StringToBytes(" + varname + ")"
|
||||||
// We don't to waste time on this conversion / w.Write call when guests don't have sessions
|
// We don't to waste time on this conversion / w.Write call when guests don't have sessions
|
||||||
// TODO: Implement this properly
|
// TODO: Implement this properly
|
||||||
if c.guestOnly && base == "[]byte("+con.RootHolder+".CurrentUser.Session))" {
|
if c.guestOnly && base == "StringToBytes("+con.RootHolder+".CurrentUser.Session))" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
c.importMap["strconv"] = "strconv"
|
c.importMap["strconv"] = "strconv"
|
||||||
base = "[]byte(strconv.FormatInt(" + varname + ", 10))"
|
base = "StringToBytes(strconv.FormatInt(" + varname + ", 10))"
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
// TODO: Avoid clashing with other packages which have structs named Time
|
// TODO: Avoid clashing with other packages which have structs named Time
|
||||||
if val.Type().Name() == "Time" {
|
if val.Type().Name() == "Time" {
|
||||||
base = "[]byte(" + varname + ".String())"
|
base = "StringToBytes(" + varname + ".String())"
|
||||||
} else {
|
} else {
|
||||||
if !val.IsValid() {
|
if !val.IsValid() {
|
||||||
panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
|
panic(assLines + varname + "^\n" + "Invalid value. Maybe, it doesn't exist?")
|
||||||
|
|
|
@ -288,10 +288,16 @@ func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
obRoute(b, "/topic/hm."+benchTid)
|
obRoute(b, "/topic/hm."+benchTid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkTopicGuestRouteParallelWithRouterAlt(b *testing.B) {
|
||||||
|
obRoute(b, "/topic/hm."+benchTid)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
||||||
obRoute(b, "/garble/haa")
|
obRoute(b, "/garble/haa")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Alternate between member and guest to bust some CPU caches?
|
||||||
|
|
||||||
func binit(b *testing.B) {
|
func binit(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
err := gloinit()
|
err := gloinit()
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
package tmpl
|
package tmpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
var GetFrag = func(name string) [][]byte {
|
var GetFrag = func(name string) [][]byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WriteString interface {
|
||||||
|
WriteString(s string) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToBytes(s string) (bytes []byte) {
|
||||||
|
str := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||||
|
slice := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
|
||||||
|
slice.Data = str.Data
|
||||||
|
slice.Len = str.Len
|
||||||
|
slice.Cap = str.Len
|
||||||
|
runtime.KeepAlive(&s)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
20
tmpl_stub.go
20
tmpl_stub.go
|
@ -1,5 +1,25 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
var GetFrag = func(name string) [][]byte {
|
var GetFrag = func(name string) [][]byte {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WriteString interface {
|
||||||
|
WriteString(s string) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToBytes(s string) (bytes []byte) {
|
||||||
|
str := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||||
|
slice := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
|
||||||
|
slice.Data = str.Data
|
||||||
|
slice.Len = str.Len
|
||||||
|
slice.Cap = str.Len
|
||||||
|
runtime.KeepAlive(&s)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue