From 5c36154702a1794e11abd5f83e05addeccdf0db3 Mon Sep 17 00:00:00 2001 From: Corey Schuman Date: Mon, 5 Jan 2026 22:06:56 -0500 Subject: [PATCH] perf: fix 34 self-detected performance issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run goperf on itself and fix all actionable issues: Slice preallocation (33 fixes): - Convert `var issues []Issue` to `make([]Issue, 0, N)` across 25+ Check() functions in all rule files - Preallocate in analyzer.go, fixer.go, main.go, types.go - Add capacity hints to helper function return slices String concatenation (1 fix): - Convert += to strings.Builder in benchmark.go generateBenchmarkCode() - Convert += to strings.Builder in benchmark.go joinPatterns() Map size hints: - Add size hint to byFile map in fixer.go Line splitting optimization: - Count newlines first to preallocate in types.go splitLines() Results: 147 issues → 113 issues (34 fixed) Remaining 113 are false positives (AST traversal loops) or low-priority suggestions (benchmark recommendations). --- fixer/fixer.go | 9 +++++---- main.go | 7 ++++--- reporter/console.go | 4 +++- rules/algorithm.go | 4 ++-- rules/allocation.go | 6 +++--- rules/analyzer.go | 22 ++++++++++++++-------- rules/benchmark.go | 45 +++++++++++++++++++++++++++----------------- rules/cache.go | 8 ++++---- rules/concurrency.go | 6 +++--- rules/context.go | 6 +++--- rules/database.go | 4 ++-- rules/dbpool.go | 4 ++-- rules/http.go | 8 ++++---- rules/interface.go | 6 +++--- rules/io.go | 8 ++++---- rules/memory.go | 6 +++--- rules/types.go | 9 ++++++++- 17 files changed, 95 insertions(+), 67 deletions(-) diff --git a/fixer/fixer.go b/fixer/fixer.go index 71e588c..0ef2cb1 100644 --- a/fixer/fixer.go +++ b/fixer/fixer.go @@ -82,10 +82,11 @@ func validatePathForWrite(filename string) error { // FixIssues attempts to fix the given issues func (f *Fixer) FixIssues(issues []rules.Issue) []Fix { - var fixes []Fix + // Preallocate: assume ~1 fix per issue + fixes := make([]Fix, 0, len(issues)) - // Group issues by file - byFile := make(map[string][]rules.Issue) + // Group issues by file with size hint + byFile := make(map[string][]rules.Issue, len(issues)/2+1) for _, issue := range issues { byFile[issue.File] = append(byFile[issue.File], issue) } @@ -99,7 +100,7 @@ func (f *Fixer) FixIssues(issues []rules.Issue) []Fix { } func (f *Fixer) fixFile(filename string, issues []rules.Issue) []Fix { - var fixes []Fix + fixes := make([]Fix, 0, len(issues)) // Validate path before any operations if err := validatePathForWrite(filename); err != nil { diff --git a/main.go b/main.go index 010fc9a..396ded5 100644 --- a/main.go +++ b/main.go @@ -109,8 +109,8 @@ Auto-Fix Support: Verbose: *verboseFlag, }) - // Collect Go files - var files []string + // Collect Go files - estimate ~100 files per pattern + files := make([]string, 0, len(paths)*100) for _, pattern := range paths { matches, err := collectGoFiles(pattern, ignorePaths) if err != nil { @@ -236,7 +236,8 @@ func validatePath(path string) error { } func collectGoFiles(pattern string, ignorePaths []string) ([]string, error) { - var files []string + // Preallocate with reasonable estimate + files := make([]string, 0, 64) fileCount := 0 // Validate the base pattern first diff --git a/reporter/console.go b/reporter/console.go index 8d8cd53..1e0ed94 100644 --- a/reporter/console.go +++ b/reporter/console.go @@ -176,7 +176,9 @@ func wordWrap(text string, width int, indent string) string { return text } - var lines []string + // Estimate lines needed: text length / width + estimatedLines := len(text)/width + 1 + lines := make([]string, 0, estimatedLines) var currentLine strings.Builder for _, word := range words { diff --git a/rules/algorithm.go b/rules/algorithm.go index 4c683f7..cfa50ca 100644 --- a/rules/algorithm.go +++ b/rules/algorithm.go @@ -18,7 +18,7 @@ func (r *NestedRangeRule) Name() string { return "nested-range" } func (r *NestedRangeRule) Category() string { return "algorithm" } func (r *NestedRangeRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) @@ -187,7 +187,7 @@ func (r *LinearSearchInLoopRule) Name() string { return "linear-search-in-lo func (r *LinearSearchInLoopRule) Category() string { return "algorithm" } func (r *LinearSearchInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) diff --git a/rules/allocation.go b/rules/allocation.go index 7723970..4cd5a6c 100644 --- a/rules/allocation.go +++ b/rules/allocation.go @@ -19,7 +19,7 @@ func (r *UnpreallocatedSliceRule) Name() string { return "unpreallocated-sli func (r *UnpreallocatedSliceRule) Category() string { return "allocation" } func (r *UnpreallocatedSliceRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 8) // For each function, track preallocated slices ast.Inspect(file, func(n ast.Node) bool { @@ -186,7 +186,7 @@ func (r *StringConcatInLoopRule) Name() string { return "string-concat-loop" func (r *StringConcatInLoopRule) Category() string { return "allocation" } func (r *StringConcatInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Track strings.Builder usage builders := findStringBuilders(file) @@ -320,7 +320,7 @@ func (r *MapWithoutSizeRule) Name() string { return "map-without-size" } func (r *MapWithoutSizeRule) Category() string { return "allocation" } func (r *MapWithoutSizeRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Only flag maps that are actually populated in loops ast.Inspect(file, func(n ast.Node) bool { diff --git a/rules/analyzer.go b/rules/analyzer.go index d971f1c..1794215 100644 --- a/rules/analyzer.go +++ b/rules/analyzer.go @@ -16,7 +16,12 @@ type Analyzer struct { // NewAnalyzer creates a new analyzer with the given config func NewAnalyzer(config AnalyzerConfig) *Analyzer { - a := &Analyzer{config: config} + // Estimate total rules: ~3 rules per category on average + estimatedRules := len(config.Rules) * 3 + a := &Analyzer{ + config: config, + rules: make([]Rule, 0, estimatedRules), + } // Collect rules based on config for _, category := range config.Rules { @@ -30,7 +35,8 @@ func NewAnalyzer(config AnalyzerConfig) *Analyzer { // Analyze runs all rules against the given files func (a *Analyzer) Analyze(files []string) []Issue { - var allIssues []Issue + // Preallocate with estimate: ~2 issues per file on average + allIssues := make([]Issue, 0, len(files)*2) fset := token.NewFileSet() @@ -90,7 +96,7 @@ func (a *Analyzer) Analyze(files []string) []Issue { // FindNestedRangeLoops finds nested for-range loops func FindNestedRangeLoops(file *ast.File, fset *token.FileSet) []ast.Node { - var nested []ast.Node + nested := make([]ast.Node, 0, 4) // Most files have few nested loops ast.Inspect(file, func(n ast.Node) bool { outerRange, ok := n.(*ast.RangeStmt) @@ -116,7 +122,7 @@ func FindNestedRangeLoops(file *ast.File, fset *token.FileSet) []ast.Node { // FindAppendInLoop finds append calls inside loops without preallocation func FindAppendInLoop(file *ast.File, fset *token.FileSet) []AppendInLoopInfo { - var results []AppendInLoopInfo + results := make([]AppendInLoopInfo, 0, 8) // Typical file has few append-in-loop issues ast.Inspect(file, func(n ast.Node) bool { // Look for for statements (both range and regular) @@ -174,7 +180,7 @@ type AppendInLoopInfo struct { // FindStringConcatInLoop finds string concatenation in loops func FindStringConcatInLoop(file *ast.File, fset *token.FileSet) []token.Position { - var results []token.Position + results := make([]token.Position, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt @@ -215,7 +221,7 @@ func FindStringConcatInLoop(file *ast.File, fset *token.FileSet) []token.Positio // FindSQLInLoop finds database query patterns inside loops func FindSQLInLoop(file *ast.File, fset *token.FileSet) []SQLInLoopInfo { - var results []SQLInLoopInfo + results := make([]SQLInLoopInfo, 0, 4) sqlMethods := map[string]bool{ "Query": true, @@ -277,7 +283,7 @@ type SQLInLoopInfo struct { // FindUnbufferedChannels finds unbuffered channel creation func FindUnbufferedChannels(file *ast.File, fset *token.FileSet) []token.Position { - var results []token.Position + results := make([]token.Position, 0, 4) ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) @@ -347,7 +353,7 @@ func FindMutexHotspots(file *ast.File, fset *token.FileSet) map[string]int { // FindJSONInLoop finds JSON marshal/unmarshal calls in loops func FindJSONInLoop(file *ast.File, fset *token.FileSet) []JSONInLoopInfo { - var results []JSONInLoopInfo + results := make([]JSONInLoopInfo, 0, 4) jsonFuncs := map[string]bool{ "Marshal": true, diff --git a/rules/benchmark.go b/rules/benchmark.go index d0ab3ad..195017b 100644 --- a/rules/benchmark.go +++ b/rules/benchmark.go @@ -3,6 +3,7 @@ package rules import ( "go/ast" "go/token" + "strings" ) func init() { @@ -132,33 +133,40 @@ func generateBenchmarkCode(funcName string, funcDecl *ast.FuncDecl) string { // Generate basic benchmark scaffold benchName := "Benchmark" + capitalizeFirst(funcName) - code := "func " + benchName + "(b *testing.B) {\n" - code += "\t// Setup: initialize test data\n" + var b strings.Builder + b.Grow(256) // Pre-allocate reasonable capacity + + b.WriteString("func ") + b.WriteString(benchName) + b.WriteString("(b *testing.B) {\n") + b.WriteString("\t// Setup: initialize test data\n") // Add parameter hints based on function signature if funcDecl.Type.Params != nil && len(funcDecl.Type.Params.List) > 0 { - code += "\t// params: " + b.WriteString("\t// params: ") for i, param := range funcDecl.Type.Params.List { if i > 0 { - code += ", " + b.WriteString(", ") } for j, name := range param.Names { if j > 0 { - code += ", " + b.WriteString(", ") } - code += name.Name + b.WriteString(name.Name) } } - code += "\n" + b.WriteString("\n") } - code += "\n\tb.ResetTimer()\n" - code += "\tfor i := 0; i < b.N; i++ {\n" - code += "\t\t" + funcName + "(...) // Add arguments\n" - code += "\t}\n" - code += "}" + b.WriteString("\n\tb.ResetTimer()\n") + b.WriteString("\tfor i := 0; i < b.N; i++ {\n") + b.WriteString("\t\t") + b.WriteString(funcName) + b.WriteString("(...) // Add arguments\n") + b.WriteString("\t}\n") + b.WriteString("}") - return code + return b.String() } func capitalizeFirst(s string) string { @@ -177,12 +185,15 @@ func joinPatterns(patterns []perfPattern) string { return "" } - result := "" + var b strings.Builder + b.Grow(len(patterns) * 20) // Estimate ~20 chars per pattern for i, p := range patterns { if i > 0 { - result += ", " + b.WriteString(", ") } - result += itoa(p.count) + " " + p.name + b.WriteString(itoa(p.count)) + b.WriteString(" ") + b.WriteString(p.name) } - return result + return b.String() } diff --git a/rules/cache.go b/rules/cache.go index 040fee7..170eb2c 100644 --- a/rules/cache.go +++ b/rules/cache.go @@ -19,7 +19,7 @@ func (r *RepeatedRegexpCompileRule) Name() string { return "repeated-regexp- func (r *RepeatedRegexpCompileRule) Category() string { return "cache" } func (r *RepeatedRegexpCompileRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Track if we're inside a function var inFunc bool @@ -75,7 +75,7 @@ func (r *RepeatedTemplateParseRule) Name() string { return "repeated-templat func (r *RepeatedTemplateParseRule) Category() string { return "cache" } func (r *RepeatedTemplateParseRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) var inFunc bool @@ -134,7 +134,7 @@ func (r *RegexpMatchStringRule) Name() string { return "regexp-match-string- func (r *RegexpMatchStringRule) Category() string { return "cache" } func (r *RegexpMatchStringRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt @@ -202,7 +202,7 @@ func (r *JSONSchemaValidationRule) Name() string { return "json-schema-in-lo func (r *JSONSchemaValidationRule) Category() string { return "cache" } func (r *JSONSchemaValidationRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt diff --git a/rules/concurrency.go b/rules/concurrency.go index 89bfde4..83f38cb 100644 --- a/rules/concurrency.go +++ b/rules/concurrency.go @@ -19,7 +19,7 @@ func (r *UnbufferedChannelRule) Name() string { return "unbuffered-channel" func (r *UnbufferedChannelRule) Category() string { return "concurrency" } func (r *UnbufferedChannelRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) @@ -273,7 +273,7 @@ func (r *MutexInLoopRule) Name() string { return "mutex-in-loop" } func (r *MutexInLoopRule) Category() string { return "concurrency" } func (r *MutexInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt @@ -343,7 +343,7 @@ func (r *GoroutineLeakRule) Name() string { return "goroutine-leak" } func (r *GoroutineLeakRule) Category() string { return "concurrency" } func (r *GoroutineLeakRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { goStmt, ok := n.(*ast.GoStmt) diff --git a/rules/context.go b/rules/context.go index 76eda02..fcc5f61 100644 --- a/rules/context.go +++ b/rules/context.go @@ -18,7 +18,7 @@ func (r *ContextBackgroundInHandlerRule) Name() string { return "context-bac func (r *ContextBackgroundInHandlerRule) Category() string { return "context" } func (r *ContextBackgroundInHandlerRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) @@ -82,7 +82,7 @@ func (r *MissingContextTimeoutRule) Name() string { return "missing-context- func (r *MissingContextTimeoutRule) Category() string { return "context" } func (r *MissingContextTimeoutRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Track contexts with timeouts in each function ast.Inspect(file, func(n ast.Node) bool { @@ -173,7 +173,7 @@ func (r *ContextLeakRule) Name() string { return "context-leak" } func (r *ContextLeakRule) Category() string { return "context" } func (r *ContextLeakRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) diff --git a/rules/database.go b/rules/database.go index 60b014a..0ae4a26 100644 --- a/rules/database.go +++ b/rules/database.go @@ -17,7 +17,7 @@ func (r *SQLInLoopRule) Name() string { return "sql-in-loop" } func (r *SQLInLoopRule) Category() string { return "database" } func (r *SQLInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Track prepared statements declared in the current scope preparedStmts := findPreparedStatements(file) @@ -255,7 +255,7 @@ func (r *UnbatchedInsertRule) Name() string { return "unbatched-insert" } func (r *UnbatchedInsertRule) Category() string { return "database" } func (r *UnbatchedInsertRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt diff --git a/rules/dbpool.go b/rules/dbpool.go index 1b29084..d30bd5b 100644 --- a/rules/dbpool.go +++ b/rules/dbpool.go @@ -17,7 +17,7 @@ func (r *MissingConnectionPoolRule) Name() string { return "missing-connecti func (r *MissingConnectionPoolRule) Category() string { return "database" } func (r *MissingConnectionPoolRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Find sql.Open calls sqlOpenVars := make(map[string]token.Position) @@ -111,7 +111,7 @@ func (r *UnlimitedConnectionPoolRule) Name() string { return "unlimited-conn func (r *UnlimitedConnectionPoolRule) Category() string { return "database" } func (r *UnlimitedConnectionPoolRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 2) ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) diff --git a/rules/http.go b/rules/http.go index 92b8455..7aabdc0 100644 --- a/rules/http.go +++ b/rules/http.go @@ -18,7 +18,7 @@ func (r *MissingMaxBytesReaderRule) Name() string { return "missing-max-byte func (r *MissingMaxBytesReaderRule) Category() string { return "io" } func (r *MissingMaxBytesReaderRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) @@ -114,7 +114,7 @@ func (r *MissingBodyCloseRule) Name() string { return "missing-body-close" } func (r *MissingBodyCloseRule) Category() string { return "io" } func (r *MissingBodyCloseRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) @@ -131,7 +131,7 @@ func (r *MissingBodyCloseRule) Check(file *ast.File, fset *token.FileSet, src [] varName string pos token.Position } - var responses []respInfo + responses := make([]respInfo, 0, 4) ast.Inspect(funcDecl.Body, func(inner ast.Node) bool { assign, ok := inner.(*ast.AssignStmt) @@ -233,7 +233,7 @@ func (r *ResponseWriterBufferingRule) Name() string { return "response-write func (r *ResponseWriterBufferingRule) Category() string { return "io" } func (r *ResponseWriterBufferingRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) diff --git a/rules/interface.go b/rules/interface.go index da77be9..6964bd1 100644 --- a/rules/interface.go +++ b/rules/interface.go @@ -18,7 +18,7 @@ func (r *InterfaceBoxingInLoopRule) Name() string { return "interface-boxing func (r *InterfaceBoxingInLoopRule) Category() string { return "allocation" } func (r *InterfaceBoxingInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Find functions that take interface{} or any parameters interfaceFuncs := findInterfaceFunctions(file) @@ -83,7 +83,7 @@ func (r *VariadicInterfaceRule) Name() string { return "variadic-interface" func (r *VariadicInterfaceRule) Category() string { return "allocation" } func (r *VariadicInterfaceRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Find functions with variadic interface{} parameters variadicFuncs := map[string]bool{ @@ -179,7 +179,7 @@ func (r *TypeAssertionInLoopRule) Name() string { return "type-assertion-loo func (r *TypeAssertionInLoopRule) Category() string { return "allocation" } func (r *TypeAssertionInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt diff --git a/rules/io.go b/rules/io.go index 2ccbeb8..74b41ea 100644 --- a/rules/io.go +++ b/rules/io.go @@ -19,7 +19,7 @@ func (r *JSONInLoopRule) Name() string { return "json-in-loop" } func (r *JSONInLoopRule) Category() string { return "io" } func (r *JSONInLoopRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { funcDecl, ok := n.(*ast.FuncDecl) @@ -234,7 +234,7 @@ func (r *HTTPClientCreationRule) Name() string { return "http-client-creatio func (r *HTTPClientCreationRule) Category() string { return "io" } func (r *HTTPClientCreationRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Track package-level http.Client declarations (those are fine) packageLevelClients := findPackageLevelHTTPClients(file) @@ -310,7 +310,7 @@ func (r *HTTPClientCreationRule) Check(file *ast.File, fset *token.FileSet, src } func findPackageLevelHTTPClients(file *ast.File) []string { - var clients []string + clients := make([]string, 0, 4) for _, decl := range file.Decls { genDecl, ok := decl.(*ast.GenDecl) @@ -399,7 +399,7 @@ func (r *ReadAllRule) Name() string { return "read-all" } func (r *ReadAllRule) Category() string { return "io" } func (r *ReadAllRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) diff --git a/rules/memory.go b/rules/memory.go index 59d2ddd..7ff63bd 100644 --- a/rules/memory.go +++ b/rules/memory.go @@ -18,7 +18,7 @@ func (r *PprofInHotPathRule) Name() string { return "pprof-in-hot-path" } func (r *PprofInHotPathRule) Category() string { return "memory" } func (r *PprofInHotPathRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) pprofFuncs := map[string]bool{ "WriteHeapProfile": true, @@ -133,7 +133,7 @@ func (r *LargeStructCopyRule) Name() string { return "large-struct-copy" } func (r *LargeStructCopyRule) Category() string { return "memory" } func (r *LargeStructCopyRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) // Find struct definitions and estimate their size structSizes := estimateStructSizes(file) @@ -242,7 +242,7 @@ func (r *EscapeToHeapRule) Name() string { return "escape-to-heap" } func (r *EscapeToHeapRule) Category() string { return "memory" } func (r *EscapeToHeapRule) Check(file *ast.File, fset *token.FileSet, src []byte) []Issue { - var issues []Issue + issues := make([]Issue, 0, 4) ast.Inspect(file, func(n ast.Node) bool { var loopBody *ast.BlockStmt diff --git a/rules/types.go b/rules/types.go index cb5a755..8dfc663 100644 --- a/rules/types.go +++ b/rules/types.go @@ -90,7 +90,14 @@ func ExtractContext(src []byte, pos token.Position, contextLines int) []string { } func splitLines(src []byte) []string { - var lines []string + // Count newlines to preallocate + lineCount := 1 + for _, b := range src { + if b == '\n' { + lineCount++ + } + } + lines := make([]string, 0, lineCount) start := 0 for i, b := range src { if b == '\n' {