如何查看 Go 项目的大小?
How do I check the size of a Go project?
有没有简单的方法可以检查 Golang 项目的大小?它不是可执行文件,它是我在自己的项目中导入的包。
您可以通过查看 $GOPATH/pkg
目录来查看库二进制文件有多大(如果未导出 $GOPATH
go
默认为 $HOME/go
)。
所以要检查一些 gorilla
http pkgs 的大小。首先安装它们:
$ go get -u github.com/gorilla/mux
$ go get -u github.com/gorilla/securecookie
$ go get -u github.com/gorilla/sessions
我的 64 位 MacOS (darwin_amd64
) 上的 KB 二进制大小:
$ cd $GOPATH/pkg/darwin_amd64/github.com/gorilla/
$ du -k *
284 mux.a
128 securecookie.a
128 sessions.a
编辑:
库(包)大小是一回事,但 space 在 link 阶段之后在您的可执行文件中占用的大小可能会有很大差异。这是因为包有它们自己的依赖项,并且随之而来的是额外的 baggage,但是这些包可能会被您导入的其他 packages 共享。
一个例子最能说明这一点:
empty.go:
package main
func main() {}
http.go:
package main
import "net/http"
var _ = http.Serve
func main() {}
mux.go:
package main
import "github.com/gorilla/mux"
var _ = mux.NewRouter
func main() {}
所有 3 个程序在功能上是相同的 - 执行零用户代码 - 但它们的依赖关系不同。 KB
:
中生成的二进制大小
$ du -k *
1028 empty
5812 http
5832 mux
这告诉我们什么?核心 go pkg net/http
显着增加了我们的可执行文件的大小。 mux
pkg 本身并不大,但它对 net/http
pkg 具有导入依赖性 - 因此它的文件大小也很大。然而 mux
和 http
之间的差异仅为 20KB
,而 mux.a 库的列出文件大小为 284KB
。所以我们不能简单地添加库 pkg 大小来确定它们的真实占用空间。
结论:
go linker 会在构建过程中从各个库中删除很多包袱,但是为了真正了解导入某些包的额外 weight 有多少,还必须查看 pkg 的所有 子依赖项 。
您可以使用 go mod vendor
下载所有 import
ed 模块,然后计算所有 .go
不是测试文件的文件的行数:
package main
import (
"bytes"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
)
func count(mod string) int {
imp := fmt.Sprintf("package main\nimport _ %q", mod)
os.WriteFile("size.go", []byte(imp), os.ModePerm)
exec.Command("go", "mod", "init", "size").Run()
exec.Command("go", "mod", "vendor").Run()
var count int
filepath.WalkDir("vendor", func(s string, d fs.DirEntry, err error) error {
if strings.HasSuffix(s, ".go") && !strings.HasSuffix(s, "_test.go") {
data, err := os.ReadFile(s)
if err != nil {
return err
}
count += bytes.Count(data, []byte{'\n'})
}
return nil
})
return count
}
func main() {
println(count("github.com/klauspost/compress/zstd"))
}
这是另一个利用 https://pkg.go.dev/golang.org/x/tools/go/packages
的解决方案
我拍了example provided by the author, and slightly updated it with the demonstration binary available here.
package main
import (
"flag"
"fmt"
"log"
"os"
"sort"
"golang.org/x/tools/go/packages"
)
func main() {
flag.Parse()
// Many tools pass their command-line arguments (after any flags)
// uninterpreted to packages.Load so that it can interpret them
// according to the conventions of the underlying build system.
cfg := &packages.Config{Mode: packages.NeedFiles |
packages.NeedSyntax |
packages.NeedImports,
}
pkgs, err := packages.Load(cfg, flag.Args()...)
if err != nil {
fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if packages.PrintErrors(pkgs) > 0 {
os.Exit(1)
}
// Print the names of the source files
// for each package listed on the command line.
var size int64
for _, pkg := range pkgs {
for _, file := range pkg.GoFiles {
s, err := os.Stat(file)
if err != nil {
log.Println(err)
continue
}
size += s.Size()
}
}
fmt.Printf("size of %v is %v b\n", pkgs[0].ID, size)
size = 0
for _, pkg := range allPkgs(pkgs) {
for _, file := range pkg.GoFiles {
s, err := os.Stat(file)
if err != nil {
log.Println(err)
continue
}
size += s.Size()
}
}
fmt.Printf("size of %v and deps is %v b\n", pkgs[0].ID, size)
}
func allPkgs(lpkgs []*packages.Package) []*packages.Package {
var all []*packages.Package // postorder
seen := make(map[*packages.Package]bool)
var visit func(*packages.Package)
visit = func(lpkg *packages.Package) {
if !seen[lpkg] {
seen[lpkg] = true
// visit imports
var importPaths []string
for path := range lpkg.Imports {
importPaths = append(importPaths, path)
}
sort.Strings(importPaths) // for determinism
for _, path := range importPaths {
visit(lpkg.Imports[path])
}
all = append(all, lpkg)
}
}
for _, lpkg := range lpkgs {
visit(lpkg)
}
return all
}
有没有简单的方法可以检查 Golang 项目的大小?它不是可执行文件,它是我在自己的项目中导入的包。
您可以通过查看 $GOPATH/pkg
目录来查看库二进制文件有多大(如果未导出 $GOPATH
go
默认为 $HOME/go
)。
所以要检查一些 gorilla
http pkgs 的大小。首先安装它们:
$ go get -u github.com/gorilla/mux
$ go get -u github.com/gorilla/securecookie
$ go get -u github.com/gorilla/sessions
我的 64 位 MacOS (darwin_amd64
) 上的 KB 二进制大小:
$ cd $GOPATH/pkg/darwin_amd64/github.com/gorilla/
$ du -k *
284 mux.a
128 securecookie.a
128 sessions.a
编辑:
库(包)大小是一回事,但 space 在 link 阶段之后在您的可执行文件中占用的大小可能会有很大差异。这是因为包有它们自己的依赖项,并且随之而来的是额外的 baggage,但是这些包可能会被您导入的其他 packages 共享。
一个例子最能说明这一点:
empty.go:
package main
func main() {}
http.go:
package main
import "net/http"
var _ = http.Serve
func main() {}
mux.go:
package main
import "github.com/gorilla/mux"
var _ = mux.NewRouter
func main() {}
所有 3 个程序在功能上是相同的 - 执行零用户代码 - 但它们的依赖关系不同。 KB
:
$ du -k *
1028 empty
5812 http
5832 mux
这告诉我们什么?核心 go pkg net/http
显着增加了我们的可执行文件的大小。 mux
pkg 本身并不大,但它对 net/http
pkg 具有导入依赖性 - 因此它的文件大小也很大。然而 mux
和 http
之间的差异仅为 20KB
,而 mux.a 库的列出文件大小为 284KB
。所以我们不能简单地添加库 pkg 大小来确定它们的真实占用空间。
结论: go linker 会在构建过程中从各个库中删除很多包袱,但是为了真正了解导入某些包的额外 weight 有多少,还必须查看 pkg 的所有 子依赖项 。
您可以使用 go mod vendor
下载所有 import
ed 模块,然后计算所有 .go
不是测试文件的文件的行数:
package main
import (
"bytes"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
)
func count(mod string) int {
imp := fmt.Sprintf("package main\nimport _ %q", mod)
os.WriteFile("size.go", []byte(imp), os.ModePerm)
exec.Command("go", "mod", "init", "size").Run()
exec.Command("go", "mod", "vendor").Run()
var count int
filepath.WalkDir("vendor", func(s string, d fs.DirEntry, err error) error {
if strings.HasSuffix(s, ".go") && !strings.HasSuffix(s, "_test.go") {
data, err := os.ReadFile(s)
if err != nil {
return err
}
count += bytes.Count(data, []byte{'\n'})
}
return nil
})
return count
}
func main() {
println(count("github.com/klauspost/compress/zstd"))
}
这是另一个利用 https://pkg.go.dev/golang.org/x/tools/go/packages
的解决方案我拍了example provided by the author, and slightly updated it with the demonstration binary available here.
package main
import (
"flag"
"fmt"
"log"
"os"
"sort"
"golang.org/x/tools/go/packages"
)
func main() {
flag.Parse()
// Many tools pass their command-line arguments (after any flags)
// uninterpreted to packages.Load so that it can interpret them
// according to the conventions of the underlying build system.
cfg := &packages.Config{Mode: packages.NeedFiles |
packages.NeedSyntax |
packages.NeedImports,
}
pkgs, err := packages.Load(cfg, flag.Args()...)
if err != nil {
fmt.Fprintf(os.Stderr, "load: %v\n", err)
os.Exit(1)
}
if packages.PrintErrors(pkgs) > 0 {
os.Exit(1)
}
// Print the names of the source files
// for each package listed on the command line.
var size int64
for _, pkg := range pkgs {
for _, file := range pkg.GoFiles {
s, err := os.Stat(file)
if err != nil {
log.Println(err)
continue
}
size += s.Size()
}
}
fmt.Printf("size of %v is %v b\n", pkgs[0].ID, size)
size = 0
for _, pkg := range allPkgs(pkgs) {
for _, file := range pkg.GoFiles {
s, err := os.Stat(file)
if err != nil {
log.Println(err)
continue
}
size += s.Size()
}
}
fmt.Printf("size of %v and deps is %v b\n", pkgs[0].ID, size)
}
func allPkgs(lpkgs []*packages.Package) []*packages.Package {
var all []*packages.Package // postorder
seen := make(map[*packages.Package]bool)
var visit func(*packages.Package)
visit = func(lpkg *packages.Package) {
if !seen[lpkg] {
seen[lpkg] = true
// visit imports
var importPaths []string
for path := range lpkg.Imports {
importPaths = append(importPaths, path)
}
sort.Strings(importPaths) // for determinism
for _, path := range importPaths {
visit(lpkg.Imports[path])
}
all = append(all, lpkg)
}
}
for _, lpkg := range lpkgs {
visit(lpkg)
}
return all
}