打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Golang Internals Part 2: Nice benefits of named return values

Golang Internals Part 2: Nice benefits of named return values

You may know that Golang offers the ability to name return values. Thus far at minio we have not been using this feature much, but that’ll change since there are some nice hidden benefits as we will explain in this blog post.

If you are like us, you may have considerable amounts of code as shown down below whereby for every return statement you are instantiating a new object in order to return a ‘default’ value:

type objectInfo struct {
arg1 int64
arg2 uint64
arg3 string
arg4 []int
}
func NoNamedReturnParams(i int) (objectInfo) {

if i == 1 {
// Do one thing
return objectInfo{}
}

if i == 2 {
// Do another thing
return objectInfo{}
}

if i == 3 {
// Do one more thing still
return objectInfo{}
}

// Normal return
return objectInfo{}
}

If you look at the actual code that the Golang compiler generates, you’ll end up with something like this:

"".NoNamedReturnParams t=1 size=243 args=0x40 locals=0x0
0x0000 TEXT "".NoNamedReturnParams(SB), $0-64
0x0000 MOVQ $0, "".~r1+16(FP)
0x0009 LEAQ "".~r1+24(FP), DI
0x000e XORPS X0, X0
0x0011 ADDQ $-16, DI
0x0015 DUFFZERO $288
0x0028 MOVQ "".i+8(FP), AX
0x002d CMPQ AX, $1
0x0031 JEQ $0, 199
0x0037 CMPQ AX, $2
0x003b JEQ $0, 155
0x003d CMPQ AX, $3
0x0041 JNE 111
0x0043 MOVQ "".statictmp_2(SB), AX
0x004a MOVQ AX, "".~r1+16(FP)
0x004f LEAQ "".~r1+24(FP), DI
0x0054 LEAQ "".statictmp_2+8(SB), SI
0x005b DUFFCOPY $854
0x006e RET
0x006f MOVQ "".statictmp_3(SB), AX
0x0076 MOVQ AX, "".~r1+16(FP)
0x007b LEAQ "".~r1+24(FP), DI
0x0080 LEAQ "".statictmp_3+8(SB), SI
0x0087 DUFFCOPY $854
0x009a RET
0x009b MOVQ "".statictmp_1(SB), AX
0x00a2 MOVQ AX, "".~r1+16(FP)
0x00a7 LEAQ "".~r1+24(FP), DI
0x00ac LEAQ "".statictmp_1+8(SB), SI
0x00b3 DUFFCOPY $854
0x00c6 RET
0x00c7 MOVQ "".statictmp_0(SB), AX
0x00ce MOVQ AX, "".~r1+16(FP)
0x00d3 LEAQ "".~r1+24(FP), DI
0x00d8 LEAQ "".statictmp_0+8(SB), SI
0x00df DUFFCOPY $854
0x00f2 RET

All fine and dandy, but if that looks a bit repetitive to you, you are quite right. Essentially for each of the return statements the object to be returned is more or less allocated/initialized (or more precisely copied via the DUFFCOPY macro).

After all that is what we asked for by returning via return objectInfo{} in every case.

Naming the return value

Now look at what happens if we make a very simple change, essentially just giving the return value a name (oi) and using the ‘naked’ return feature of Golang (dropping the argument for the return statement, although this is not strictly required, more on that later):

func NamedReturnParams(i int) (oi objectInfo) {

if i == 1 {
// Do one thing
return
}

if i == 2 {
// Do another thing
return
}

if i == 3 {
// Do one more thing still
return
}

// Normal return
return
}

Again looking at the code generated by the compiler, we get the following:

"".NamedReturnParams t=1 size=67 args=0x40 locals=0x0
0x0000 TEXT "".NamedReturnParams(SB), $0-64
0x0000 MOVQ $0, "".oi+16(FP)
0x0009 LEAQ "".oi+24(FP), DI
0x000e XORPS X0, X0
0x0011 ADDQ $-16, DI
0x0015 DUFFZERO $288
0x0028 MOVQ "".i+8(FP), AX
0x002d CMPQ AX, $1
0x0031 JEQ $0, 66
0x0033 CMPQ AX, $2
0x0037 JEQ $0, 65
0x0039 CMPQ AX, $3
0x003d JNE 64
0x003f RET
0x0040 RET
0x0041 RET
0x0042 RET

