go cmd/compile: unnecessary bounds check for global slices

7bsow1i6  于 6个月前  发布在  Go
关注(0)|答案(5)|浏览(62)

使用 go1.12
假设我们有一个未导出的全局变量:

var mySlice = make([]T, 4)

并且对这个切片执行的操作中没有以下情况:

  • 让对切片本身的引用从包中逃逸(意味着该变量是未导出的)
  • 修改切片头本身(例如,不给变量赋值)

例如,允许的操作有元素赋值(例如,mySlice[3] = ...)和元素寻址(例如,&mySlice[3])。
如果满足上述条件,我认为在初始大小以下的切片中的索引可以省略边界检查,因为索引是确信不会超过边界的。
这种情况的使用场景如下:

var globalMessageTypes = make([]protoimpl.MesageType, 2)

func (T) Type() protoreflect.MessageType {
    return &globalMessageTypes[0] // Desire no bounds check here
}

func (R) Type() protoreflect.MessageType {
    return &globalMessageTypes[1] // Desire no bounds check here
}

func init() {
    for i := range ... {
        globalMessageTypes[i] = ...
    }
}

我们可以使用数组来避免边界检查,但这也有其他问题(参见#30528)。

vlju58qv

vlju58qv1#

检查一个变量是否被赋值过是耗费资源的,有时几乎是不可能的。但是我们可以对this proposal中描述的最终结果这样做。#29422的另一个好处。

yquaqz18

yquaqz182#

假设你声称这是"代价高昂,有时几乎不可能",这并不有利于你的提案,该提案依赖于编译器能够进行这种类型的分析(以强制执行建议的新语言语义)。
我应该指出,在编译器中解决这个问题并不需要像#29422这样完整的提案所需的严格正确性证明。如果编译器不确定变量是否被变异,它总是可以采取安全的做法,即发出边界检查。

628mspwn

628mspwn3#

我认为在某些情况下,代码的作者可以确保全局值永远不会被修改,但编译器很难得出同样的结论。

举一个简单的例子,如果全局切片作为参数传递给许多函数,而这些函数继续将相应的参数传递给更多的其他函数。对于编译器来说,推断切片是否会被修改是非常耗费成本的。

b5buobof

b5buobof4#

我的意思是,让编译器推断出某些全局切片永远不会被修改(如果推断工作不昂贵)并不是一件坏事。然而,让代码作者标记一些永远不会被修改的值会更好。

mwkjh3gx

mwkjh3gx5#

优化传递,包括移除边界检查,是在每个函数上独立完成的。你是否尝试过将你问题的所有方面都放在同一个函数中的例子?

相关问题