Go lang中的html /模板性能下降,有什么解决方法吗?

uiw*_*e83 12 go go-html-template

我正在压力测试(使用loader.io)这种类型的代码在Go中创建一个包含100个项目的数组以及一些其他基本变量,并在模板中解析它们:

package main

import (
    "html/template"
    "net/http"
)

var templates map[string]*template.Template

// Load templates on program initialisation
func init() {
    if templates == nil {
        templates = make(map[string]*template.Template)
    }

    templates["index.html"] = template.Must(template.ParseFiles("index.html"))
}

func handler(w http.ResponseWriter, r *http.Request) {
    type Post struct {
        Id int
        Title, Content string
    }

    var Posts [100]Post

    // Fill posts
    for i := 0; i < 100; i++ {
        Posts[i] = Post{i, "Sample Title", "Lorem Ipsum Dolor Sit Amet"}
    }

    type Page struct {
        Title, Subtitle string
        Posts [100]Post
    }

    var p Page

    p.Title = "Index Page of My Super Blog"
    p.Subtitle = "A blog about everything"
    p.Posts = Posts

    tmpl := templates["index.html"]

    tmpl.ExecuteTemplate(w, "index.html", p)
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8888", nil)
}
Run Code Online (Sandbox Code Playgroud)

我对Loader的测试是在1分钟内使用5k并发连接.问题是,在开始测试几秒钟后,我得到了很高的平均延迟(差不多10秒),因此5k成功响应并且测试停止,因为它达到50%的错误率(超时).

在同一台机器上,PHP提供50k +.

我知道这不是Go性能问题,但可能与html/template有关.Go当然可以比PHP之类的任何东西更快地管理足够硬的计算,但是当把数据解析到模板时,为什么它太可怕了?

任何变通办法,或者我可能只是做错了(我是Go的新手)?

PS实际上即使有一个项目它也完全相同... 5-6k并且在大量超时后停止.但这可能是因为带有帖子的阵列保持相同的长度.

我的模板代码(index.html):

{{ .Title }}
{{ .Subtitle }}

{{ range .Posts }}
        {{ .Title }}
        {{ .Content }}
{{ end }}
Run Code Online (Sandbox Code Playgroud)

这是github.com/pkg/profile的分析结果:

root@Test:~# go tool pprof app /tmp/profile311243501/cpu.pprof
Possible precedence issue with control flow operator at /usr/lib/go/pkg/tool/linux_amd64/pprof line 3008.
Welcome to pprof!  For help, type 'help'.
(pprof) top10
Total: 2054 samples
      97   4.7%   4.7%      726  35.3% reflect.Value.call
      89   4.3%   9.1%      278  13.5% runtime.mallocgc
      85   4.1%  13.2%       86   4.2% syscall.Syscall
      66   3.2%  16.4%       75   3.7% runtime.MSpan_Sweep
      58   2.8%  19.2%     1842  89.7% text/template.(*state).walk
      54   2.6%  21.9%      928  45.2% text/template.(*state).evalCall
      51   2.5%  24.3%       53   2.6% settype
      47   2.3%  26.6%       47   2.3% runtime.stringiter2
      44   2.1%  28.8%      149   7.3% runtime.makeslice
      40   1.9%  30.7%      223  10.9% text/template.(*state).evalField
Run Code Online (Sandbox Code Playgroud)

这些是在完善代码之后的分析结果(如icza的答案所示):

root@Test:~# go tool pprof app /tmp/profile501566907/cpu.pprof
Possible precedence issue with control flow operator at /usr/lib/go/pkg/tool/linux_amd64/pprof line 3008.
Welcome to pprof!  For help, type 'help'.
(pprof) top10
Total: 2811 samples
     137   4.9%   4.9%      442  15.7% runtime.mallocgc
     126   4.5%   9.4%      999  35.5% reflect.Value.call
     113   4.0%  13.4%      115   4.1% syscall.Syscall
     110   3.9%  17.3%      122   4.3% runtime.MSpan_Sweep
     102   3.6%  20.9%     2561  91.1% text/template.(*state).walk
      74   2.6%  23.6%      337  12.0% text/template.(*state).evalField
      68   2.4%  26.0%       72   2.6% settype
      66   2.3%  28.3%     1279  45.5% text/template.(*state).evalCall
      65   2.3%  30.6%      226   8.0% runtime.makeslice
      57   2.0%  32.7%       57   2.0% runtime.stringiter2
