Golang Go语言中函数传参 string 和 *string 效率一样么

发布于 1周前 作者 itying888 来自 Go语言

Golang Go语言中函数传参 string 和 *string 效率一样么


我测了一下发现差不多(循环 1000000000 比较调用时间),string 不是传值拷贝么,效率不明显低点?
代码


package main

import (
	"fmt"
	"time"
)

func test(str *string) int{

	return len(*str)
}
func test2(str string) int{

	return len(str)
}
func main() {
	str := "abcdgjsiogjerhodsifjtudfmjadskdospfiorejtgdfkhkhijdsuyfuirnfclkvjkhiiddusyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdfsyfuewndsodsdsafdf"

	time1:=time.Now().UnixNano() / int64(time.Millisecond)
	for i:=0;i<1000000000;i++{
		test2(str)
	}
	time2 := time.Now().UnixNano() / int64(time.Millisecond)

	for i:=0;i<1000000000;i++{
		test(&str)
	}
	time3 :=time.Now().UnixNano() / int64(time.Millisecond)
	fmt.Println("milliseconds:,",time2-time1)
	fmt.Println("milliseconds,",time3-time2)
}


运行结果

milliseconds:, 535

milliseconds, 523
就慢 12ms,2%。。。好像没啥差距啊。(多跑几次甚至有时候后者更慢)


更多关于Golang Go语言中函数传参 string 和 *string 效率一样么的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

16 回复

试试修改字符串?
没有修改,会不会被优化了,刚开始学 go,我猜的

更多关于Golang Go语言中函数传参 string 和 *string 效率一样么的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


没记错 go 的 string 应该是一个特殊的 Slice,所谓的值传递只是传这个特殊 slice 的结构体而已。

String 不可变,实际上传递的就是指针,所以理论上应该传 string*更慢一些?

https://go101.org/article/string.html

按照这里的说法 string 是一个特殊的内部类型,在复制的时候复制的是指向底层字节数组的指针而不是字符串本身。然后因为 string 类型是不可变的,所以也不需要考虑字符串被修改的问题。

显然应该拿 struct 试啊

string 实际上是不可变的字节切片,所以虽然是值传递,且切片是值类型,但是由于切片结构体并不是存储的底层数组,而是指向数组的指针,所以实际上开销很小

string 包含指向字节数组的指针和表示字节数的 int

没有什么区别,string 传递时也是指针。

复制 string,相当于复制 reflect.StringHeader,64bit 机器上是 16 字节,*string 是 8 字节。复制 16 字节和复制 8 字节是没有区别的,你观察到的性能差距只是抖动而已。

type stringStruct struct {
str unsafe.Pointer
len int
}

https://golang.org/src/runtime/string.go

string 是引用类型,用指针反而多了层指针的转换

lz 应该比较的是 string vs []byte

我觉得你应该从汇编的角度来分析。

teststr.go
<br>package main<br><br>func test(str *string) int {<br><br> return len(*str)<br>}<br>func test2(str string) int {<br><br> return len(str)<br>}<br><br>func main() {<br> s :="a"<br><br> test2(s)<br> test(&amp;s)<br><br><br>}<br><br><br>

汇编

<br>"".test STEXT nosplit size=53 args=0x10 locals=0x10<br> 0x0000 00000 (teststr.go:3) TEXT "".test(SB), NOSPLIT, $16-16<br> 0x0000 00000 (teststr.go:3) SUBQ $16, SP<br> 0x0004 00004 (teststr.go:3) MOVQ BP, 8(SP)<br> 0x0009 00009 (teststr.go:3) LEAQ 8(SP), BP<br> 0x000e 00014 (teststr.go:3) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)<br> 0x000e 00014 (teststr.go:3) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)<br> 0x000e 00014 (teststr.go:3) FUNCDATA $3, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)<br> 0x000e 00014 (teststr.go:3) PCDATA $2, $0<br> 0x000e 00014 (teststr.go:3) PCDATA $0, $0<br> 0x000e 00014 (teststr.go:3) MOVQ $0, "".~r1+32(SP)<br> 0x0017 00023 (teststr.go:5) PCDATA $2, $1<br> 0x0017 00023 (teststr.go:5) PCDATA $0, $1<br> 0x0017 00023 (teststr.go:5) MOVQ "".str+24(SP), AX<br> 0x001c 00028 (teststr.go:5) TESTB AL, (AX)<br> 0x001e 00030 (teststr.go:5) PCDATA $2, $0<br> 0x001e 00030 (teststr.go:5) MOVQ 8(AX), AX<br> 0x0022 00034 (teststr.go:5) MOVQ AX, ""..autotmp_2(SP)<br> 0x0026 00038 (teststr.go:5) MOVQ AX, "".~r1+32(SP)<br> 0x002b 00043 (teststr.go:5) MOVQ 8(SP), BP<br> 0x0030 00048 (teststr.go:5) ADDQ $16, SP<br> 0x0034 00052 (teststr.go:5) RET<br><br>


