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:
Azareal 2018-12-15 14:39:50 +10:00
parent de955559d3
commit 72c92672b7
6 changed files with 137 additions and 24 deletions

View File

@ -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, " )", ")")

View File

@ -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)

View File

@ -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?")

View File

@ -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()

View File

@ -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
}

View File

@ -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
}