That is a pretty massive difference with all four occurrences of the object initialization and DUFFCOPY stuff gone (even for this trivial case). It reduces the size of the function down from 243 to 67 bytes. Also as an additional benefit you will save some CPU cycles upon exiting out because there is no need anymore to do anything in order to setup the return value.

Note that if you don’t like or prefer the naked return that Golang offers, you can use return io while still getting the same benefit, like so:

	if i == 1 {
return io
}

Real world example in minio server

Examining a bit further in minio server we took the following case:

// parse credentialHeader string into its structured form.
func parseCredentialHeader(credElement string) (credentialHeader) {
creds := strings.Split(strings.TrimSpace(credElement), "=")
if len(creds) != 2 {
return credentialHeader{}
}
if creds[0] != "Credential" {
return credentialHeader{}
}
credElements := strings.Split(strings.TrimSpace(creds[1]), "/")
if len(credElements) != 5 {
return credentialHeader{}
}
if false /*!isAccessKeyValid(credElements[0])*/ {
return credentialHeader{}
}
// Save access key id.
cred := credentialHeader{
accessKey: credElements[0],
}
var e error
cred.scope.date, e = time.Parse(yyyymmdd, credElements[1])
if e != nil {
return credentialHeader{}
}
cred.scope.region = credElements[2]
if credElements[3] != "s3" {
return credentialHeader{}
}
cred.scope.service = credElements[3]
if credElements[4] != "aws4_request" {
return credentialHeader{}
}
cred.scope.request = credElements[4]
return cred
}

Looking at the assembly we get the following function header (we’ll spare you the full listing…):

"".parseCredentialHeader t=1 size=1157 args=0x68 locals=0xb8

If we modify the code to use a named return parameter (second source code block below), check out what happens to the size of the function:

"".parseCredentialHeader t=1 size=863 args=0x68 locals=0xb8 

It is shaving off some 300 bytes out of a total of 1150 bytes which is not bad for a such a minimal change to the source code. And depending where you are coming from, you may prefer the ‘cleaner’ look of the source code too:

// parse credentialHeader string into its structured form.
func parseCredentialHeader(credElement string) (ch credentialHeader) {
creds := strings.Split(strings.TrimSpace(credElement), "=")
if len(creds) != 2 {
return
}
if creds[0] != "Credential" {
return
}
credElements := strings.Split(strings.TrimSpace(creds[1]), "/")
if len(credElements) != 5 {
return
}
if false /*!isAccessKeyValid(credElements[0])*/ {
return
}
// Save access key id.
cred := credentialHeader{
accessKey: credElements[0],
}
var e error
cred.scope.date, e = time.Parse(yyyymmdd, credElements[1])
if e != nil {
return
}
cred.scope.region = credElements[2]
if credElements[3] != "s3" {
return
}
cred.scope.service = credElements[3]
if credElements[4] != "aws4_request" {
return
}
cred.scope.request = credElements[4]
return cred
}

Conclusion

So we will be gradually adopting named return values more and more, both for new code as well as for existing code.

In fact we are also investigating if we can develop a little utility to help or automate this process. Think along the lines of gofmt but then modifying the source automatically to make the changes outlined above. Especially in the case where the return value is not yet named (and so the utility would have to give it a name), it necessarily cannot be the case that this return variable is changed in any way in the existing source, and thus using return ch (in case of the listing above) will not result in any functional changes of the program whatsoever. So stay tuned for that.

We hope that this post was useful to you and provides some new insights into how Go operates internally and on how to improve your Golang code.

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【Go语言入门100题】042 日期格式化 (5 分) Go语言|Golang
php定时执行任务的几个方法
个性化你的Git Log的输出格式,参数集合
Golang的坑之http读取大文件必须读完
一个发邮件的demo 用golang
Golang Channel用法简编 | Tony Bai
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服