标签 编译 下的文章

Go 1.5中值得关注的几个变化

GopherCon2015开幕之 际,Google Go Team终于放出了Go 1.5Beta1版本的安装包。在go 1.5Beta1的发布说明中,Go Team也诚恳地承认Go 1.5将打破之前6个月一个版本的发布周期,这是因为Go 1.5变动太大,需要更多时间来准备这次发布(fix bug, Write doc)。关于Go 1.5的变化,之前Go Team staff在各种golang技术会议的slide  中暴露不少,包括:

- 编译器和运行时由C改为Go(及少量汇编语言)重写,实现了Go的self Bootstrap(自举)
- Garbage Collector优化,大幅降低GC延迟(Stop The World),实现Gc在单独的goroutine中与其他user goroutine并行运行。
- 标准库变更以及一些go tools的引入。

每项变动都会让gopher激动不已。但之前也只是激动,这次beta1出来后,我们可以实际体会一下这些变动带来的“快感”了。Go 1.5beta1的发布文档目前还不全,有些地方还有“待补充”字样,可能与最终go 1.5发布时的版本有一定差异,不过大体内容应该是固定不变的了。这篇文章就想和大家一起浅显地体验一下go 1.5都给gophers们带来了哪些变化吧。

一、语言

【map literal】

go 1.5依旧兼容Go 1 language specification,但修正了之前的一个“小疏忽”。

Go 1.4及之前版本中,我们只能这么来写代码:

//testmapliteral.go
package main

import (
    "fmt"
)

type Point struct {
    x int
    y int
}

func main() {
    var sl = []Point{{3, 4}, {5, 6}}
    var m = map[Point]string{
        Point{3,4}:"foo1",
        Point{5,6}:"foo2",
    }
    fmt.Println(sl)
    fmt.Println(m)
}

可以看到,对于Point这个struct来说,在初始化一个slice时,slice value literal中无需显式的带上元素类型Point,即

var sl = []Point{{3, 4}, {5, 6}}

而不是

var sl = []Point{Point{3, 4}, Point{5, 6}}

但当Point作为map类型的key类型时,初始化map时则要显式带上元素类型Point。Go team承认这是当初的一个疏忽,在本次Go 1.5中将该问题fix掉了。也就是说,下面的代码在Go 1.5中可以顺利编译通过:

func main() {
    var sl = []Point{{3, 4}, {5, 6}}
    var m = map[Point]string{
        {3,4}:"foo1",
        {5,6}:"foo2",
    }
    fmt.Println(sl)
    fmt.Println(m)
}

【GOMAXPROCS】

就像这次GopherCon2015上现任Google Go project Tech Lead的Russ Cox的开幕Keynote中所说的那样:Go目标定位于高度并发的云环境。Go 1.5中将标识并发系统线程个数的GOMAXPROCS的初始值由1改为了运行环境的CPU核数。

// testgomaxprocs.go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Println(runtime.GOMAXPROCS(-1))
    fmt.Println(runtime.NumGoroutine())
}

这个代码在Go 1.4下(Mac OS X 4核)运行结果是:

$go run testgomaxprocs.go
1
4

而在go 1.5beta1下,结果为:

$go run testgomaxprocs.go
4
4

二、编译

【简化跨平台编译】

1.5之前的版本要想实现跨平台编译,需要到$GOROOT/src下重新执行一遍make.bash,执行前设置好目标环境的环境变量(GOOS和 GOARCH),Go 1.5大大简化这个过程,使得跨平台编译几乎与普通编译一样简单。下面是一个简单的例子:

//testcrosscompile.go
package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Println(runtime.GOOS)
}

在我的Mac上,本地编译执行:
$go build -o testcrosscompile_darwin testcrosscompile.go
$testcrosscompile_darwin
darwin

跨平台编译linux amd64上的目标程序:

$GOOS=linux GOARCH=amd64 go build -o testcrosscompile_linux testcrosscompile.go

