[インデックス 12987] ファイルの概要
コミット
commit fe252584f5bae7d1ce3b729b83d968c0e3b77139
Author: David Symonds <dsymonds@golang.org>
Date: Fri Apr 27 23:16:54 2012 +1000
misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6128044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fe252584f5bae7d1ce3b729b83d968c0e3b77139
元コミット内容
misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6128044
変更の背景
このコミットは、Goプロジェクトのコードレビューダッシュボード(misc/dashboard/codereview
)のフロントページにおける並行処理を簡素化し、処理時間の計測機能を追加することを目的としています。
当時のGoプロジェクトでは、コードレビュープロセスを管理するためのウェブベースのダッシュボードが利用されていました。このダッシュボードのフロントページでは、複数の異なる種類のコードレビューリスト(例:自分に割り当てられたレビュー、自分が送ったレビュー、その他のアクティブなレビュー、最近クローズされたレビューなど)を同時に表示する必要がありました。これらのリストの取得は、Google App EngineのDatastoreに対するクエリとして実行されており、それぞれが独立した処理であるため、並行して実行することでページのロード時間を短縮することが期待されます。
しかし、元の実装では、これらの並行処理が個別にgo func() { ... }()
ブロックとして記述されており、コードの重複や管理の複雑さがありました。また、各データ取得処理がどれくらいの時間を要しているかを把握するメカニズムがなかったため、パフォーマンスのボトルネックを特定し、最適化を行うための情報が不足していました。
このコミットは、これらの課題に対処し、コードの可読性と保守性を向上させるとともに、パフォーマンス分析のための基盤を導入することを意図しています。
前提知識の解説
このコミットを理解するためには、以下の技術的知識が前提となります。
- Go言語: GoはGoogleによって開発された静的型付けのコンパイル型言語です。特に、軽量な並行処理を実現するためのGoroutineとChannelの概念が重要です。
- Goroutine: Goにおける軽量なスレッドのようなものです。
go
キーワードを使って関数呼び出しの前に記述することで、その関数を新しいGoroutineで実行し、並行処理を実現します。 sync.WaitGroup
: 複数のGoroutineの完了を待つための同期プリミティブです。Add
で待つGoroutineの数を増やし、各Goroutineが完了する際にDone
を呼び出し、Wait
で全てのGoroutineが完了するまでブロックします。chan error
: エラーをGoroutine間で安全に伝達するためのチャネルです。
- Goroutine: Goにおける軽量なスレッドのようなものです。
- Google App Engine (GAE): Googleが提供するPaaS(Platform as a Service)であり、ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォームです。
- Datastore: Google App Engineが提供するNoSQLデータベースサービスです。このコミットでは、コードレビューに関するデータをDatastoreから取得するために利用されています。
appengine.Context
: App Engineアプリケーションの各リクエストに関連付けられたコンテキストオブジェクトです。DatastoreクエリなどのApp Engineサービスへの呼び出しには、このコンテキストが必要です。
- ウェブアプリケーションのフロントエンド開発: HTMLテンプレート(Goの
html/template
パッケージ)とCSSに関する基本的な知識が必要です。html/template
: Goの標準ライブラリに含まれるHTMLテンプレートエンジンです。サーバーサイドで動的にHTMLを生成するために使用されます。
- パフォーマンス計測: プログラムの実行時間を計測し、ボトルネックを特定するための基本的な概念。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
並行処理の抽象化と簡素化:
handleFront
関数内で、複数のデータ取得処理(CLs assigned to you, CLs sent by you, Other active CLs, Recently closed CLs)がそれぞれ独立したGoroutineで実行されていました。- このコミットでは、これらの並行処理を
tableFetch
という新しいヘルパー関数に抽象化しました。tableFetch
は、Goroutine内で実行される匿名関数を受け取り、その関数内でデータ取得ロジックを実行します。 - これにより、各データ取得処理の記述が簡潔になり、コードの重複が削減されました。
-
処理時間の計測機能の追加:
tableFetch
関数内で、time.Now()
を使って各データ取得処理の開始時刻を記録し、処理完了後に再度time.Now().Sub(start)
で経過時間を計算しています。- 計測された時間は、
frontPageData
構造体の新しいフィールドTiming [4]time.Duration
に格納されます。これにより、フロントページに表示される各テーブルのデータ取得にかかった時間を追跡できるようになります。 - このタイミング情報は、HTMLテンプレート内で表示され、開発者がページのパフォーマンスを分析するのに役立ちます。
-
sync.WaitGroup
の利用:tableFetch
関数は、内部でwg.Add(1)
を呼び出し、Goroutineの開始をsync.WaitGroup
に通知します。- Goroutineの実行が完了すると、
defer wg.Done()
が呼び出され、WaitGroup
のカウンターが減らされます。 handleFront
関数の最後でwg.Wait()
を呼び出すことで、全てのデータ取得Goroutineが完了するまで処理がブロックされ、全てのデータが揃ってからテンプレートのレンダリングに進むことが保証されます。
-
エラーハンドリングの改善:
- 元のコードでは、各Goroutine内でエラーが発生した場合、
errc <- err
を通じてエラーチャネルにエラーを送信していました。 tableFetch
関数に抽象化されたことで、データ取得ロジックをカプセル化し、エラーをより一貫した方法で処理できるようになりました。tableFetch
に渡される関数はエラーを返すようになり、そのエラーがerrc
に送信されます。
- 元のコードでは、各Goroutine内でエラーが発生した場合、
-
HTMLテンプレートの更新:
frontPageData
にTiming
フィールドが追加されたことに伴い、front.go
内のHTMLテンプレートも更新されました。- 新しく追加された
<address>
タグ内に、各データストアのタイミング情報が表示されるようになりました。これにより、ユーザー(または開発者)はページのロードにどの部分が寄与しているかを視覚的に確認できます。 - CSSも更新され、
<address>
タグのスタイルが定義されています。
コアとなるコードの変更箇所
misc/dashboard/codereview/dashboard/front.go
このファイルが最も大きく変更されています。
-
import
文の追加:+ "time"
time
パッケージがインポートされ、処理時間の計測に利用されます。 -
handleFront
関数の変更:tableFetch
という新しいヘルパー関数が定義されました。
この関数は、+ tableFetch := func(index int, f func(tbl *clTable) error) { + wg.Add(1) + go func() { + defer wg.Done() + start := time.Now() + if err := f(&data.Tables[index]); err != nil { + errc <- err + } + data.Timing[index] = time.Now().Sub(start) + }() + }
sync.WaitGroup
の管理、Goroutineの起動、処理時間の計測、エラーのチャネルへの送信をカプセル化します。- 既存の個別のGoroutineブロックが
tableFetch
の呼び出しに置き換えられました。--- a/misc/dashboard/codereview/dashboard/front.go +++ b/misc/dashboard/codereview/dashboard/front.go @@ -34,42 +35,42 @@ func handleFront(w http.ResponseWriter, r *http.Request) { Filter("Closed =", false). Order("-Modified") - if data.UserIsReviewer { -+ tableFetch := func(index int, f func(tbl *clTable) error) { - t wg.Add(1) - t go func() { - t defer wg.Done() - t tbl := &data.Tables[0] - t q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10) - t tbl.Title = "CLs assigned to you for review" - t tbl.Assignable = true - t if _, err := q.GetAll(c, &tbl.CLs); err != nil { - t errc <- err - t } - t }() - } - - wg.Add(1) - go func() { - defer wg.Done() - tbl := &data.Tables[1] -+ if data.UserIsReviewer { -+ t tableFetch(0, func(tbl *clTable) error { -+ t q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10) -+ t tbl.Title = "CLs assigned to you for review" -+ t tbl.Assignable = true -+ t _, err := q.GetAll(c, &tbl.CLs) -+ t return err -+ t }) -+ } -+ -+ tableFetch(1, func(tbl *clTable) error { q := activeCLs.Filter("Author =", currentPerson).Limit(10) tbl.Title = "CLs sent by you" tbl.Assignable = true - if _, err := q.GetAll(c, &tbl.CLs); err != nil { - errc <- err - } - }() -+ _, err := q.GetAll(c, &tbl.CLs) -+ return err -+ }) - - wg.Add(1) - go func() { - defer wg.Done() - tbl := &data.Tables[2] -+ tableFetch(2, func(tbl *clTable) error { q := activeCLs.Limit(50) tbl.Title = "Other active CLs" tbl.Assignable = true if _, err := q.GetAll(c, &tbl.CLs); err != nil { - errc <- err - return -+ return err } // filter if data.UserIsReviewer { @@ -81,22 +82,19 @@ func handleFront(w http.ResponseWriter, r *http.Request) {\n }\n }\n }\n - }() -+ return nil -+ }) - - wg.Add(1) - go func() { - defer wg.Done() - tbl := &data.Tables[3] -+ tableFetch(3, func(tbl *clTable) error { q := datastore.NewQuery("CL"). Filter("Closed =", true). Order("-Modified"). Limit(10) tbl.Title = "Recently closed CLs" tbl.Assignable = false - if _, err := q.GetAll(c, &tbl.CLs); err != nil { - errc <- err - } - }() -+ _, err := q.GetAll(c, &tbl.CLs) -+ return err -+ })
-
frontPageData
構造体の変更:type frontPageData struct { Tables [4]clTable + Timing [4]time.Duration Reviewers []string UserIsReviewer bool
Timing
フィールドが追加され、各テーブルのデータ取得時間を格納します。 -
HTMLテンプレートの変更:
--- a/misc/dashboard/codereview/dashboard/front.go +++ b/misc/dashboard/codereview/dashboard/front.go @@ -175,6 +174,10 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n color: blue;\n text-decoration: none; /* no link underline */\n }\n + address {\n + font-size: 10px;\n + text-align: right;\n + }\n .email {\n font-family: monospace;\n }\n @@ -235,6 +238,11 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n {{end}}\n {{end}}\n \n +<hr />\n +<address>\n +datastore timing: {{range .Timing}} {{.}}{{end}}\n +</address>\n +\n </body>\n </html>\n `))
Timing
情報を表示するための<address>
タグと、そのスタイルが追加されました。
misc/dashboard/codereview/dashboard/gc.go
このファイルは、time
パッケージのインポート順序が変更されたのみです。機能的な変更はありません。
--- a/misc/dashboard/codereview/dashboard/gc.go
+++ b/misc/dashboard/codereview/dashboard/gc.go
@@ -4,10 +4,10 @@ package dashboard
import (
"net/http"
+ "time"
"appengine"
"appengine/datastore"
- "time"
)
func init() {
コアとなるコードの解説
このコミットの核心は、front.go
におけるtableFetch
ヘルパー関数の導入です。
tableFetch := func(index int, f func(tbl *clTable) error) {
wg.Add(1) // WaitGroupに、新しいGoroutineが開始されることを通知
go func() {
defer wg.Done() // Goroutineが終了する際にWaitGroupのカウンターを減らす
start := time.Now() // 処理開始時刻を記録
if err := f(&data.Tables[index]); err != nil { // 渡された関数を実行し、エラーがあればチャネルに送信
errc <- err
}
data.Timing[index] = time.Now().Sub(start) // 処理終了時刻から経過時間を計算し、Timing配列に格納
}()
}
このtableFetch
関数は、以下の役割を担っています。
- 並行処理の統一的な管理:
wg.Add(1)
とdefer wg.Done()
により、複数のデータ取得処理がsync.WaitGroup
によって適切に同期されることを保証します。これにより、handleFront
関数は全ての並行処理が完了するまでwg.Wait()
で待機できます。 - 処理時間の自動計測: 各データ取得処理の開始時と終了時に
time.Now()
を呼び出し、その差分をtime.Duration
として計算します。この計測結果はfrontPageData.Timing
配列に自動的に格納されます。これにより、個々のデータ取得にかかる時間を簡単に把握できるようになり、パフォーマンスのボトルネック特定に役立ちます。 - エラーハンドリングの一元化: 渡された関数
f
がエラーを返した場合、そのエラーはerrc
チャネルを通じてメインのGoroutineに通知されます。これにより、エラー処理ロジックが分散することなく、一箇所で管理できるようになります。 - コードの簡素化と再利用性: 以前は各データ取得処理ごとに重複していたGoroutineの起動、WaitGroupの管理、エラーチャネルへの送信といった定型的なコードが
tableFetch
に集約されました。これにより、handleFront
関数内のコードが大幅に簡潔になり、可読性と保守性が向上しました。
例えば、以前は以下のように記述されていたコードが、
wg.Add(1)
go func() {
defer wg.Done()
tbl := &data.Tables[0]
q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
tbl.Title = "CLs assigned to you for review"
tbl.Assignable = true
if _, err := q.GetAll(c, &tbl.CLs); err != nil {
errc <- err
}
}()
tableFetch
の導入により、以下のように簡潔に記述できるようになりました。
tableFetch(0, func(tbl *clTable) error {
q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
tbl.Title = "CLs assigned to you for review"
tbl.Assignable = true
_, err := q.GetAll(c, &tbl.CLs)
return err
})
このように、tableFetch
はGoの関数型プログラミングの特性(高階関数)を活かし、共通のロジックを抽象化することで、コードの品質と効率を向上させています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Google App Engine公式ドキュメント: https://cloud.google.com/appengine/docs
- Goの
sync
パッケージドキュメント: https://pkg.go.dev/sync - Goの
time
パッケージドキュメント: https://pkg.go.dev/time
参考にした情報源リンク
- Go言語の並行処理に関するチュートリアルや記事
- Google App Engine Datastoreに関するドキュメント
- Goの
html/template
に関するドキュメント - Goのコードレビューシステムに関する一般的な情報 (GoのGerritベースのコードレビューシステムについて)# [インデックス 12987] ファイルの概要
コミット
commit fe252584f5bae7d1ce3b729b83d968c0e3b77139
Author: David Symonds <dsymonds@golang.org>
Date: Fri Apr 27 23:16:54 2012 +1000
misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6128044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/fe252584f5bae7d1ce3b729b83d968c0e3b77139
元コミット内容
misc/dashboard/codereview: simplify parallel operations for front page, and capture timing info.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/6128044
変更の背景
このコミットは、Goプロジェクトのコードレビューダッシュボード(misc/dashboard/codereview
)のフロントページにおける並行処理を簡素化し、処理時間の計測機能を追加することを目的としています。
当時のGoプロジェクトでは、コードレビュープロセスを管理するためのウェブベースのダッシュボードが利用されていました。このダッシュボードのフロントページでは、複数の異なる種類のコードレビューリスト(例:自分に割り当てられたレビュー、自分が送ったレビュー、その他のアクティブなレビュー、最近クローズされたレビューなど)を同時に表示する必要がありました。これらのリストの取得は、Google App EngineのDatastoreに対するクエリとして実行されており、それぞれが独立した処理であるため、並行して実行することでページのロード時間を短縮することが期待されます。
しかし、元の実装では、これらの並行処理が個別にgo func() { ... }()
ブロックとして記述されており、コードの重複や管理の複雑さがありました。また、各データ取得処理がどれくらいの時間を要しているかを把握するメカニズムがなかったため、パフォーマンスのボトルネックを特定し、最適化を行うための情報が不足していました。
このコミットは、これらの課題に対処し、コードの可読性と保守性を向上させるとともに、パフォーマンス分析のための基盤を導入することを意図しています。
前提知識の解説
このコミットを理解するためには、以下の技術的知識が前提となります。
- Go言語: GoはGoogleによって開発された静的型付けのコンパイル型言語です。特に、軽量な並行処理を実現するためのGoroutineとChannelの概念が重要です。
- Goroutine: Goにおける軽量なスレッドのようなものです。
go
キーワードを使って関数呼び出しの前に記述することで、その関数を新しいGoroutineで実行し、並行処理を実現します。 sync.WaitGroup
: 複数のGoroutineの完了を待つための同期プリミティブです。Add
で待つGoroutineの数を増やし、各Goroutineが完了する際にDone
を呼び出し、Wait
で全てのGoroutineが完了するまでブロックします。chan error
: エラーをGoroutine間で安全に伝達するためのチャネルです。
- Goroutine: Goにおける軽量なスレッドのようなものです。
- Google App Engine (GAE): Googleが提供するPaaS(Platform as a Service)であり、ウェブアプリケーションやモバイルバックエンドを構築・ホストするためのプラットフォームです。
- Datastore: Google App Engineが提供するNoSQLデータベースサービスです。このコミットでは、コードレビューに関するデータをDatastoreから取得するために利用されています。
appengine.Context
: App Engineアプリケーションの各リクエストに関連付けられたコンテキストオブジェクトです。DatastoreクエリなどのApp Engineサービスへの呼び出しには、このコンテキストが必要です。
- ウェブアプリケーションのフロントエンド開発: HTMLテンプレート(Goの
html/template
パッケージ)とCSSに関する基本的な知識が必要です。html/template
: Goの標準ライブラリに含まれるHTMLテンプレートエンジンです。サーバーサイドで動的にHTMLを生成するために使用されます。
- パフォーマンス計測: プログラムの実行時間を計測し、ボトルネックを特定するための基本的な概念。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
並行処理の抽象化と簡素化:
handleFront
関数内で、複数のデータ取得処理(CLs assigned to you, CLs sent by you, Other active CLs, Recently closed CLs)がそれぞれ独立したGoroutineで実行されていました。- このコミットでは、これらの並行処理を
tableFetch
という新しいヘルパー関数に抽象化しました。tableFetch
は、Goroutine内で実行される匿名関数を受け取り、その関数内でデータ取得ロジックを実行します。 - これにより、各データ取得処理の記述が簡潔になり、コードの重複が削減されました。
-
処理時間の計測機能の追加:
tableFetch
関数内で、time.Now()
を使って各データ取得処理の開始時刻を記録し、処理完了後に再度time.Now().Sub(start)
で経過時間を計算しています。- 計測された時間は、
frontPageData
構造体の新しいフィールドTiming [4]time.Duration
に格納されます。これにより、フロントページに表示される各テーブルのデータ取得にかかった時間を追跡できるようになります。 - このタイミング情報は、HTMLテンプレート内で表示され、開発者がページのパフォーマンスを分析するのに役立ちます。
-
sync.WaitGroup
の利用:tableFetch
関数は、内部でwg.Add(1)
を呼び出し、Goroutineの開始をsync.WaitGroup
に通知します。- Goroutineの実行が完了すると、
defer wg.Done()
が呼び出され、WaitGroup
のカウンターが減らされます。 handleFront
関数の最後でwg.Wait()
を呼び出すことで、全てのデータ取得Goroutineが完了するまで処理がブロックされ、全てのデータが揃ってからテンプレートのレンダリングに進むことが保証されます。
-
エラーハンドリングの改善:
- 元のコードでは、各Goroutine内でエラーが発生した場合、
errc <- err
を通じてエラーチャネルにエラーを送信していました。 tableFetch
関数に抽象化されたことで、データ取得ロジックをカプセル化し、エラーをより一貫した方法で処理できるようになりました。tableFetch
に渡される関数はエラーを返すようになり、そのエラーがerrc
に送信されます。
- 元のコードでは、各Goroutine内でエラーが発生した場合、
-
HTMLテンプレートの更新:
frontPageData
にTiming
フィールドが追加されたことに伴い、front.go
内のHTMLテンプレートも更新されました。- 新しく追加された
<address>
タグ内に、各データストアのタイミング情報が表示されるようになりました。これにより、ユーザー(または開発者)はページのロードにどの部分が寄与しているかを視覚的に確認できます。 - CSSも更新され、
<address>
タグのスタイルが定義されています。
コアとなるコードの変更箇所
misc/dashboard/codereview/dashboard/front.go
このファイルが最も大きく変更されています。
-
import
文の追加:+ "time"
time
パッケージがインポートされ、処理時間の計測に利用されます。 -
handleFront
関数の変更:tableFetch
という新しいヘルパー関数が定義されました。
この関数は、+ tableFetch := func(index int, f func(tbl *clTable) error) { + wg.Add(1) + go func() { + defer wg.Done() + start := time.Now() + if err := f(&data.Tables[index]); err != nil { + errc <- err + } + data.Timing[index] = time.Now().Sub(start) + }() + }
sync.WaitGroup
の管理、Goroutineの起動、処理時間の計測、エラーのチャネルへの送信をカプセル化します。- 既存の個別のGoroutineブロックが
tableFetch
の呼び出しに置き換えられました。--- a/misc/dashboard/codereview/dashboard/front.go +++ b/misc/dashboard/codereview/dashboard/front.go @@ -34,42 +35,42 @@ func handleFront(w http.ResponseWriter, r *http.Request) { Filter("Closed =", false). Order("-Modified") - if data.UserIsReviewer { -+ tableFetch := func(index int, f func(tbl *clTable) error) { - t wg.Add(1) - t go func() { - t defer wg.Done() - t tbl := &data.Tables[0] - t q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10) - t tbl.Title = "CLs assigned to you for review" - t tbl.Assignable = true - t if _, err := q.GetAll(c, &tbl.CLs); err != nil { - t errc <- err - t } - t }() - } - - wg.Add(1) - go func() { - defer wg.Done() - tbl := &data.Tables[1] -+ if data.UserIsReviewer { -+ t tableFetch(0, func(tbl *clTable) error { -+ t q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10) -+ t tbl.Title = "CLs assigned to you for review" -+ t tbl.Assignable = true -+ t _, err := q.GetAll(c, &tbl.CLs) -+ t return err -+ t }) -+ } -+ -+ tableFetch(1, func(tbl *clTable) error { q := activeCLs.Filter("Author =", currentPerson).Limit(10) tbl.Title = "CLs sent by you" tbl.Assignable = true - if _, err := q.GetAll(c, &tbl.CLs); err != nil { - errc <- err - } - }() -+ _, err := q.GetAll(c, &tbl.CLs) -+ return err -+ }) - - wg.Add(1) - go func() { - defer wg.Done() - tbl := &data.Tables[2] -+ tableFetch(2, func(tbl *clTable) error { q := activeCLs.Limit(50) tbl.Title = "Other active CLs" tbl.Assignable = true if _, err := q.GetAll(c, &tbl.CLs); err != nil { - errc <- err - return -+ return err } // filter if data.UserIsReviewer { @@ -81,22 +82,19 @@ func handleFront(w http.ResponseWriter, r *http.Request) {\n }\n }\n }\n - }() -+ return nil -+ }) - - wg.Add(1) - go func() { - defer wg.Done() - tbl := &data.Tables[3] -+ tableFetch(3, func(tbl *clTable) error { q := datastore.NewQuery("CL"). Filter("Closed =", true). Order("-Modified"). Limit(10) tbl.Title = "Recently closed CLs" tbl.Assignable = false - if _, err := q.GetAll(c, &tbl.CLs); err != nil { - errc <- err - } - }() -+ _, err := q.GetAll(c, &tbl.CLs) -+ return err -+ })
-
frontPageData
構造体の変更:type frontPageData struct { Tables [4]clTable + Timing [4]time.Duration Reviewers []string UserIsReviewer bool
Timing
フィールドが追加され、各テーブルのデータ取得時間を格納します。 -
HTMLテンプレートの変更:
--- a/misc/dashboard/codereview/dashboard/front.go +++ b/misc/dashboard/codereview/dashboard/front.go @@ -175,6 +174,10 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n color: blue;\n text-decoration: none; /* no link underline */\n }\n + address {\n + font-size: 10px;\n + text-align: right;\n + }\n .email {\n font-family: monospace;\n }\n @@ -235,6 +238,11 @@ var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{\n {{end}}\n {{end}}\n \n +<hr />\n +<address>\n +datastore timing: {{range .Timing}} {{.}}{{end}}\n +</address>\n +\n </body> </html> `))
Timing
情報を表示するための<address>
タグと、そのスタイルが追加されました。
misc/dashboard/codereview/dashboard/gc.go
このファイルは、time
パッケージのインポート順序が変更されたのみです。機能的な変更はありません。
--- a/misc/dashboard/codereview/dashboard/gc.go
+++ b/misc/dashboard/codereview/dashboard/gc.go
@@ -4,10 +4,10 @@ package dashboard
import (
"net/http"
+ "time"
"appengine"
"appengine/datastore"
- "time"
)
func init() {
コアとなるコードの解説
このコミットの核心は、front.go
におけるtableFetch
ヘルパー関数の導入です。
tableFetch := func(index int, f func(tbl *clTable) error) {
wg.Add(1) // WaitGroupに、新しいGoroutineが開始されることを通知
go func() {
defer wg.Done() // Goroutineが終了する際にWaitGroupのカウンターを減らす
start := time.Now() // 処理開始時刻を記録
if err := f(&data.Tables[index]); err != nil { // 渡された関数を実行し、エラーがあればチャネルに送信
errc <- err
}
data.Timing[index] = time.Now().Sub(start) // 処理終了時刻から経過時間を計算し、Timing配列に格納
}()
}
このtableFetch
関数は、以下の役割を担っています。
- 並行処理の統一的な管理:
wg.Add(1)
とdefer wg.Done()
により、複数のデータ取得処理がsync.WaitGroup
によって適切に同期されることを保証します。これにより、handleFront
関数は全ての並行処理が完了するまでwg.Wait()
で待機できます。 - 処理時間の自動計測: 各データ取得処理の開始時と終了時に
time.Now()
を呼び出し、その差分をtime.Duration
として計算します。この計測結果はfrontPageData.Timing
配列に自動的に格納されます。これにより、個々のデータ取得にかかる時間を簡単に把握できるようになり、パフォーマンスのボトルネック特定に役立ちます。 - エラーハンドリングの一元化: 渡された関数
f
がエラーを返した場合、そのエラーはerrc
チャネルを通じてメインのGoroutineに通知されます。これにより、エラー処理ロジックが分散することなく、一箇所で管理できるようになります。 - コードの簡素化と再利用性: 以前は各データ取得処理ごとに重複していたGoroutineの起動、WaitGroupの管理、エラーチャネルへの送信といった定型的なコードが
tableFetch
に集約されました。これにより、handleFront
関数内のコードが大幅に簡潔になり、可読性と保守性が向上しました。
例えば、以前は以下のように記述されていたコードが、
wg.Add(1)
go func() {
defer wg.Done()
tbl := &data.Tables[0]
q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
tbl.Title = "CLs assigned to you for review"
tbl.Assignable = true
if _, err := q.GetAll(c, &tbl.CLs); err != nil {
errc <- err
}
}()
tableFetch
の導入により、以下のように簡潔に記述できるようになりました。
tableFetch(0, func(tbl *clTable) error {
q := activeCLs.Filter("Reviewer =", currentPerson).Limit(10)
tbl.Title = "CLs assigned to you for review"
tbl.Assignable = true
_, err := q.GetAll(c, &tbl.CLs)
return err
})
このように、tableFetch
はGoの関数型プログラミングの特性(高階関数)を活かし、共通のロジックを抽象化することで、コードの品質と効率を向上させています。
関連リンク
- Go言語公式ドキュメント: https://go.dev/doc/
- Google App Engine公式ドキュメント: https://cloud.google.com/appengine/docs
- Goの
sync
パッケージドキュメント: https://pkg.go.dev/sync - Goの
time
パッケージドキュメント: https://pkg.go.dev/time
参考にした情報源リンク
- Go言語の並行処理に関するチュートリアルや記事
- Google App Engine Datastoreに関するドキュメント
- Goの
html/template
に関するドキュメント - Goのコードレビューシステムに関する一般的な情報 (GoのGerritベースのコードレビューシステムについて)