prealloc is a Go static analysis tool to find slice declarations that could potentially be preallocated.

prealloc

prealloc is a Go static analysis tool to find slice declarations that could potentially be preallocated.

Installation

go get -u github.com/alexkohler/prealloc

Usage

Similar to other Go static analysis tools (such as golint, go vet), prealloc can be invoked with one or more filenames, directories, or packages named by its import path. Prealloc also supports the ... wildcard.

prealloc [flags] files/directories/packages

Flags

  • -simple (default true) - Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. Setting this to false may increase false positives.
  • -rangeloops (default true) - Report preallocation suggestions on range loops.
  • -forloops (default false) - Report preallocation suggestions on for loops. This is false by default due to there generally being weirder things happening inside for loops (at least from what I've observed in the Standard Library).
  • -set_exit_status (default false) - Set exit status to 1 if any issues are found.

Purpose

While the Go does attempt to avoid reallocation by growing the capacity in advance, this sometimes isn't enough for longer slices. If the size of a slice is known at the time of its creation, it should be specified.

Consider the following benchmark: (this can be found in prealloc_test.go in this repo)

import "testing"

func BenchmarkNoPreallocate(b *testing.B) {
	existing := make([]int64, 10, 10)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// Don't preallocate our initial slice
		var init []int64
		for _, element := range existing {
			init = append(init, element)
		}
	}
}

func BenchmarkPreallocate(b *testing.B) {
	existing := make([]int64, 10, 10)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// Preallocate our initial slice
		init := make([]int64, 0, len(existing))
		for _, element := range existing {
			init = append(init, element)
		}
	}
}
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkNoPreallocate-4   	 3000000	       510 ns/op	     248 B/op	       5 allocs/op
BenchmarkPreallocate-4     	20000000	       111 ns/op	      80 B/op	       1 allocs/op

As you can see, not preallocating can cause a performance hit, primarily due to Go having to reallocate the underlying array. The pattern benchmarked above is common in Go: declare a slice, then write some sort of range or for loop that appends or indexes into it. The purpose of this tool is to flag slice/loop declarations like the one in BenchmarkNoPreallocate.

Example

Some examples from the Go 1.9.2 source:

$ prealloc go/src/....
archive/tar/reader_test.go:854 Consider preallocating ss
archive/zip/zip_test.go:201 Consider preallocating all
cmd/api/goapi.go:301 Consider preallocating missing
cmd/api/goapi.go:476 Consider preallocating files
cmd/asm/internal/asm/endtoend_test.go:345 Consider preallocating extra
cmd/cgo/main.go:60 Consider preallocating ks
cmd/cgo/ast.go:149 Consider preallocating pieces
cmd/compile/internal/ssa/flagalloc.go:64 Consider preallocating oldSched
cmd/compile/internal/ssa/regalloc.go:719 Consider preallocating phis
cmd/compile/internal/ssa/regalloc.go:718 Consider preallocating oldSched
cmd/compile/internal/ssa/regalloc.go:1674 Consider preallocating oldSched
cmd/compile/internal/ssa/gen/rulegen.go:145 Consider preallocating ops
cmd/compile/internal/ssa/gen/rulegen.go:145 Consider preallocating ops
cmd/dist/build.go:893 Consider preallocating all
cmd/dist/build.go:1246 Consider preallocating plats
cmd/dist/build.go:1264 Consider preallocating results
cmd/dist/buildgo.go:59 Consider preallocating list
cmd/doc/pkg.go:363 Consider preallocating names
cmd/fix/typecheck.go:219 Consider preallocating b
cmd/go/internal/base/path.go:34 Consider preallocating out
cmd/go/internal/get/get.go:175 Consider preallocating out
cmd/go/internal/load/pkg.go:1894 Consider preallocating dirent
cmd/go/internal/work/build.go:2402 Consider preallocating absOfiles
cmd/go/internal/work/build.go:2731 Consider preallocating absOfiles
cmd/internal/objfile/pe.go:48 Consider preallocating syms
cmd/internal/objfile/pe.go:38 Consider preallocating addrs
cmd/internal/objfile/goobj.go:43 Consider preallocating syms
cmd/internal/objfile/elf.go:35 Consider preallocating syms
cmd/link/internal/ld/lib.go:1070 Consider preallocating argv
cmd/vet/all/main.go:91 Consider preallocating pp
database/sql/sql.go:66 Consider preallocating list
debug/macho/file.go:506 Consider preallocating all
internal/trace/order.go:55 Consider preallocating batches
mime/quotedprintable/reader_test.go:191 Consider preallocating outcomes
net/dnsclient_unix_test.go:954 Consider preallocating confLines
net/interface_solaris.go:85 Consider preallocating ifat
net/interface_linux_test.go:91 Consider preallocating ifmat4
net/interface_linux_test.go:100 Consider preallocating ifmat6
net/internal/socktest/switch.go:34 Consider preallocating st
os/os_windows_test.go:766 Consider preallocating args
runtime/pprof/internal/profile/filter.go:77 Consider preallocating lines
runtime/pprof/internal/profile/profile.go:554 Consider preallocating names
text/template/parse/node.go:189 Consider preallocating decl
// cmd/api/goapi.go:301
var missing []string
for feature := range optionalSet {
	missing = append(missing, feature)
}

