Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11382] ファイルの概要

このコミットは、Go言語の標準ライブラリである go/printer パッケージにおけるコードのファクタリングと、パフォーマンス改善のためのキャッシュ導入に関するものです。具体的には、頻繁に呼び出される token.Position の取得処理を最適化し、特に行番号の取得においてキャッシュメカニズムを導入することで、わずかながらも処理速度の向上を図っています。

コミット

go/printer: factor some frequently used code

Added a cache to compensate for extra call overhead.
go test -bench=Print marginally faster (in the noise).

R=r
CC=golang-dev
https://golang.org/cl/5574061

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/8efc020d7ca00e402ff9fb2b7eaf3e441d0979d4

元コミット内容

diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go
index 0f4e72b5f1..5f3b4d4a74 100644
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -72,7 +72,7 @@ func (p *printer) setComment(g *ast.CommentGroup) {
 		// for some reason there are pending comments; this
 		// should never happen - handle gracefully and flush
 		// all comments up to g, ignore anything after that
-		p.flush(p.fset.Position(g.List[0].Pos()), token.ILLEGAL)
+		p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL)
 	}
 	p.comments[0] = g
 	p.cindex = 0
@@ -122,10 +122,10 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
 		p.print(blank)
 	}
 
-	prev := p.fset.Position(prev0)
-	next := p.fset.Position(next0)
-	line := p.fset.Position(list[0].Pos()).Line
-	endLine := p.fset.Position(list[len(list)-1].End()).Line
+	prev := p.posFor(prev0)
+	next := p.posFor(next0)
+	line := p.lineFor(list[0].Pos())
+	endLine := p.lineFor(list[len(list)-1].End())
 
 	if prev.IsValid() && prev.Line == line && line == endLine {
 		// all list entries on a single line
@@ -169,7 +169,7 @@ func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exp
 	// print all list elements
 	for i, x := range list {
 		prevLine := line
-		line = p.fset.Position(x.Pos()).Line
+		line = p.lineFor(x.Pos())
 
 		// determine if the next linebreak, if any, needs to use formfeed:
 		// in general, use the entire node size to make the decision; for
@@ -272,16 +272,16 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
 	p.print(fields.Opening, token.LPAREN)
 	if len(fields.List) > 0 {
 		prevLine := p.fset.Position(fields.Opening).Line
-		prevLine := p.lineFor(fields.Opening)
 		ws := indent
 		for i, par := range fields.List {
 			// determine par begin and end line (may be different
 			// if there are multiple parameter names for this par
 			// or the type is on a separate line)
 			var parLineBeg int
-			var parLineEnd = p.fset.Position(par.Type.Pos()).Line
+			var parLineEnd = p.lineFor(par.Type.Pos())
 			if len(par.Names) > 0 {
-				parLineBeg = p.fset.Position(par.Names[0].Pos()).Line
+				parLineBeg = p.lineFor(par.Names[0].Pos())
 			} else {
 				parLineBeg = parLineEnd
 			}
@@ -314,7 +314,7 @@ func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
 		}
 		// if the closing ")" is on a separate line from the last parameter,
 		// print an additional "," and line break
-		if closing := p.fset.Position(fields.Closing).Line; 0 < prevLine && prevLine < closing {
+		if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing {
 			p.print(",")
 			p.linebreak(closing, 0, ignore, true)
 		}
@@ -380,8 +380,8 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
 	lbrace := fields.Opening
 	list := fields.List
 	rbrace := fields.Closing
-	hasComments := isIncomplete || p.commentBefore(p.fset.Position(rbrace))
-	srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.fset.Position(lbrace).Line == p.fset.Position(rbrace).Line
+	hasComments := isIncomplete || p.commentBefore(p.posFor(rbrace))
+	srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.lineFor(lbrace) == p.lineFor(rbrace)
 
 	if !hasComments && srcIsOneLine {
 		// possibly a one-line struct/interface
@@ -424,7 +424,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
 		var ml bool
 		for i, f := range list {
 			if i > 0 {
-				p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
+				p.linebreak(p.lineFor(f.Pos()), 1, ignore, ml)
 			}
 			ml = false
 			extraTabs := 0
@@ -459,7 +459,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
 			if len(list) > 0 {
 				p.print(formfeed)
 			}
-			p.flush(p.fset.Position(rbrace), token.RBRACE) // make sure we don't lose the last line comment
+			p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment
 			p.setLineComment("// contains filtered or unexported fields")
 		}
 
@@ -468,7 +468,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
 		var ml bool
 		for i, f := range list {
 			if i > 0 {
-				p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
+				p.linebreak(p.lineFor(f.Pos()), 1, ignore, ml)
 			}
 			ml = false
 			p.setComment(f.Doc)
@@ -486,7 +486,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool)
 			if len(list) > 0 {
 				p.print(formfeed)
 			}
-			p.flush(p.fset.Position(rbrace), token.RBRACE) // make sure we don't lose the last line comment
+			p.flush(p.posFor(rbrace), token.RBRACE) // make sure we don't lose the last line comment
 			p.setLineComment("// contains filtered or unexported methods")
 		}
 
@@ -642,7 +642,7 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiL
 		p.print(blank)
 	}
 	xline := p.pos.Line // before the operator (it may be on the next line!)
-	yline := p.fset.Position(x.Y.Pos()).Line
+	yline := p.lineFor(x.Y.Pos())
 	p.print(x.OpPos, x.Op)
 	if xline != yline && xline > 0 && yline > 0 {
 		// at least one line break, but respect an extra empty line
@@ -935,7 +935,7 @@ func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) {
 	for i, s := range list {
 		// _indent == 0 only for lists of switch/select case clauses;
 		// in those cases each clause is a new section
-		p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, i == 0 || _indent == 0 || multiLine)
+		p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || _indent == 0 || multiLine)
 		multiLine = false
 		p.stmt(s, nextIsRBrace && i == len(list)-1, &multiLine)
 	}
@@ -948,7 +948,7 @@ func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) {
 func (p *printer) block(s *ast.BlockStmt, indent int) {
 	p.print(s.Pos(), token.LBRACE)
 	p.stmtList(s.List, indent, true)
-	p.linebreak(p.fset.Position(s.Rbrace).Line, 1, ignore, true)
+	p.linebreak(p.lineFor(s.Rbrace), 1, ignore, true)
 	p.print(s.Rbrace, token.RBRACE)
 }
 
@@ -1049,7 +1049,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
 				break
 			}
 		} else {
-			p.linebreak(p.fset.Position(s.Stmt.Pos()).Line, 1, ignore, true)
+			p.linebreak(p.lineFor(s.Stmt.Pos()), 1, ignore, true)
 		}
 		p.stmt(s.Stmt, nextIsRBrace, multiLine)
 
