Go语言 在init或handler函数中读取模板?

jmo0nnb3  于 5个月前  发布在  Go
关注(0)|答案(3)|浏览(37)

我正在为一个网站编写一个基本的服务器。现在我面临一个(对我来说)困难的性能问题。在init()函数中读取模板文件是否更好?

// Initialize all pages of website
func init(){
 indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
 check(err)
}

字符串
http.HandlerFunc

func index(w http.ResponseWriter, req *http.Request){
  indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
  check(err)
  indexPageTpl := template.Must(template.New("index").Parse(string(indexPageData)))
  indexPageTpl.Execute(w, "test")
}


我认为在第一个例子中,在服务器启动后,您不需要访问磁盘并提高请求的性能。
但在开发过程中,我想刷新浏览器并查看新内容。这可以通过第二个示例完成。
有人有最先进的解决方案吗?或者从性能的Angular 来看,什么是正确的?

hrysbysz

hrysbysz1#

下面我们来分析一下性能:
我们将您的第一个解决方案命名为a(稍有改动,请参见下文),将您的第二个解决方案命名为b
一个请求:
a:一个磁盘访问
B:一个磁盘访问
十项请求:
a:一个磁盘访问
B:10次磁盘访问
1000万宗要求:
a:一个磁盘访问
B:10 000 000次磁盘访问(这很慢)
因此,您的第一个解决方案的性能更好。但是,您对最新数据的担忧是什么呢?从func (t *Template) Execute(wr io.Writer, data interface{}) error的文档可以看出:
Execute会将剖析过的样板套用至指定的数据对象,并将输出写入wr。如果在执行样板或写入其输出时发生错误,则会停止执行,但部分结果可能已写入输出写入器。样板可以安全地平行执行。
所以,事情是这样的:
1.您从磁盘读取模板
1.将文件解析为模板
1.您可以选择要 * 填入空白 * 的数据
1.使用该数据对模板进行Execute,结果将写入io.Writer
您选择的数据是最新的。这与从磁盘重新读取模板,甚至重新解析模板无关。这就是模板背后的全部理念:一次磁盘访问,一次解析,多个动态最终结果。
上面引用的文档告诉我们另一件事:
可以安全地并行执行模板。
这非常有用,因为如果您有多个并行请求,则http.HandlerFunc是并行运行得.
那么,现在该怎么办呢?
Read模板文件一次,
Parse模板一次
Execute每个请求的模板 *。
我不确定你是否应该在init()函数中读取和解析,因为至少Must可能会死机(不要在那里使用一些相对的硬编码路径!)--我会尝试在一个更可控的环境中这样做,例如,提供一个函数(如New())来创建一个新的服务器示例,并在那里执行这些操作。

编辑:我重新阅读了您的问题,可能误解了您的意思:

如果模板本身仍在开发中,则是的,您必须在每次请求时读取它以获得最新的结果。这比每次更改模板时重新启动服务器更方便。对于生产,模板应是固定的,并且只应更改数据。
如果我理解错了,我很抱歉。

icomxhvb

icomxhvb2#

永远不要在生产中读取和解析请求处理程序中的模板文件,这是最糟糕的(你应该总是避免这种情况)。
阅读此问题以了解更多详情:
It takes too much time when using "template" package to generate a dynamic web page to client in golang
你可以用多种方法来实现这一点。这里我列出了4个例子。

1.设置为“dev mode”

你可以有一个常量或变量来告诉你是否在开发模式下运行,这意味着模板不会被缓存。
这里有一个例子:

const dev = true

var indexTmpl *template.Template

func init() {
    if !dev { // Prod mode, read and cache template
        indexTmpl = template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
    }
}