上传testcrosscompile_linux到ubuntu 14.04上执行:
$testcrosscompile_linux
linux

虽然从用户角度跨平台编译命令很简单,但事实是go替你做了很多事情,我们可以通过build -x -v选项来输出编译的详细过程,你会发现go会先进入到$GOROOT/src重新编译runtime.a以及一些平台相关的包。编译输出的信息 太多,这里就不贴出来了。但在1.5中这个过程非常快(10秒以内),与1.4之前版本的跨平台编译相比,完全不是一个级别,这也许就是编译器用Go重写完的好处之一吧。

除了直接使用go build,我们还可以使用go tool compile和go tool link来编译程序,实际上go build也是调用这两个工具完成编译过程的。

$go tool compile testcrosscompile.go
testcrosscompile.o
$go tool link testcrosscompile.o
a.out
$a.out
darwin

go 1.5移除了以前的6a,6l之类的编译连接工具,将这些工具整合到go tool中。并且go tool compile的输出默认改为.o文件,链接器输出默认改为了a.out。

【动态共享库】

个人不是很赞同Go语言增加对动态共享库的支持,.so和.dll这类十多年前的技术在如今内存、磁盘空间都“非常大”的前提下,似乎已经失去了以往的魅 力。并且动态共享库所带来的弊端:"DLL hell"会让程序后续的运维痛苦不已。Docker等轻量级容器的兴起,面向不变性的架构(immutable architecture)受到更多的关注。人们更多地会在container这一层进行操作,一个纯static link的应用在部署和维护方面将会有天然优势,.so只会增加复杂性。如果单纯从与c等其他语言互操作的角度,似乎用途也不会很广泛(但游戏或ui领域 可能会用到)。不过go 1.5还是增加了对动态链接库的支持,不过从go tool compile和link的doc说明来看,目前似乎还处于实验阶段。

既然go 1.5已经支持了shared library,我们就来实验一下。我们先规划一下测试repository的目录结构:

$GOPATH
    /src
        /testsharedlib
            /shlib
                – lib.go
        /app
            /main.go

lib.go中的代码很简单:

//lib.go
package shlib

import "fmt"

// export Method1
func Method1() {
    fmt.Println("shlib -Method1")
}

对于希望导出的方法,采用export标记。

我们来将这个lib.go编译成shared lib,注意目前似乎只有linux平台支持编译go shared library:

$ go build -buildmode=shared testsharedlib/shlib
# /tmp/go-build709704006/libtestsharedlib-shlib.so
warning: unable to find runtime/cgo.a

编译ok,那个warning是何含义不是很理解。

要想.so被其他go程序使用,需要将.so安装到相关目录下。我们install一下试试:

$ go install -buildmode=shared testsharedlib/shlib
multiple roots /home1/tonybai/test/go/go15/pkg/linux_amd64_dynlink & /home1/tonybai/.bin/go15beta1/go/pkg/linux_amd64_dynlink

go工具居然纠结了,不知道选择放在哪里,一个是$GOPATH/pkg/linux_amd64_dynlink,另外一个则是$GOROOT/pkg/linux_amd64_dynlink,我不清楚这是不是一个bug。

在Google了之后,我尝试了网上的一个解决方法,先编译出runtime的动态共享库:

$go install -buildmode=shared runtime sync/atomic

编译安装后,你就会在$GOROOT/pkg下面看到多出来一个目录:linux_amd64_dynlink。这个目录下的结构如下:

$ ls -R
.:
libruntime,sync-atomic.so  runtime  runtime.a  runtime.shlibname  sync

./runtime:
cgo.a  cgo.shlibname

./sync:
atomic.a  atomic.shlibname

这里看到了之前warning提到的runtime/cgo.a,我们再来重新执行一下build,看看能不能消除warning:

$ go build -buildmode=shared testsharedlib/shlib
# /tmp/go-build086398801/libtestsharedlib-shlib.so
/home1/tonybai/.bin/go15beta1/go/pkg/tool/linux_amd64/link: cannot implicitly include runtime/cgo in a shared library