<br>"".test2 STEXT nosplit size=47 args=0x18 locals=0x10<br> 0x0000 00000 (teststr.go:7) TEXT "".test2(SB), NOSPLIT, $16-24<br> 0x0000 00000 (teststr.go:7) SUBQ $16, SP<br> 0x0004 00004 (teststr.go:7) MOVQ BP, 8(SP)<br> 0x0009 00009 (teststr.go:7) LEAQ 8(SP), BP<br> 0x000e 00014 (teststr.go:7) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)<br> 0x000e 00014 (teststr.go:7) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)<br> 0x000e 00014 (teststr.go:7) FUNCDATA $3, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)<br> 0x000e 00014 (teststr.go:7) PCDATA $2, $0<br> 0x000e 00014 (teststr.go:7) PCDATA $0, $0<br> 0x000e 00014 (teststr.go:7) MOVQ $0, "".~r1+40(SP)<br> 0x0017 00023 (teststr.go:9) PCDATA $0, $1<br> 0x0017 00023 (teststr.go:9) MOVQ "".str+32(SP), AX<br> 0x001c 00028 (teststr.go:9) MOVQ AX, ""..autotmp_2(SP)<br> 0x0020 00032 (teststr.go:9) MOVQ AX, "".~r1+40(SP)<br> 0x0025 00037 (teststr.go:9) MOVQ 8(SP), BP<br> 0x002a 00042 (teststr.go:9) ADDQ $16, SP<br> 0x002e 00046 (teststr.go:9) RET<br><br>

test 指针参数代码都比 不传指针的多。

指针的计算要多一个操作


下面是调用



<br>// 定义字符串<br> 0x0024 00036 (teststr.go:13) LEAQ go.string."a"(SB), AX<br> 0x002b 00043 (teststr.go:13) MOVQ AX, "".s+24(SP)<br> 0x0030 00048 (teststr.go:13) MOVQ $1, "".s+32(SP)<br><br><br>// 传值<br> 0x0039 00057 (teststr.go:15) PCDATA $2, $0<br> // StringSlice 结构<br> 0x0039 00057 (teststr.go:15) MOVQ AX, (SP)<br> 0x003d 00061 (teststr.go:15) MOVQ $1, 8(SP)<br> 0x0046 00070 (teststr.go:15) CALL "".test2(SB)<br><br><br>// 传指针<br> 0x004b 00075 (teststr.go:16) LEAQ "".s+24(SP), AX<br> 0x0050 00080 (teststr.go:16) PCDATA $2, $0<br> // Stringslice 指针<br> 0x0050 00080 (teststr.go:16) MOVQ AX, (SP)<br> 0x0054 00084 (teststr.go:16) CALL "".test(SB)<br>

都是引用类型,效率是一致的

在Golang(Go语言)中,函数传参的效率对于字符串(string)和字符串指针(*string)是有所不同的,尽管在某些情况下差异可能不明显,但理解其底层机制有助于做出更明智的决策。

  1. 值传递(string:在Go中,字符串是不可变的,且是通过值传递的。当你传递一个字符串到函数中时,实际上是传递了该字符串的副本(包括其底层的数据和长度)。这意味着,如果字符串较长,会有一定的内存开销和复制成本。然而,由于Go的优化(如小字符串的短字符串池),对于短字符串这种开销可能会被减少。

  2. 指针传递(*string:传递字符串指针则只传递指针值,这通常比传递整个字符串值要轻量级。指针指向的是原始字符串数据,因此在函数内部对字符串的修改(尽管Go字符串本身不可变,但可以通过指针重新赋值)会影响到原始数据(通过不安全操作或重新赋值指针指向的变量)。主要优势在于减少了内存复制,特别是在处理大型字符串时。

总结来说,对于短字符串,直接传递值(string)可能由于Go的优化而效率较高;而对于长字符串或需要避免不必要内存复制的场景,使用指针(*string)传递更为高效。选择哪种方式应根据具体的应用场景和性能需求来决定。

回到顶部