func getIndexTmpl() *template.Template {
    if dev { // Dev mode, always read fresh template
        return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
    } else { // Prod mode, return cached template
        return indexTmpl
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    getIndexTmpl().Execute(w, "test")
}

字符串

2.在请求中指定(作为参数)是否需要新模板

当你开发时,你可以指定一个额外的URL参数,指示读取一个新的模板,而不是使用缓存的模板,例如http://localhost:8080/index?dev=true
示例实现:

var indexTmpl *template.Template

func init() {
    indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
    return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    t := indexTmpl
    if r.FormValue("dev") != nil {
        t = getIndexTmpl()
    }
    t.Execute(w, "test")
}

3.根据主机决定

您也可以检查请求URL的主机名,如果是"localhost",您可以忽略该高速缓存并使用新的模板。这需要最小的额外代码和工作。请注意,您可能还想接受其他主机,例如"127.0.0.1"(取决于您想要包含的内容)。
示例实现:

var indexTmpl *template.Template

func init() {
    indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
    return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    t := indexTmpl
    if r.URL.Host == "localhost" || strings.HasPrefix(r.URL.Host, "localhost:") {
        t = getIndexTmpl()
    }
    t.Execute(w, "test")
}

4.检查模板文件最后一次修改

您也可以在加载模板文件时存储模板文件的最后修改时间。每当请求模板时,您可以检查源模板文件的最后修改时间。如果它已更改,您可以在执行之前重新加载它。
示例实现:

type mytempl struct {
    t       *template.Template
    lastmod time.Time
    mutex   sync.Mutex
}

var indexTmpl mytempl

func init() {
    // You may want to call this in init so first request won't be slow
    checkIndexTempl()
}

func checkIndexTempl() {
    nm := ".tpl/index.tpl"
    fi, err := os.Stat(nm)
    if err != nil {
        panic(err)
    }
    if indexTmpl.lastmod != fi.ModTime() {
        // Changed, reload. Don't forget the locking!
        indexTmpl.mutex.Lock()
        defer indexTmpl.mutex.Unlock()
        indexTmpl.t = template.Must(template.New("index").ParseFiles(nm))
        indexTmpl.lastmod = fi.ModTime()
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    checkIndexTempl()
    indexTmpl.t.Execute(w, "test")
}

w8biq8rn

w8biq8rn3#

考虑更好,所以证明它(使用基准)

  • Iterations =(越大越好)
  • ns/op =每次操作的时间(越少越好)
  • B/op =每个操作分配的内存(越少越好)
  • allocs/op =每个操作的分配(越少越好)

第四名(最慢访问和最高内存)

package main

import (
    "html/template"
    "testing"
)

// goos: darwin
// goarch: arm64
// pkg: web
// BenchmarkTestTemplateParseFiles-12         110205          9837 ns/op        4761 B/op         35 allocs/op
// PASS
// ok   web 1.400s
func BenchmarkTestTemplateParseFiles(b *testing.B) {
    for n := 0; n < b.N; n++ {
        template.ParseFiles("./templates/index.tpl")
    }
}

字符串

第三名(访问速度更快,内存比前一个少一点)

package main

import (
    "embed"
    "html/template"
    "testing"
)

//go:embed templates/*.tpl
var templates embed.FS

// goos: darwin
// goarch: arm64
// pkg: web
// BenchmarkTestTemplateParseFS-12        527152          2253 ns/op        4297 B/op         35 allocs/op
// PASS
// ok   web 1.836s
func BenchmarkTestTemplateParseFS(b *testing.B) {
    for n := 0; n < b.N; n++ {
        template.ParseFS(templates, "templates/index.tpl")
    }
}

第二名(访问速度更快,内存更小)

package main

import (
    _ "embed"
    "html/template"
    "testing"
)

//go:embed templates/index.tpl
var view []byte

// goos: darwin
// goarch: arm64
// pkg: web
// BenchmarkTestTemplateParse-12          686432          1761 ns/op        3913 B/op         30 allocs/op
// PASS
// ok   web 2.331s
func BenchmarkTestTemplateParse(b *testing.B) {
    for n := 0; n < b.N; n++ {
        template.New("register").Parse(string(view))
    }
}

第二名(访问速度慢,内存比前一名少)

package main

import (
    "os"
    "testing"
)

// goos: darwin
// goarch: arm64
// pkg: web
// === RUN   BenchmarkTestIOReadFile
// BenchmarkTestIOReadFile
// BenchmarkTestIOReadFile-12        139602              7768 ns/op             848 B/op         5 allocs/op
// PASS
// ok      web     1.396s
func BenchmarkTestIOReadFile(b *testing.B) {
    for n := 0; n < b.N; n++ {
        os.ReadFile("./templates/index.tpl")
    }
}

第一名(访问速度最快,内存最小)

package main

import (
    _ "embed"
    "testing"
)

// I prefer go:embed instead of init function
// with panic when folders/files not found on OS.
//go:embed templates/index.tpl
var view []byte

// goos: darwin
// goarch: arm64
// pkg: web
// BenchmarkTestGlobalTemplate-12       1000000000           0.0000001 ns/op           0 B/op          0 allocs/op
// PASS
// ok   web 0.301s
func BenchmarkTestGlobalTemplate(b *testing.B) {
    // Let's assume this is a handler returning some html file
    func() []byte {
        return view
    }()
}

相关问题