@@ -1161,7 +1161,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
 	case *ast.SelectStmt:
 		p.print(token.SELECT, blank)
 		body := s.Body
-		if len(body.List) == 0 && !p.commentBefore(p.fset.Position(body.Rbrace)) {
+		if len(body.List) == 0 && !p.commentBefore(p.posFor(body.Rbrace)) {
 			// print empty select statement w/o comments on one line
 			p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE)
 		} else {
@@ -1353,7 +1353,7 @@ func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) {
 				var ml bool
 				for i, s := range d.Specs {
 					if i > 0 {
-						p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, ml)
+						p.linebreak(p.lineFor(s.Pos()), 1, ignore, ml)
 					}
 					ml = false
 					p.valueSpec(s.(*ast.ValueSpec), keepType[i], false, &ml)
@@ -1362,7 +1362,7 @@ func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) {
 				var ml bool
 				for i, s := range d.Specs {
 					if i > 0 {
-						p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, ml)
+						p.linebreak(p.lineFor(s.Pos()), 1, ignore, ml)
 					}
 					ml = false
 					p.spec(s, n, false, &ml)
@@ -1419,11 +1419,11 @@ func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
 func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool {
 	pos1 := b.Pos()
 	pos2 := b.Rbrace
-	if pos1.IsValid() && pos2.IsValid() && p.fset.Position(pos1).Line != p.fset.Position(pos2).Line {
+	if pos1.IsValid() && pos2.IsValid() && p.lineFor(pos1) != p.lineFor(pos2) {
 		// opening and closing brace are on different lines - don't make it a one-liner
 		return false
 	}
-	if len(b.List) > 5 || p.commentBefore(p.fset.Position(pos2)) {
+	if len(b.List) > 5 || p.commentBefore(p.posFor(pos2)) {
 		// too many statements or there is a comment inside - don't make it a one-liner
 		return false
 	}
@@ -1474,7 +1474,7 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi
 // are on the same line; if they are on different lines (or unknown)
 // the result is infinity.\n func (p *printer) distance(from0 token.Pos, to token.Position) int {
-	from := p.fset.Position(from0)
+	from := p.posFor(from0)
 	if from.IsValid() && to.IsValid() && from.Line == to.Line {
 		return to.Column - from.Column
 	}
@@ -1543,7 +1543,7 @@ func (p *printer) file(src *ast.File) {
 			if prev != tok || getDoc(d) != nil {
 				min = 2
 			}
-			p.linebreak(p.fset.Position(d.Pos()).Line, min, ignore, false)
+			p.linebreak(p.lineFor(d.Pos()), min, ignore, false)
 			p.decl(d, ignoreMultiLine)
 		}
 	}
diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go
index c720f2e665..52dfff6f4b 100644
--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -75,6 +75,10 @@ type printer struct {
 
 	// Cache of already computed node sizes.
 	nodeSizes map[ast.Node]int
+
+	// Cache of most recently computed line position.
+	cachedPos  token.Pos
+	cachedLine int // line corresponding to cachedPos
 }
 
 func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
@@ -82,6 +86,7 @@ func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]
 	p.fset = fset
 	p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
 	p.nodeSizes = nodeSizes
+	p.cachedPos = -1
 }
 
 func (p *printer) internalError(msg ...interface{}) {
@@ -92,6 +97,19 @@ func (p *printer) internalError(msg ...interface{}) {
 	}
 }\n+func (p *printer) posFor(pos token.Pos) token.Position {
+\t// not used frequently enough to cache entire token.Position
+\treturn p.fset.Position(pos)
+}\n+\n+func (p *printer) lineFor(pos token.Pos) int {
+\tif pos != p.cachedPos {
+\t\tp.cachedPos = pos
+\t\tp.cachedLine = p.fset.Position(pos).Line
+\t}\n+\treturn p.cachedLine
+}\n+\n // writeByte writes ch to p.output and updates p.pos.
 func (p *printer) writeByte(ch byte) {
 	p.output.WriteByte(ch)
@@ -529,7 +547,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
 
 	// shortcut common case of //-style comments
 	if text[1] == '/' {
-		p.writeItem(p.fset.Position(comment.Pos()), text, true)
+		p.writeItem(p.posFor(comment.Pos()), text, true)
 		return
 	}
 
@@ -540,7 +558,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
 
 	// write comment lines, separated by formfeed,
 	// without a line break after the last line
-	pos := p.fset.Position(comment.Pos())
+	pos := p.posFor(comment.Pos())
 	for i, line := range lines {
 		if i > 0 {
 			p.writeByte('\f')
@@ -602,14 +620,14 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (wro
 	var last *ast.Comment
 	for ; p.commentBefore(next); p.cindex++ {
 		for _, c := range p.comments[p.cindex].List {
-			p.writeCommentPrefix(p.fset.Position(c.Pos()), next, last, c, tok.IsKeyword())
+			p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok.IsKeyword())
 			p.writeComment(c)
 			last = c
 		}
 	}
 
 	if last != nil {
-		if last.Text[1] == '*' && p.fset.Position(last.Pos()).Line == next.Line {
+		if last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line {
 			// the last comment is a /*-style comment and the next item
 			// follows on the same line: separate with an extra blank
 			p.writeByte(' ')
@@ -770,7 +788,7 @@ func (p *printer) print(args ...interface{}) {
 		case token.Pos:
 			if x.IsValid() {
-				next = p.fset.Position(x) // accurate position of next item
+				next = p.posFor(x) // accurate position of next item
 			}
 			tok = p.lastTok
 		case string:
@@ -813,7 +831,7 @@ func (p *printer) print(args ...interface{}) {
 // before the next position in the source code.
 //
 func (p *printer) commentBefore(next token.Position) bool {
-	return p.cindex < len(p.comments) && p.fset.Position(p.comments[p.cindex].List[0].Pos()).Offset < next.Offset
+	return p.cindex < len(p.comments) && p.posFor(p.comments[p.cindex].List[0].Pos()).Offset < next.Offset
 }
 
 // Flush prints any pending comments and whitespace occurring textually

変更の背景

このコミットの主な目的は、Go言語のコードフォーマッタである go/printer パッケージのパフォーマンスを微調整することです。特に、ソースコード内の位置情報(token.Pos)から行番号(token.Position.Line)を取得する処理が頻繁に実行されており、この処理のオーバーヘッドを削減することが狙いです。

コミットメッセージには「factor some frequently used code」(頻繁に使用されるコードをファクタリングする)とあり、これはコードの重複を排除し、より効率的で保守しやすい構造に再編成することを意味します。また、「Added a cache to compensate for extra call overhead」(追加の呼び出しオーバーヘッドを補償するためにキャッシュを追加した)と明記されており、特定の計算結果を再利用することで、繰り返し発生する計算コストを削減しようとしていることがわかります。

ベンチマーク結果として「go test -bench=Print marginally faster (in the noise)」(go test -bench=Print がわずかに高速化された(誤差の範囲内))とあるように、劇的なパフォーマンス改善ではなく、細かな最適化の一環として行われた変更であることが示唆されます。しかし、このような小さな最適化の積み重ねが、大規模なコードベースや頻繁に利用されるツールにおいては全体的な効率向上に寄与します。

前提知識の解説

このコミットを理解するためには、Go言語の以下のパッケージと一般的なプログラミング概念についての知識が必要です。

Go言語の go/ast パッケージ

go/ast パッケージは、Go言語のソースコードを抽象構文木(Abstract Syntax Tree, AST)として表現するためのデータ構造を提供します。ASTは、プログラムの構造を木構造で表現したもので、コンパイラやコード分析ツール、フォーマッタなどがソースコードを解析・操作する際に利用します。

  • ast.Node: AST内のすべてのノードが実装するインターフェース。
  • ast.CommentGroup: コメントのグループを表す構造体。
  • ast.Expr: 式を表すインターフェース。
  • ast.FieldList: 構造体や関数の引数リストなどのフィールドのリストを表す構造体。
  • ast.Stmt: 文を表すインターフェース。
  • ast.BlockStmt: ブロック文({ ... })を表す構造体。
  • ast.GenDecl: import, const, type, var などの一般的な宣言を表す構造体。
  • ast.ValueSpec: 変数や定数の宣言を表す構造体。
  • ast.File: 単一のGoソースファイル全体を表す構造体。

go/printer パッケージは、このASTを受け取り、整形されたGoコードを出力します。

Go言語の go/token パッケージ

go/token パッケージは、Goソースコード内のトークン(キーワード、識別子、演算子など)と、それらのソースファイル内の位置情報を扱うための機能を提供します。

  • token.Pos: ソースファイル内の位置を表す型。これは単なる整数値であり、ファイルセット内のオフセットを示します。
  • token.FileSet: 複数のソースファイルを管理し、token.Pos からより詳細な位置情報(ファイル名、行番号、列番号、オフセット)を取得するためのマップを提供する構造体。
  • token.Position: token.FileSet を介して token.Pos から変換される、具体的なソースコード上の位置情報(Filename, Offset, Line, Column)を持つ構造体。

go/printer パッケージは、ASTノードが持つ token.Pos を利用して、整形時に正確な位置情報を参照し、コメントの配置や改行の判断などを行います。

Go言語の go/printer パッケージ

go/printer パッケージは、go/ast パッケージで表現された抽象構文木を、Go言語の標準的なフォーマット規則に従って整形し、出力する機能を提供します。gofmt コマンドの基盤となるパッケージの一つです。このパッケージは、コードの可読性を高め、一貫したスタイルを維持するために非常に重要です。

キャッシング

キャッシングとは、計算コストの高い操作の結果を一時的に保存し、同じ入力に対して再度計算を行う代わりに保存された結果を再利用する最適化手法です。これにより、処理速度を向上させ、システムのリソース消費を削減できます。

このコミットでは、token.Pos から token.Position.Line を取得する処理が頻繁に発生するため、この結果をキャッシュすることでパフォーマンス改善を図っています。

技術的詳細

このコミットの核心は、go/printer パッケージ内の printer 構造体に、token.Pos から行番号を取得する処理を効率化するための新しいヘルパーメソッド posForlineFor を導入した点にあります。

以前のコードでは、p.fset.Position(pos) を直接呼び出して token.Position オブジェクトを取得し、そこから .Line フィールドにアクセスして行番号を得ていました。p.fset.Position(pos) の呼び出しは、token.FileSet 内のマップ検索を伴うため、ある程度のオーバーヘッドがあります。特に、同じ token.Pos に対して繰り返し行番号を取得する場合、このオーバーヘッドが累積します。

新しい lineFor メソッドは、この問題を解決するためにキャッシュメカニズムを導入しています。

  • printer.cachedPosprinter.cachedLine: printer 構造体に新しく追加されたフィールドで、直前に計算された token.Pos とそれに対応する行番号を保持します。
  • printer.lineFor(pos token.Pos) int:
    • このメソッドが呼び出されると、まず引数 posp.cachedPos と同じかどうかをチェックします。
    • もし同じであれば、前回の計算結果である p.cachedLine を即座に返します。これにより、p.fset.Position(pos) の呼び出しとそれに伴うマップ検索をスキップできます。
    • posp.cachedPos と異なる場合、p.fset.Position(pos).Line を呼び出して新しい行番号を計算します。そして、この新しい pos と計算された行番号を p.cachedPosp.cachedLine に保存し、その行番号を返します。

一方、printer.posFor(pos token.Pos) token.Position メソッドも導入されていますが、このメソッドは単に p.fset.Position(pos) をラップしているだけで、内部的なキャッシュは持っていません。コメントには「not used frequently enough to cache entire token.Position」(token.Position 全体をキャッシュするほど頻繁には使われない)とあり、token.Position オブジェクト全体のキャッシュは、そのメリットがオーバーヘッドを上回らないと判断されたことを示しています。これは、token.Position が構造体であり、コピーのコストやキャッシュの管理コストが、頻繁な呼び出しによるメリットを相殺する可能性があるためと考えられます。

この変更により、特にループ内で同じ token.Pos の行番号を繰り返し参照するようなシナリオにおいて、go/printer のパフォーマンスがわずかに向上することが期待されます。コミットメッセージにある「marginally faster (in the noise)」という表現は、この最適化が非常に細かいレベルで行われ、全体的な実行時間に対する影響は小さいものの、積み重ねによって効果を発揮するタイプの改善であることを示しています。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更は、主に以下の2つのファイルに集中しています。

  1. src/pkg/go/printer/printer.go:

    • printer 構造体に新しいフィールド cachedPos (型: token.Pos) と cachedLine (型: int) が追加されました。これらは行番号キャッシュのために使用されます。
    • printer.init メソッド内で p.cachedPos = -1 が追加され、キャッシュが初期化されます。
    • 新しいヘルパーメソッド posFor(pos token.Pos) token.Position が追加されました。これは p.fset.Position(pos) の単純なラッパーです。
    • 新しいヘルパーメソッド lineFor(pos token.Pos) int が追加されました。このメソッドが行番号のキャッシュロジックを実装しています。
  2. src/pkg/go/printer/nodes.go:

    • p.fset.Position(pos) の呼び出しが、新しいヘルパーメソッド p.posFor(pos) に置き換えられました。
    • p.fset.Position(pos).Line の呼び出しが、新しいヘルパーメソッド p.lineFor(pos) に置き換えられました。この変更が最も多く、nodes.go 内の多くの場所で行われています。

コアとなるコードの解説

src/pkg/go/printer/printer.go の変更

type printer struct {
	// ... 既存のフィールド ...

	// Cache of most recently computed line position.
	cachedPos  token.Pos
	cachedLine int // line corresponding to cachedPos
}

func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
	// ... 既存の初期化 ...
	p.cachedPos = -1 // キャッシュの初期化
}

func (p *printer) posFor(pos token.Pos) token.Position {
	// not used frequently enough to cache entire token.Position
	return p.fset.Position(pos)
}

func (p *printer) lineFor(pos token.Pos) int {
	if pos != p.cachedPos { // キャッシュされた位置と異なる場合
		p.cachedPos = pos // キャッシュを更新
		p.cachedLine = p.fset.Position(pos).Line // 行番号を計算してキャッシュ
	}
	return p.cachedLine // キャッシュされた行番号を返す
}
  • cachedPoscachedLine: printer 構造体のこれらの新しいフィールドは、直近で取得された token.Pos とそれに対応する行番号を保持します。cachedPostoken.Pos のデフォルト値である -1 で初期化されることで、最初の呼び出し時には必ずキャッシュミスとなり、実際の計算が行われるようになっています。
  • posFor メソッド: このメソッドは、token.Pos から token.Position オブジェクトを取得するためのラッパーです。コメントにあるように、token.Position オブジェクト全体をキャッシュするほどの頻度ではないため、単純に p.fset.Position(pos) を呼び出すだけです。
  • lineFor メソッド: このメソッドが、行番号取得のキャッシュロジックの肝です。
    • 引数 posp.cachedPos と同じであれば、前回の計算結果である p.cachedLine をそのまま返します。これにより、p.fset.Position(pos) の呼び出しを回避し、パフォーマンスを向上させます。
    • posp.cachedPos と異なる場合(キャッシュミス)、p.fset.Position(pos).Line を呼び出して行番号を計算し、その結果を p.cachedLine に、対応する posp.cachedPos にそれぞれ保存(キャッシュ)してから、計算された行番号を返します。

src/pkg/go/printer/nodes.go の変更

nodes.go では、go/printer がASTノードを処理する際に、ソースコード上の位置情報や行番号を頻繁に参照します。このコミットでは、これらの参照が新しいヘルパーメソッドに置き換えられています。

例:

- p.flush(p.fset.Position(g.List[0].Pos()), token.ILLEGAL)
+ p.flush(p.posFor(g.List[0].Pos()), token.ILLEGAL)

- line := p.fset.Position(list[0].Pos()).Line
+ line := p.lineFor(list[0].Pos())

- if closing := p.fset.Position(fields.Closing).Line; 0 < prevLine && prevLine < closing {
+ if closing := p.lineFor(fields.Closing); 0 < prevLine && prevLine < closing {

これらの変更により、nodes.go 内の多くの場所で直接 p.fset.Position(...)p.fset.Position(...).Line を呼び出す代わりに、p.posFor(...)p.lineFor(...) を使用するようになりました。これにより、コードの可読性が向上し、特に lineFor メソッドの導入によって、行番号の取得処理が効率化されています。

関連リンク

参考にした情報源リンク