这回连warnning都没有了,直接是一个error。这里提示:无法在一个共享库中隐式包含runtime/cgo。也就是说我们在构建 testshared/shlib这个动态共享库时,还需要显式的link到runtime/cgo,这里就需要另外一个命令行标志:- linkshared。我们再来试试:

$ go build  -linkshared -buildmode=shared testsharedlib/shlib

这回build成功!我们再来试试install:

$ go install  -linkshared -buildmode=shared testsharedlib/shlib

同样成功了。并且我们在$GOPATH/pkg/linux_amd64_dynlink下发现了共享库:

$ ls -R
.:
libtestsharedlib-shlib.so  testsharedlib

./testsharedlib:
shlib.a  shlib.shlibname

$ ldd libtestsharedlib-shlib.so
    linux-vdso.so.1 =>  (0x00007fff93983000)
    libruntime,sync-atomic.so => /home1/tonybai/.bin/go15beta1/go/pkg/linux_amd64_dynlink/libruntime,sync-atomic.so (0x00007fa150f1b000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa150b3f000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa150921000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fa1517a7000)

好了,既然共享库编译出来了。我们就来用一下这个共享库。

//app/main.go

package main

import (
    "testsharedlib/shlib"
)

func main() {
    shlib.Method1()
}

$ go build -linkshared main.go
$ ldd main
    linux-vdso.so.1 =>  (0x00007fff579f7000)
    libruntime,sync-atomic.so => /home1/tonybai/.bin/go15beta1/go/pkg/linux_amd64_dynlink/libruntime,sync-atomic.so (0x00007fa8d6df2000)
    libtestsharedlib-shlib.so => /home1/tonybai/test/go/go15/pkg/linux_amd64_dynlink/libtestsharedlib-shlib.so (0x00007fa8d6962000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa8d6586000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fa8d6369000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fa8d71ef000)

$ main
shlib -Method1

编译执行ok。从输出结果来看,我们可以清晰看到main依赖的.so以及so的路径。我们再来试试,如果将testsharedlib源码目录移除后,是否还能编译ok:

$ go build -linkshared main.go
main.go:4:2: cannot find package "testsharedlib/shlib" in any of:
    /home1/tonybai/.bin/go15beta1/go/src/testsharedlib/shlib (from $GOROOT)
    /home1/tonybai/test/go/go15/src/testsharedlib/shlib (from $GOPATH)

go编译器无法找到shlib,也就说即便是动态链接,我们也要有动态共享库的源码,应用才能编译通过。

internal package

internal包不是go 1.5的原创,在go 1.4中就已经提出对internal package的支持了。但go 1.4发布时,internal package只能用于GOROOT下的go core核心包,用户层面GOPATH不支持internal package。按原计划,go 1.5中会将internal包机制工作范围全面扩大到所有repository的。我原以为1.5beta1以及将internal package机制生效了,但实际结果呢,我们来看看示例代码:

测试目录结构如下:

testinternal/src
    mypkg/
        /internal
            /foo
                foo.go
        /pkg1
            main.go

    otherpkg/
            main.go

按照internal包的原理,预期mypkg/pkg1下的代码是可以import "mypkg/internal/foo"的,otherpkg/下的代码是不能import "mypkg/internal/foo"的。

//foo.go
package foo

import "fmt"

func Foo() {
    fmt.Println("mypkg/internal/foo")
}

//main.go
package main

import "mypkg/internal/foo"

func main() {
    foo.Foo()
}

在pkg1和otherpkg下分别run main.go:

mypkg/pkg1$ go run main.go
mypkg/internal/foo

otherpkg$ go run main.go
mypkg/internal/foo

可以看到在otherpkg下执行时,并没有任何build error出现。看来internal机制并未生效。

我们再来试试import $GOROOT下某些internal包,看看是否可以成功:

package main

import (
    "fmt"
    "image/internal/imageutil"
)

func main() {
    fmt.Println(imageutil.DrawYCbCr)
}

