for与for range

传统的for和for range是golang唯二提供的能够遍历的循环结构,但是for range并不是简单的for语法糖,实际上他们之间仍然有很大的不同

for range更新数组未成功?

	var a = [3]int{1, 2, 3}
	var r [3]int

	for i, v := range a {
		if i == 0 {
			a[1] = 4
		}
		r[i] = v
    // 1,2,3
    t.Log(r[i])
	}

	// 1,2,3
	t.Log(r)
	// 1,4,3
	t.Log(a)

以上代码输出的r并非期望的[1,4,3],而是一个[1,2,3]。那么这是不是意味着没有更新成功呢?

再来看另一段代码

	a := []int{1, 2, 3}
	for i, v := range a {
		if i == 0 {
			a[1] = 4
		}
    // 输出1,4,3
		t.Log(v)
	}
	// 1,4,3
	t.Log(a)

与前段代码不同的在于数组a在遍历过程中的值发生了改变

那么这是怎么回事呢?

首先,让我们看看两段代码的不同之处,除了第一段多用一个数组进行储存遍历过程中的元素之外,还有就是第一段是数组而非slice,让我们改成slice试试看

	var a = []int{1, 2, 3}
	r := make([]int, 3)

	for i, v := range a {
		if i == 0 {
			a[1] = 4
		}
		r[i] = v
    // 1, 4, 3
		t.Log(r[i])
	}
	
	// 1, 4, 3
	t.Log(r)
  // 1, 4, 3
	t.Log(a)

结果居然和第二段代码一致,无论是打印的,还是r、a都是1、4、3,也就是值确实是发生了更新

显然我们可以得出一个结论——那就是for range对于数组和slice更新的结果并不完全一致。那这是for range针对两种不同的数据结构作出不同的处理了呢?

或许可以回到slice和数组的区别上来。我们知道数组是固定的,最基本的结构,而slice是动态的,由一个slice header表示。

而for range在go tour中就说的很清楚了,是元素的复制,那么对于数组来说,数组复制就是复制全部,而slice复制仅仅只会slice header结构,那么对slice的遍历更改还是会直接影响到slice,但是array却并不会,只会在遍历结束后,更新到原数组

那么基于此,一个猜想提出了,会不会for range这种复制的形势会更加消耗资源呢?

for range会更加消耗资源吗?

以下测试在macOS下,cpu为Intel® Core™ i5-1038NG7 CPU @ 2.00GHz,数据量为100 000,go版本为1.18

for for range
int 31830 36660
1 int结构体 34304 38568
2 int结构体 35278 183267
3 int结构体 35219 200697
4 int结构体 34232 322685
5 int结构体 34219 620547

从表中可以看出在int情况下for range相比于for性能慢15%

而对于结构体,差的就更远了,在5 int结构体情况下,for range相比于for性能慢1700%,这是相当大的差距,显然这提醒我们在结构体复杂的大数据量遍历还是尽量不用for range

不出意料的是for range确实比普通的for性能消耗更多,但是值得注意的是在结构体的情况下,for range的性能消耗以一个惊人的速度上升,这是怎么回事呢?

为什么结构体对for range性能影响这么大?

从https://github.com/golang/go/issues/24416来看,似乎是因为大结构体无法被ssa优化,并且这个优化在go1.18看起来仍未完成

SSA(Single-assignment form)是一种中间表示形式属性,其要求每个变量只被赋值一次,并在使用之前被定义。

Ref

  1. https://go.dev/tour/moretypes/16
  2. https://en.wikipedia.org/wiki/Static_single-assignment_form

你可能感兴趣的:(lang,golang)