// cmd/fix/typecheck.go:219
var b []ast.Expr
for _, x := range a {
	b = append(b, x)
}

// net/internal/socktest/switch.go:34
var st []Stat
sw.smu.RLock()
for _, s := range sw.stats {
	ns := *s
	st = append(st, ns)
}
sw.smu.RUnlock()

// cmd/api/goapi.go:301
var missing []string
for feature := range optionalSet {
	missing = append(missing, feature)
}

Even if the size the slice is being preallocated to is small, there's still a performance gain to be had in explicitly specifying the capacity rather than leaving it up to append to discover that it needs to preallocate. Of course, preallocation doesn't need to be done everywhere. This tool's job is just to help suggest places where one should consider preallocating.

How do I fix prealloc's suggestions?

During the declaration of your slice, rather than using the zero value of the slice with var, initialize it with Go's built-in make function, passing the appropriate type and length. This length will generally be whatever you are ranging over. Fixing the examples from above would look like so:

// cmd/api/goapi.go:301
missing := make([]string, 0, len(optionalSet))
for feature := range optionalSet {
	missing = append(missing, feature)
}

// cmd/fix/typecheck.go:219
b := make([]ast.Expr, 0, len(a))
for _, x := range a {
	b = append(b, x)
}

// net/internal/socktest/switch.go:34
st := make([]Stat, 0, len(sw.stats))
sw.smu.RLock()
for _, s := range sw.stats {
	ns := *s
	st = append(st, ns)
}
sw.smu.RUnlock()

// cmd/api/goapi.go:301
missing := make ([]string, 0, len(optionalSet))
for feature := range optionalSet {
	missing = append(missing, feature)
}

Note: If performance is absolutely critical, it may be more efficient to use copy instead of append for larger slices. For reference, see the following benchmark:

func BenchmarkSize200PreallocateCopy(b *testing.B) {
	existing := make([]int64, 200, 200)
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// Preallocate our initial slice
		init := make([]int64, len(existing))
		copy(init, existing)
	}
}
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkSize200NoPreallocate-4     	  500000	      3080 ns/op	    4088 B/op	       9 allocs/op
BenchmarkSize200Preallocate-4       	 1000000	      1163 ns/op	    1792 B/op	       1 allocs/op
BenchmarkSize200PreallocateCopy-4   	 2000000	       807 ns/op	    1792 B/op	       1 allocs/op