我们run这个代码:

$go run main.go
0x6b7f0

同样没有出现任何error。

不是很清楚为何在1.5beta1中internal依旧无效。难道非要等最终1.5 release版么?

【Vendor】
Vendor机制是go team为了解决go第三方包依赖和管理而引入的实验性技术。你执行以下go env:

$go env
GOARCH="amd64"
GOBIN="/Users/tony/.bin/go15beta1/go/bin"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/tony/Test/GoToolsProjects"
GORACE=""
GOROOT="/Users/tony/.bin/go15beta1/go"
GOTOOLDIR="/Users/tony/.bin/go15beta1/go/pkg/tool/darwin_amd64"
GO15VENDOREXPERIMENT=""
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"

从结果中你会看到新增一个GO15VENDOREXPERIMENT变量,这个就是用来控制vendor机制是否开启的环境变量,默认不开启。若要开启,可以在环境变量文件中设置或export GO15VENDOREXPERIMENT=1临时设置。

vendor机制是在go 1.5beta1发布前不长时间临时决定加入到go 1.5中的,Russ Cox在Keith Rarick之前的一个Proposal的基础上重新做了设计而成,大致机制内容:

If there is a source directory d/vendor, then,
    when compiling a source file within the subtree rooted at d,
    import "p" is interpreted as import "d/vendor/p" if that exists.

    When there are multiple possible resolutions,
    the most specific (longest) path wins.

    The short form must always be used: no import path can
    contain “/vendor/” explicitly.

    Import comments are ignored in vendored packages.

下面我们来测试一下这个机制。首先我们临时开启vendor机制,export GO15VENDOREXPERIMENT=1,我们的测试目录规划如下:

testvendor
    vendor/
        tonybai.com/
            foolib/
                foo.go
    main/
        main.go

$GOPATH/src/tonybai.com/foolib/foo.go

//vendor/tonybai.com/foolib/foo.go
package foo

import "fmt"

func Hello() {
    fmt.Println("foo in vendor")
}

//$GOPATH/src/tonybai.com/foolib/foo.go
package foo

import "fmt"

func Hello() {
    fmt.Println("foo in gopath")
}

vendor和gopath下的foo.go稍有不同,主要在输出内容上,以方便后续区分。

现在我们编译执行main.go

//main/main.go
package main

import (
    "tonybai.com/foolib"
)

func main() {
    foo.Hello()
}

$go run main.go
foo in gopath

显然结果与预期不符,我们通过go list -json来看main.go的依赖包路径:

$go list -json
{
… …
    "Imports": [
        "tonybai.com/foolib"
    ],
    "Deps": [
        "errors",
        "fmt",
        "io",
        "math",
        "os",
        "reflect",
        "runtime",
        "strconv",
        "sync",
        "sync/atomic",
        "syscall",
        "time",
        "tonybai.com/foolib",
        "unicode/utf8",
        "unsafe"
    ]
}

可以看出并没有看到vendor路径,main.go import的是$GOPATH下的foo。难道是go 1.5beta1的Bug?于是翻看各种资料,最后在go 1.5beta1发布前最后提交的revison的commit log中得到了帮助:

cmd/go: disable vendoredImportPath for code outside $GOPATH
It was crashing.
This fixes the build for
GO15VENDOREXPERIMENT=1 go test -short runtime

Fixes #11416.

Change-Id: I74a9114cdd8ebafcc9d2a6f40bf500db19c6e825
Reviewed-on: https://go-review.googlesource.com/11964
Reviewed-by: Russ Cox <rsc@golang.org>

从commit log来看,大致意思是$GOPATH之外的代码的vendor机制被disable了(因为某个bug)。也就是说只有$GOPATH路径下的包在 import时才会考虑vendor路径,我们的代码的确没有在$GOPATH下,我们重新设置一下$GOPATH。

