12

Go でこのタイプのコードを (loader.io を使用して) ストレス テストして、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)
}

ローダーを使用した私のテストでは、1 分間で 5,000 の同時接続/秒を使用しています。問題は、テストを開始してから数秒後に、平均レイテンシが高くなり (ほぼ 10 秒)、その結果、5,000 の成功した応答が得られ、50% のエラー率 (タイムアウト) に達したためにテストが停止することです。

同じマシンで、PHP は 50k+ を返します。

Go のパフォーマンスの問題ではなく、おそらく html/template に関連する問題であることは理解しています。もちろん、Go は十分に難しい計算を PHP のようなものよりもはるかに高速に簡単に処理できますが、テンプレートへのデータの解析に関しては、なぜそれほどひどいのでしょうか?

回避策はありますか、またはおそらく私は間違っているだけです(私はGoが初めてです)?

PS実際には1つのアイテムでもまったく同じです... 5〜6kで、大量のタイムアウト後に停止します。しかし、それはおそらく、投稿のある配列が同じ長さのままであるためです。

私のテンプレートコード(index.html):

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

{{ range .Posts }}
        {{ .Title }}
        {{ .Content }}
{{ end }}

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

これらは、コードを改良した後のプロファイリング結果です(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)
4

5 に答える 5

11

html/template使用する同等のアプリケーションが PHP バリアントよりも遅い主な理由は 2 つあります。

まず第一にhtml/template、PHP よりも多くの機能を提供します。主な違いはhtml/template、結果の HTML 出力内の位置に応じて、正しいエスケープ規則 (HTML、JS、CSS など) を使用して変数を自動的にエスケープすることです (これは非常にクールだと思います!)。

第 2html/templateに、レンダリング コードはリフレクションと可変数の引数を持つメソッドを多用し、静的にコンパイルされたコードほど高速ではありません。

内部では、次のテンプレート

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

{{ range .Posts }}
    {{ .Title }}
    {{ .Content }}
{{ end }}

のようなものに変換されます

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

{{ range .Posts }}
    {{ .Title | html_template_htmlescaper }}
    {{ .Content | html_template_htmlescaper }}
{{ end }}

html_template_htmlescaperループ内でリフレクションを使用して呼び出すと、パフォーマンスが低下します。

Goを使用するかどうかを決定するために、このマイクロベンチマークをhtml/template使用すべきではありません。データベースを操作するコードをリクエスト ハンドラに追加すると、テンプレートのレンダリング時間はほとんど気にならなくなると思います。

html/templateまた、時間の経過とともに Go リフレクションとパッケージの両方が高速になると確信しています。

実際のアプリケーションでそれがボトルネックであることがわかった場合でも、すでにエスケープされたデータhtml/templateに切り替えて提供することができます。text/template

于 2015-07-14T06:25:59.937 に答える
10

どちらも非ポインター型であり、記述子でもありません (スライス、マップ、チャネルなど)。したがって、それらを渡すと、常に値のコピーが作成され、配列値を変数に割り当てると、すべての要素がコピーされます。これは遅く、GC に膨大な量の作業を与えます。


また、1 つの CPU コアしか使用していません。さらに利用するには、これをmain()関数に追加します。

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

編集: これは Go 1.5 より前のケースのみでした。Go 1.5runtime.NumCPU()がデフォルトなので。


あなたのコード

var Posts [100]Post

100 秒分のスペースを持つ配列Postが割り当てられます。

Posts[i] = Post{i, "Sample Title", "Lorem Ipsum Dolor Sit Amet"}

複合リテラルで値を作成するPostと、この値がi配列の th 要素にコピーされます。(冗長)

var p Page

これにより、 type の変数が作成されますPage。であるstructため、フィールドも含むメモリが割り当てられているため、別の要素Posts [100]Postの配列が割り当てられます。100

p.Posts = Posts

100これは要素 (100 個の構造体)をコピーします!

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

これは のコピーを作成するp(タイプは) ため、投稿Pageの別の配列が作成され、 の要素がコピーされ、 に渡されます。100pExecuteTemplate()

は配列であるため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)
}

このコードをテストして、結果を報告してください。

于 2015-07-12T11:12:23.113 に答える