TODO

  • Configuration on whether or not to run on test files
  • Support for embedded ifs (currently, prealloc will only find breaks/returns/continues/gotos if they are in a single if block, I'd like to expand this to supporting multiple if blocks in the future).
  • Globbing support (e.g. prealloc *.go)

Contributing

Pull requests welcome!

Other static analysis tools

If you've enjoyed prealloc, take a look at my other static analysis tools!

Comments
  • Use copy() instead of append() for large arrays

    Use copy() instead of append() for large arrays

    If the array is quite large, it's more efficient to use copy() instead of append()

    so for example, if len(optionalSet) > 100 and if optionalSet is a slice

    var missing []string
    for _, feature := range optionalSet {
    	missing = append(missing, feature)
    }
    

    should become

    missing := make([]string, len(optionalSet)
    copy(missing, optionalSet)
    

    instead of

    missing := make([]string, 0, len(optionalSet))
    for _, feature := range optionalSet {
    	missing = append(missing, feature)
    }
    

    Benchmark:

    For example, when len(existing) = 500, with this new test:

    func BenchmarkPreallocateCopy(b *testing.B) {
    	existing := make([]int64, 200, 200)
    	b.ResetTimer()
    	for i := 0; i < b.N; i++ {
    		// Preallocate our initial slice
    		init := make([]int64, len(existing))
    		copy(init, existing)
    	}
    }
    

    the results clearly shows that copy is faster:

    BenchmarkNoPreallocate-2     	  300000	      4039 ns/op
    BenchmarkPreallocate-2       	 1000000	      1664 ns/op
    BenchmarkPreallocateCopy-2   	 1000000	      1303 ns/op
    

    Maybe it should be mention somewhere in the README ?

  • Extract importable package from CLI

    Extract importable package from CLI

    The motivation here is to remove the need for forking prealloc for use in linter aggregation tools such as golangci-lint. I attempted to make as few changes as possible to the code itself to keep the existing structure, while adding the interface necessary for golangci-lint to use it.

    Those changes are based largely on the commit here: https://github.com/golangci/prealloc/commit/215b22d4de21190b80ce05e7d8466677c1aa3223

    If there are other approaches that might suit this repo better, I'd be happy to make the change. I just figured a proposal in the form of a quick refactor would be the easiest place to start the discussion.

    Related: https://github.com/golangci/golangci-lint/issues/991#issuecomment-617468077

  • fix for issue 2

    fix for issue 2

    This is a fix for the issue

    I have done manual testing to test the code changes and it works as expected.

    type MySlice3 []string
    func main() {
    	type MySlice1 []string
    	type MySlice2 = []string
    
    	var var1 MySlice1
    	var var2 []string
    	var3 := make([]string, 10, 10)
    	var var4 MySlice2
        var var5 MySlice3
    	for feature := range optionalSet {
    		var1 = append(var1, feature)
    		var2 = append(var2, feature)
    		var3 = append(var3, feature)
    		var4 = append(var4, feature)
    		var5 = append(var5, feature)
    	}
    }
    

    ../../bin/test.go:9 Consider preallocating var1 ../../bin/test.go:10 Consider preallocating var2 ../../bin/test.go:12 Consider preallocating var4 ../../bin/test.go:13 Consider preallocating var5

  • prealloc confused by ranging over a channel

    prealloc confused by ranging over a channel

    var jobs []job
    for job := range jobc {
        jobs = append(jobs, job)
    }
    

    In the code above, jobc is an unbuffered channel. I don't know the number of jobs that will be pulled from it. So I can't preallocate it.

  • Duplicate output lines for repeat append calls in a loop

    Duplicate output lines for repeat append calls in a loop

    Given this sample program:

    package main
    
    import "fmt"
    
    func main() {
    	var buf []byte
    
    	strings := []string{"foo", "bar", "baz"}
    	for i, s := range strings {
    		buf = append(buf, s...)
    		buf = append(buf, '\n')
    	}
    
    	fmt.Println(buf)
    }
    

    prealloc will output:

    main.go:6 Consider preallocating buf
    main.go:6 Consider preallocating buf
    

    It appears to be outputting a line for each append in the loop, i.e. if I add one more buf = append(buf, x) there will be one more "main.go:6 Consider preallocating buf" line output.

    There should be at most one warning regardless of multiple append calls.

  • is preallocating array with 0 length still get benefit on performance ?

    is preallocating array with 0 length still get benefit on performance ?

    Hi, i have case when i need to define array that supposed to received result from database. Previously i have done this:

    var models []Model
    result := db.Where("namespace = ?", namespace).Find(&clients)
    

    where the result length is arbitrary. When i run prealloc, it suggests me to preallocating the array so i revised the code like this

    models := make([]Model, 0) // i put 0 since i don't know the length yet
    result := db.Where("namespace = ?", namespace).Find(&clients)
    

    So, am i doing this correctly ? is preallocating the array with zero length because the length can be arbitrary still gaining the performance benefit rather than defining it with var?

    Thanks!

  • Variadic parameters might not need preallocation

    Variadic parameters might not need preallocation

    a := []int{}
    b := []int{1}
    c := []int{1,2}
    s := []int{}
    for _, x := range [][]int{a, b, c} {
        s = append(s, x...)
    } 
    // s cannot be preallocated
    

    But prealloc warns about this code?

    Thanks.

  • Support for custom slice types or aliases

    Support for custom slice types or aliases

    This is not detected at the moment:

    type MySlice []string
    
    var missing MySlice
    for feature := range optionalSet {
    	missing = append(missing, feature)
    }
    

    And this as well:

    type MySlice = []string
    
    var missing MySlice
    for feature := range optionalSet {
    	missing = append(missing, feature)
    }
    
  • fix false positive for ellipsis

    fix false positive for ellipsis

    Long time no see. 😁

    See https://github.com/golangci/golangci-lint/issues/991. Trying to pre-allocate when ... is in play in a for loop gets messy. Also revised the logic to better handle multiple assignments, and added a test.

  • Set exit status when suggestions are found

    Set exit status when suggestions are found

    Setting the exit status of prealloc to something other than 0 would make it possible to use in CI builds as a means of verification.

    If compatibility is a concern, a flag could be used to enable this behavior. In that case, the flag would ideally be -set_exit_status to be consistent with golint.

  • Map preallocation

    Map preallocation

    Preallocation of maps can be useful, as shown by PR #23, would be nice to have prealloc suggest that too.

    Just wondering how smart it can be made as things are not as straightforward as with slices because there's key uniqueness at play. Cases where the optimal preallocated capacity can be easily determined beforehand are more rare with maps, and in that sense suggesting it could have a worse signal to noise ratio. Maybe it should be made optional if it can't be made "smart enough" :thinking:

  • Add map preallocation benchmarks

    Add map preallocation benchmarks

    Results from system in front of me:

    BenchmarkMap/Size10NoPreallocate-4  	 1954923	       603.7 ns/op	     484 B/op	       3 allocs/op
    BenchmarkMap/Size10Preallocate-4    	 2951250	       393.8 ns/op	     340 B/op	       2 allocs/op
    BenchmarkMap/Size200NoPreallocate-4 	   71530	     15337 ns/op	   11270 B/op	      25 allocs/op
    BenchmarkMap/Size200Preallocate-4   	  126673	      8216 ns/op	    5719 B/op	       8 allocs/op
    
  • Faulty prealloc

    Faulty prealloc

    I currently work in a team and we had a code issue a few times that made it beyond code review. It is pre-allocating a slice with size instead of cap, then use append on it. Example

    ar := make([]int, 10)
    for i:=0; i<10; i++ {
      ar = append(ar, i)
      }
    

    The correct pre-alloc would be ar := make([]int, 0, 10). Because of the missing 0,, the faulty code produces an array of size 20.

    Would it be possible to add a check for this into your linter? I could not find any linter that has this check.

  • line of sight loops trick prealloc

    line of sight loops trick prealloc

    When ranging over a list that contains conditional break/continue directives and a slice = append(slice, element) at the end, prealloc suggests preallocation.

    No lint triggered

    var a []int
    for i := range []struct{}{{}, {}, {}} {
            if i < 1 {
                    a = append(a, i)
            } 
    }
    

    Lint triggered

    var a []int
    for i := range []struct{}{{}, {}, {}} {
            if i < 1 {
                  continue
            } 
            a = append(a, i)
    }
    
  • Prealloc confused by variable redeclaration

    Prealloc confused by variable redeclaration

    In the following function, prealloc is confused by buf being redeclared:

    func copySort(w io.Writer, r io.Reader, sep rune, keys []int) error {
    	// Copy the header line.
    	var buf [1]byte // THATS LINE 275 WHERE THE ERROR MESSAGES POINT TO
    	for {
    		n, err := r.Read(buf[:])
    		if n != 0 {
    			if _, err := w.Write(buf[:]); err != nil {
    				return err
    			}
    		}
    		if err != nil {
    			if err == io.EOF {
    				return nil
    			}
    			return err
    		}
    		if buf[0] == '\n' {
    			break
    		}
    	}
    	...
    	for i, k := range keys {
    		buf := make([]byte, 0, 16)
    		buf = append(buf, "-k"...)
    		n := int64(1 + k)
    		buf = strconv.AppendInt(buf, n, 10)
    		buf = append(buf, ',')
    		buf = strconv.AppendInt(buf, n, 10)
    		args[2+i] = alloc.DirectString(buf)
    	}
    	...
    }
    

    preallocreports these 2 error messages:

    ...:275 Consider preallocating buf
    ...:275 Consider preallocating buf
    

    If I rename the first buf declaration with any other variable name (e.g., cell) the error messages disappear.

    It seems a variable redeclaration is not always managed correctly by prealloc.

💪 Helper Utils For The Go: string, array/slice, map, format, cli, env, filesystem, test and more.
💪 Helper Utils For The Go: string, array/slice, map, format, cli, env, filesystem, test and more.

?? Helper Utils For The Go: string, array/slice, map, format, cli, env, filesystem, test and more. Go 的一些工具函数,格式化,特殊处理,常用信息获取等等

Jan 6, 2023
make slice items unique in go

make slice items unique in go

Jan 20, 2022
Wrap byte read options with uniform interface for io.Reader and byte slice

nibbler Nibble chunks from Reader streams and slice in a common way Overview This is a golang module that provides an interface for treating a Reader

Dec 23, 2021
Slice conversion between primitive types

sliceconv Sliceconv implements conversions to and from string representations of primitive types on entire slices. The package supports types int, flo

Sep 27, 2022
Go 1.18 generics based slice and sorts.

genfuncs import "github.com/nwillc/genfuncs" Package genfuncs implements various functions utilizing Go's Generics to help avoid writing boilerplate c

Jan 2, 2023
Slice - provides generic Map, Reduce and Filter functions for Go.

slice slice is a simple Go package to provide generic versions of Map, Reduce and Filter on slices. I mainly wrote it as an exercise to get more famil

Jan 1, 2023
🍕 Enjoy a slice! A utility library for dealing with slices and maps that focuses on type safety and performance.

?? github.com/elliotchance/pie Enjoy a slice! pie is a library of utility functions for common operations on slices and maps. Quick Start FAQ What are

Dec 30, 2022
A tool to find redirection chains in multiple URLs
A tool to find redirection chains in multiple URLs

UnChain A tool to find redirection chains in multiple URLs Introduction UnChain automates process of finding and following `30X` redirects by extracti

Dec 12, 2022
Otx - otx tool can scrap to find sensitive information and vulnerable endpoint urls.
Otx - otx tool can scrap to find sensitive information and vulnerable endpoint urls.

otx Description This tool is base on AlienVault Open Threat Exchange (OTX)? and this tool can help you to extract all the urls endpoints which can be

Sep 24, 2022
Project developed for the course Software Systems Analysis and Design (SSAD) at IU in F21 semester.

Go knowledge yield summary Project description Project developed for the course Software Systems Analysis and Design (SSAD) at IU in F21 semester. Eva

Sep 17, 2022
Continuous profiling for analysis of CPU, memory usage over time, and down to the line number. Saving infrastructure cost, improving performance, and increasing reliability.
Continuous profiling for analysis of CPU, memory usage over time, and down to the line number. Saving infrastructure cost, improving performance, and increasing reliability.

Continuous profiling for analysis of CPU, memory usage over time, and down to the line number. Saving infrastructure cost, improving performance, and increasing reliability.

Jan 2, 2023
A process that receives probe information and stores it in a database for reporting and analysis

probed is a process that receives probe information and stores it in a database for reporting and analysis.

Nov 2, 2022
Sentiment Analysis Pipeline + API written in Golang (currently processing Twitter tweets).

Go Sentiment Analysis Components Config: config module based in JSON (enter twitter credentials for use) Controllers: handle the API db call/logic for

Mar 22, 2022
Sentiment Analysis Pipeline + API written in Golang (currently processing Twitter tweets).

Go Sentiment Analysis Components Config: config module based in JSON (enter twitter credentials for use) Controllers: handle the API db call/logic for

Mar 22, 2022
Find the issues linked with a Pull Request

linked-issues docker action This action prints "Hello World" or "Hello" + the name of a person to greet to the log. Inputs who-to-greet Required The n

Jun 30, 2022
Goterators - A util library that Supports aggregate & transforms functions Go. Such as filter, map, reduce, find, exist
Goterators - A util library that Supports aggregate & transforms functions Go. Such as filter, map, reduce, find, exist

Goterators Goterators is util library that Supports aggregate & transforms functions Go, including: for-each find exist reduce filter map API and func

Dec 19, 2022
A protoc plugin that generates fieldmask paths as static type properties for proto messages

protoc-gen-fieldmask A protoc plugin that generates fieldmask paths as static ty

Nov 3, 2022
efaceconv - Code generation tool for high performance conversion from interface{} to immutable type without allocations.

efaceconv High performance conversion from interface{} to immutable types without additional allocations This is tool for go generate and common lib (

May 14, 2022
GoWrap is a command line tool for generating decorators for Go interfaces

GoWrap GoWrap is a command line tool that generates decorators for Go interface types using simple templates. With GoWrap you can easily add metrics,

Dec 30, 2022