$export GOPATH=~/test/go/go15
[tony@TonydeMacBook-Air-2 ~/test/go/go15/src/testvendor/main]$go list -json
{
  
  … …
    "Imports": [
        "testvendor/vendor/tonybai.com/foolib"
    ],
    "Deps": [
        "errors",
        "fmt",
        "io",
        "math",
        "os",
        "reflect",
        "runtime",
        "strconv",
        "sync",
        "sync/atomic",
        "syscall",
        "testvendor/vendor/tonybai.com/foolib",
        "time",
        "unicode/utf8",
        "unsafe"
    ]
}

这回可以看到vendor机制生效了。执行main.go:

$go run main.go
foo in vendor

这回与预期结果就相符了。

前面提到,关闭GOPATH外的vendor机制是因为一个bug,相信go 1.5正式版发布时,这块会被enable的。

三、小结

Go 1.5还增加了很多工具,如trace,但因文档不全,尚不知如何使用。

Go 1.5标准库也有很多小的变化,这个只有到使用时才能具体深入了解。

Go 1.5更多是Go语言骨子里的变化,也就是runtime和编译器重写。语法由于兼容Go 1,所以基本frozen,因此从外在看来,基本没啥变动了。

至于Go 1.5的性能,官方的说法是,有的程序用1.5编译后会变得慢点,有的会快些。官方bench的结果是总体比1.4快上一些。但Go 1.5在性能方面主要是为了减少gc延迟,后续版本才会在性能上做进一步优化,优化空间还较大的,这次runtime、编译器由c变go,很多地方的go 代码并非是最优的,多是自动翻译,相信经过Go team的优化后,更idiomatic的Go code会让Go整体性能更为优异。

Go 1.4中值得关注的几个变化

在Go 1.3发布半年过去后,Go核心项目组于本月初发布了Go 1.4 Beta1版本。这个版本的几个变化点虽然不是革命性的,但对后续Go语言的发展来说,打下了基础,定下了基调。这里就几个值得关注的变化点结合Go 1.4代码进行一些简单描述,希望大家能对Go 1.4有个感性的认知和了解。