(pprof)
Run Code Online (Sandbox Code Playgroud)

kos*_*tya 11

使用等效应用程序html/template比PHP变体慢的原因主要有两个.

首先html/template提供比PHP更多的功能.主要区别在于,html/template它将使用正确的转义规则(HTML,JS,CSS等)自动转义变量,具体取决于它们在生成的HTML输出中的位置(我觉得这很酷!).

其次html/template渲染代码大量使用具有可变数量参数的反射和方法,并且它们没有静态编译代码那么快.

引擎盖下面的模板

{{ .Title }}
{{ .Subtitle }}

{{ range .Posts }}
    {{ .Title }}
    {{ .Content }}
{{ end }}
Run Code Online (Sandbox Code Playgroud)

转换成类似的东西

{{ .Title | html_template_htmlescaper }}
{{ .Subtitle | html_template_htmlescaper }}

{{ range .Posts }}
    {{ .Title | html_template_htmlescaper }}
    {{ .Content | html_template_htmlescaper }}
{{ end }}
Run Code Online (Sandbox Code Playgroud)

html_template_htmlescaper在循环中使用反射调用会导致性能下降.

尽管如此,html/template不应该使用这个微观基准来决定是否使用Go.一旦你添加代码来处理数据库到请求处理程序,我怀疑模板渲染时间几乎不会引人注意.

另外我很确定随着时间的推移,Go反射和html/template包装都会变得更快.

如果在实际应用程序中,您会发现这html/template是一个瓶颈,它仍然可以切换到text/template并提供已经转义的数据.


icz*_*cza 9

您正在使用数组和结构,它们都是非指针类型,也不是描述符(如切片或贴图或通道).因此传递它们总是创建值的副本,为变量分配数组值会复制所有元素.这很慢并且为GC提供了大量的工作.


此外,您只使用1个CPU内核.要利用更多功能,请将其添加到您的main()功能中:

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8888", nil))
}
Run Code Online (Sandbox Code Playgroud)

编辑: 这只是Go 1.5之前的情况.由于Go 1.5 runtime.NumCPU()是默认值.


你的代码

var Posts [100]Post
Run Code Online (Sandbox Code Playgroud)

Post分配空间为100 s 的数组.

Posts[i] = Post{i, "Sample Title", "Lorem Ipsum Dolor Sit Amet"}
Run Code Online (Sandbox Code Playgroud)

Post使用复合文字创建值,然后将此值复制到i数组中的th元素中.(冗余)

var p Page
Run Code Online (Sandbox Code Playgroud)

这会创建一个类型的变量Page.它是a struct,因此它的内存被分配,它还包含一个字段,Posts [100]Post因此分配了另一个100元素数组.

p.Posts = Posts
Run Code Online (Sandbox Code Playgroud)

这复制100元素(一百个结构)!

tmpl.ExecuteTemplate(w, "index.html", p)
Run Code Online (Sandbox Code Playgroud)

这会创建一个p(类型为Page)的副本,因此100会创建另一个帖子数组并p复制元素,然后传递给它ExecuteTemplate().

并且由于Page.Posts是一个数组,很可能在处理它时(在模板引擎中迭代),将从每个元素创建一个副本(未检查 - 未经验证).

建议更高效的代码

加速代码的一些事情:

func handler(w http.ResponseWriter, r *http.Request) {
    type Post struct {
        Id int
        Title, Content string
    }

    Posts := make([]*Post, 100) // A slice of pointers

    // Fill posts
    for i := range Posts {
        // Initialize pointers: just copies the address of the created struct value
        Posts[i]= &Post{i, "Sample Title", "Lorem Ipsum Dolor Sit Amet"}
    }

    type Page struct {
        Title, Subtitle string
        Posts []*Post // "Just" a slice type (it's a descriptor)
    }

    // Create a page, only the Posts slice descriptor is copied
    p := Page{"Index Page of My Super Blog", "A blog about everything", Posts}

    tmpl := templates["index.html"]

    // Only pass the address of p
    // Although since Page.Posts is now just a slice, passing by value would also be OK 
    tmpl.ExecuteTemplate(w, "index.html", &p)
}
Run Code Online (Sandbox Code Playgroud)

请测试此代码并报告您的结果.