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
|
||||
}
|
||||
})
|
||||
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) {
|
||||
braceAt, hasEndBrace := skipUntilIfExists(data, index, ')')
|
||||
// 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, "w.Write([]byte(", "out += ")
|
||||
data = replace(data, "w.Write(StringToBytes(", "out += ")
|
||||
data = replace(data, "w.Write(", "out += ")
|
||||
data = replace(data, "strconv.Itoa(", "")
|
||||
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 cached_var_", "let cached_var_")
|
||||
data = replace(data, " = []byte(", " = ")
|
||||
data = replace(data, " = StringToBytes(", " = ")
|
||||
data = replace(data, "if ", "if(")
|
||||
data = replace(data, "return nil", "return out")
|
||||
data = replace(data, " )", ")")
|
||||
|
|
|
@ -509,18 +509,29 @@ func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string)
|
|||
}
|
||||
getterstr += "}\nreturn nil\n}\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 {
|
||||
var fragmentPrefix string
|
||||
front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
|
||||
fp, ok := bodyMap[frag.Body]
|
||||
/*fp, ok := bodyMap[frag.Body]
|
||||
if !ok {
|
||||
fragmentPrefix = front + " = []byte(`" + frag.Body + "`)\n"
|
||||
bodyMap[frag.Body] = front
|
||||
} else {
|
||||
fragmentPrefix = front + " = " + fp + "\n"
|
||||
bodyMap[frag.Body] = front*/
|
||||
var bits string
|
||||
for _, char := range []byte(frag.Body) {
|
||||
if char == '\'' {
|
||||
bits += "'\\" + string(char) + "',"
|
||||
} else {
|
||||
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"
|
||||
err := writeFile(prefix+"template_list.go", out)
|
||||
|
|
|
@ -425,6 +425,15 @@ func (c *CTemplateSet) rootIterate(tree *parse.Tree, con CContext) {
|
|||
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) {
|
||||
c.dumpCall("compileSwitch", con, node)
|
||||
defer c.retCall("compileSwitch")
|
||||
|
@ -451,14 +460,6 @@ func (c *CTemplateSet) compileSwitch(con CContext, node parse.Node) {
|
|||
c.detail("Expression:", expr)
|
||||
// Simple member / guest optimisation for now
|
||||
// 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{
|
||||
con.RootHolder + ".CurrentUser.Loggedin",
|
||||
con.RootHolder + ".CurrentUser.IsSuperMod",
|
||||
|
@ -861,7 +862,7 @@ func (c *CTemplateSet) compileIdentSwitch(con CContext, node *parse.CommandNode)
|
|||
c.dumpCall("compileIdentSwitch", con, node)
|
||||
var litString = func(inner string, bytes bool) {
|
||||
if !bytes {
|
||||
inner = "[]byte(" + inner + ")"
|
||||
inner = "StringToBytes(" + inner + ")"
|
||||
}
|
||||
out = "w.Write(" + inner + ")\n"
|
||||
literal = true
|
||||
|
@ -1219,7 +1220,7 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
|||
|
||||
// Is this a literal string?
|
||||
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
|
||||
}
|
||||
for _, varItem := range c.varList {
|
||||
|
@ -1250,9 +1251,44 @@ func (c *CTemplateSet) compileVarSub(con CContext, varname string, val reflect.V
|
|||
switch val.Kind() {
|
||||
case reflect.Int:
|
||||
c.importMap["strconv"] = "strconv"
|
||||
base = "[]byte(strconv.Itoa(" + varname + "))"
|
||||
base = "StringToBytes(strconv.Itoa(" + varname + "))"
|
||||
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("varsub", "[]byte(\"true\")")
|
||||
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(") {
|
||||
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
|
||||
// TODO: Implement this properly
|
||||
if c.guestOnly && base == "[]byte("+con.RootHolder+".CurrentUser.Session))" {
|
||||
if c.guestOnly && base == "StringToBytes("+con.RootHolder+".CurrentUser.Session))" {
|
||||
return
|
||||
}
|
||||
case reflect.Int64:
|
||||
c.importMap["strconv"] = "strconv"
|
||||
base = "[]byte(strconv.FormatInt(" + varname + ", 10))"
|
||||
base = "StringToBytes(strconv.FormatInt(" + varname + ", 10))"
|
||||
case reflect.Struct:
|
||||
// TODO: Avoid clashing with other packages which have structs named Time
|
||||
if val.Type().Name() == "Time" {
|
||||
base = "[]byte(" + varname + ".String())"
|
||||
base = "StringToBytes(" + varname + ".String())"
|
||||
} else {
|
||||
if !val.IsValid() {
|
||||
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)
|
||||
}
|
||||
|
||||
func BenchmarkTopicGuestRouteParallelWithRouterAlt(b *testing.B) {
|
||||
obRoute(b, "/topic/hm."+benchTid)
|
||||
}
|
||||
|
||||
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
||||
obRoute(b, "/garble/haa")
|
||||
}
|
||||
|
||||
// TODO: Alternate between member and guest to bust some CPU caches?
|
||||
|
||||
func binit(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
err := gloinit()
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package tmpl
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var GetFrag = func(name string) [][]byte {
|
||||
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
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var GetFrag = func(name string) [][]byte {
|
||||
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