Go 1.4依旧保持了Go 1兼容性的承诺,你的已有代码几乎无需任何改动就可以通过Go 1.4的编译并运行。(以下是我的测试环境:go version go1.3 darwin/amd64 vs. go version go1.4beta1 linux/amd64

一、语言变化

1、For-range循环

在Go 1.3及以前,for-range循环具有两种形式:

for k, v := range x {
    …
}

for k := range x {
    …
}

问题:如果我们不关心循环中的值,我们只关心循环本身,我们仍然要提供一个变量,或用_占位。

for _ = range x {
    …
}

下面这样的语法在Go 1.3及以前是无法编译通过的:

for range x {
    …
}

不过Go 1.4支持这种形式的语法,它使得代码更加clean,虽然它可能很少会被使用到。

例子:

//testforrange.go

package main

import "fmt"

func main() {
        var a [5]int = [5]int{2, 3, 4, 5, 6}
        for k, v := range a {
                fmt.Println(k, v)
        }

        for k := range a {
                fmt.Println(k)
        }

        for _ = range a {
                fmt.Println("print without care about the key and value")
        }

        for range a {
                fmt.Println("new syntax – print without care about the key and value")
        }
}

Go 1.3编译出错:

$go run testforrange.go
# command-line-arguments
./testforrange.go:19: syntax error: unexpected range, expecting {
./testforrange.go:22: syntax error: unexpected }

Go 1.4编译成功并输出正确结果:

0 2
1 3
2 4
3 5
4 6
0
1
2
3
4
print without care about the key and value
print without care about the key and value
print without care about the key and value
print without care about the key and value
print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value
new syntax – print without care about the key and value

2、通过**T调用方法

下面这个例子:

package main

import "fmt"

type T int

func (T) M() {
        fmt.Println("Call M")
}

var x **T

func main() {
        x.M()
}

按照Go 1.4官方release note的说法,1.3版本及以前的gc和gccgo都会正常接受这种调用方式。但Go 1规范只允许自动在x前面加一个解引用,而不是两个,因此这个是有悖于定义的。Go 1.4强制禁止这种调用。

不过根据我实际的测试,Go 1.3和Go 1.4针对上面代码都会出现同样地编译错误。

$go run testdoubledeferpointer.go
# command-line-arguments
./testdoubledeferpointer.go:14: calling method M with receiver x (type **T) requires explicit dereference

二、支持的操作系统以及处理器体系架构的变化

这个无法演示。不过一个主要的变化就是Go 1.4可以构建出运行于ARM处理器Android操作系统上的二进制程序了。使用go.mobile库中的支持包,Go 1.4也可以构建出可以被Android应用加载的.so库。

三、兼容性变化

人们通过unsafe包并利用Go的内部实现细节和数据的机器表示形式来绕过Go语言类型系统的约束。Go的设计者们认为这是对Go兼容性规范的 不尊重,在Go 1.4中,Go核心组正式宣布unsafe code不再保证其兼容性。这次Go 1.4并没有针对此做任何代码变动,只是一个clarification而已。

四、实现和工具的变化

1、运行时(runtime)的变化

Go 1.3及以前版本,Go语言的runtime(垃圾收集、并发支持、interface管理、maps、slices、strings等)主要由C语言和 少量汇编语言实现的。在1.4版本中,很多代码被替换成了用Go自身实现,这样垃圾回收器可以扫描程序运行时栈,获取活跃变量的精确信息。这个变 化很大,但对程序应该没有语义上的影响。

这次重写使得垃圾回收器变得更加精确,这意味着它知道所有程序中活跃指针的位置。这些相关改变将减小heap的大小,总体上大约减少 10%~30%。

这样做的结果是栈也不再需要是分段的(segmented)了,消除了“hot split”的问题。如果一个stack到达了使用上限,Go将分配一个新的更大的stack,相应goroutine中的所有活跃的栈帧将被复制到新 stack上,所有指向栈的指针将被更新。在某些场景下,其性能将会变得显著提升,并且这样修改后,其性能更具可预测性。

连续栈(contiguous stacks)的使用使得栈的初始Size可以更小,在Go 1.4中goroutine的初始栈大小从8192字节缩小为2048字节。(正式发布时也许会改为4096)。

interface值类型的实现也做了调整。在之前的发布版中,interface值内部用一个字(word)来承载,要么是一个指针,要么是一 个单字(one-word)大小的纯量值,这取决于interface值变量中具体存储的是什么对象。这个实现会给垃圾收集器带来诸多困难,因此 在Go 1.4版本中interface值内部就用指针表示。在运行的程序中,绝大多数interface值都是指针,因此这个影响很小。不过那些在 interface值类型变量中存储整型值的程序将会有更多的内存分配。

2、gccgo的状态

Gcc和Go两个项目的发布计划不是同步的,GCC 4.9版本包含了实现了1.2规范的gccgo,下一个发布版gcc 5.0将可能包含实现了1.4规范的gccgo。

3、internal包(内部包)

Go以package为基本逻辑单元组织代码。Go 1.3及之前版本的Go语言实际上只支持两种形式Package内符号的可见性:本地的(unexported)和全局的(exported)。有些时候 我们希望一些包并非能被所有外部包所导入,但却能被其“临近”的包所导入和访问。但之前的Go语言不具备这种特性。Go 1.4引入了"internal"包的概念,导入这种internal包的规则约束如下:

如果导入代码本身不在以"internal"目录的父目录为root的目录树中,那么 不允许其导入路径(import path)中包含internal元素。

例如:
    – a/b/c/internal/d/e/f只可以被以a/b/c为根的目录树下的代码导入,不能被a/b/g下的代码导入。
    – $GOROOT/src/pkg/internal/xxx只能被标准库($GOROOT/src)中的代码所导入。(注:Go 1.4 取消了$GOROOT/src/pkg,标准库都移到$GOROOT/src下了)。
    – $GOROOT/src/pkg/net/http/internal只能被net/http和net/http/*的包所导入
    – $GOPATH/src/mypkg/internal/foo只能被$GOPATH/src/mypkg包的代码所导入

对于Go 1.4该规则首先强制应用于$GOROOT下。Go 1.5将扩展应用到$GOPATH下。

4、权威导入路径(import paths)

我们经常使用托管在公共代码托管服务中的代码,诸如github.com,这意味着包导入路径包含托管服务名,比如github.com/rsc /pdf。一些场景下为了不破坏用户代码,我们用rsc.io/pdf,屏蔽底层具体哪家托管服务,比如rso.io/pdf的背后可能是 github.com也可能是bitbucket。但这样会引入一个问题,那就是不经意间我们为一个包生成了两个合法的导入路径。如果一个程序中 使用了这两个合法路径,一旦某个路径没有被识别出有更新,或者将包迁移到另外一个不同的托管公共服务下去时,使用旧导入路径包的程序就会报错。

Go 1.4引入一个包字句的注释,用于标识这个包的权威导入路径。如果使用的导入的路径不是权威路径,go命令会拒绝编译。语法很简单:

package pdf // import "rsc.io/pdf"

如果pdf包使用了权威导入路径注释,那么那些尝试使用github.com/rsc/pdf导入路径的程序将会被go编译器拒绝编译。

这个权威导入路径检查是在编译期进行的,而不是下载阶段。

我们举个例子:

我们的包foo以前是放在github.com/bigwhite/foo下面的,后来主托管站换成了tonybai.com/foo,最新的 foo包的代码:

package foo // import "tonybai.com/foo"

import "fmt"

func Echo(a string) {
        fmt.Println("Foo:, a)
}

某个应用通过旧路径github.com/bigwhite/foo导入了该包:

//testcanonicalimportpath.go
package main

import "github.com/bigwhite/foo"

func main() {
        foo.Echo("Hello!")
}

我们编译该go文件,得到以下结果:

code in directory /home/tonybai/Test/Go/src/github.com/bigwhite/foo expects import "tonybai.com/foo"

5、go generate子命令

go 1.4中go工具集合新引入一个子命令:go generate,用于在编译前自动化生成某类代码。例如在.y上运行yacc编译器生成实现该语法的.go源文件。或是使用stringer工 具自动为常量生成String方法。这个命令并非由go tools(build, get等)自动执行,而必须显式执行。

不过我简单测试了一下,似乎这个命令设计文档中的:

// +build generate

并不好用啊。即便将其作为generate directive放入go源文件,该文件依旧会被go编译器当做正常go文件编译。Go 1.4标准库中使用go generate directive的有三个地方:

strconv/quote.go://go:generate go run makeisprint.go -output isprint.go
time/zoneinfo_windows.go://go:generate go run genzabbrs.go -output zoneinfo_abbrs_windows.go
unicode/letter.go://go:generate go run maketables.go -tables=all -output tables.go

通过go generate来实现泛型(generics)似乎不那么优雅啊。虽然设计者并非将其作为Go泛型的实现^_^。

6、源码布局变化

在Go自身源码库($GOROOT下)中,包的源码放在src/pkg中,这样做与其他库不同,包括Go自己的子库,比如go.tools。因此在Go 1.4中,pkg这一层目录树将被去除,比如fmt包的源码曾经放在src/pkg/fmt下,现在则放在src/fmt下。

五、性能

绝大多数程序使用1.4编译后的运行速度会与1.3的一致或略有提升,有些可能也会变得慢些。这次修改的较多,很难准确预测。

这次许多runtime的代码由C变为Go,这将导致一些heap大小有所缩减。另外这样做后有利于Go编译器的优化,诸如内联,会带来性能上的小幅提升。

垃圾回收器一方面得到了加速,使得重度依赖垃圾收集的程序得到可衡量的提升。但另外一方面,新的write barrier又引起了性能下降。提升和下降的量的多少取决于程序的行为。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats