Go语言 用一行程序只返回一些命名的返回值(半裸返回)[已关闭]

jm81lzqq  于 2023-04-09  发布在  Go
关注(0)|答案(1)|浏览(117)

**已关闭。**此问题为not reproducible or was caused by typos。当前不接受答案。

这个问题是由一个错字或一个无法再复制的问题引起的。虽然类似的问题可能是on-topic在这里,但这个问题的解决方式不太可能帮助未来的读者。
昨天关门了。
Improve this question
如果一个函数声明了命名的返回值,并且其中有多个return语句,常见的情况是这样的:

func foo() (bar Bar, baz Baz, err error) {
    // other code
    if condition {
        err = fmt.Errorf("specific error description")
        return
    }
    // other code
    return
}

condition为true时,将设置err命名返回值,但其他返回值(barbaz)保持不变。
有没有办法替代

err = fmt.Errorf("specific error description")
return

用一句俏皮话
当然,这一行可以工作并给予正确的结果:

return bar, baz, fmt.Errorf("specific error description")

但是,编译器是否足够聪明,知道不需要将barbaz的值分别赋值给它们自己?
由于我不熟悉编译器实现的细节,所以我尝试了gobench来查看差异。
下面是我的测试代码:

const fieldSize = 32

type foo struct {
    bigField [fieldSize]byte
}

func genFoo() (result1 foo, result2 foo, ok bool) {
    result1.bigField[fieldSize/2] = 1
    result2.bigField[fieldSize-1] = 8
    return result1, result2, true
}

func genFoo2() (result1 foo, result2 foo, ok bool) {
    result1.bigField[fieldSize/2] = 1
    result2.bigField[fieldSize-1] = 8
    return result2, result1, true
}

func genFoo3() (result1 foo, result2 foo, ok bool) {
    result1.bigField[fieldSize/2] = 1
    result2.bigField[fieldSize-1] = 8
    ok = true
    return
}

func Benchmark_genFoo(b *testing.B) {
    for i := 0; i < b.N; i++ {
        result1, result2, ok := genFoo()
        if !(ok && result1.bigField[fieldSize/2] == 1 && result2.bigField[fieldSize-1] == 8) {
            b.Fatalf("Incorrect")
        }
    }
}

func Benchmark_genFoo2(b *testing.B) {
    for i := 0; i < b.N; i++ {
        result1, result2, ok := genFoo2()
        if !(ok && result2.bigField[fieldSize/2] == 1 && result1.bigField[fieldSize-1] == 8) {
            b.Fatalf("Incorrect")
        }
    }
}

func Benchmark_genFoo3(b *testing.B) {
    for i := 0; i < b.N; i++ {
        result1, result2, ok := genFoo3()
        if !(ok && result1.bigField[fieldSize/2] == 1 && result2.bigField[fieldSize-1] == 8) {
            b.Fatalf("Incorrect")
        }
    }
}

genFoo()是“一行程序”版本。
genFoo2()使用完全相同的语法编写,但return语句交换了result1result2
genFoo3()是“多线程”版本。
结果是:

goos: windows
goarch: amd64
cpu: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
Benchmark_genFoo
Benchmark_genFoo-12         166055121            7.085 ns/op
Benchmark_genFoo2
Benchmark_genFoo2-12        134723227            8.945 ns/op
Benchmark_genFoo3
Benchmark_genFoo3-12        166496562            7.163 ns/op

使用fieldSize = 32genFoo()genFoo3()的性能几乎相同。
另一方面,genFoo2()尽管语法相同,但速度较慢。
使用fieldSize = 1000000,结果将是:

Benchmark_genFoo
Benchmark_genFoo-12            10000        113464 ns/op
Benchmark_genFoo2
Benchmark_genFoo2-12            5528        215486 ns/op
Benchmark_genFoo3
Benchmark_genFoo3-12           10000        112115 ns/op

genFoo()genFoo3()仍然彼此接近,而genFoo2()明显较慢。
对于fieldSize = 100000000,结果将是:

Benchmark_genFoo
Benchmark_genFoo-12               12      91378267 ns/op
Benchmark_genFoo2
Benchmark_genFoo2-12              10     102982670 ns/op
Benchmark_genFoo3
Benchmark_genFoo3-12              20      55025230 ns/op

哎呀,genFoo()在这个例子中失败了,尽管它的行为与genFoo2()不同。
但为什么呢?

5uzkadbs

5uzkadbs1#

如果你希望听到这样的俏皮话

func f() (i, j int) {
    i++
    return _, 1
}

它的性能和genFoo3()完全一样,不幸的是,它在go中不存在。
我可以回答你提到的一些性能问题,以及go中命名结果的底层实现如何解释为什么你看到genFoogenFoo2genFoo3之间的性能差异。
对于未命名和已命名的返回函数:func f(a int) (b int)func g(a int) int,它们的堆栈分配略有不同。

f(a int) (b int) | g(a int) int
-------------------------------
* int a          | * int a
* int b          | * space for return value

因为你的函数都有命名的返回值,result1result2ok已经在堆栈中命名了。所以genFoo2看起来比genFoo慢很多,因为需要在result1result2之间进行交换,这是更昂贵的。return语句genFoo2将执行相当于

temp := return1
return1 = return2
return2 = temp
return

至于为什么genFoogenFoo3慢,编译器可能在genFoo的return语句中试图复制result1result2(而genFoo3不需要这样的操作)。尽管你可以使用-gcflags -m检查你的特定版本是否如此。

相关问题