可以(在底层数组中)在Go中具有大起始索引的切片是否可以高效地分配内存?

时间:2014-03-10 09:22:14

标签: go allocation slice

我正在尝试使用具有非常大的起始索引的切片,例如mySlice。我不想通过总是将它作为mySlice[index - mySliceStartIndex]显式地减去起始索引,而是试图简单地定义切片,以便我可以在没有像mySlice[index]这样的算术的情况下使用它。这可以在不为所有未使用的低指数分配内存的情况下完成吗?

这样做的天真方式,分配一个切片然后重新解析它(例如mySlice = mySlice[3*1024*1024*1024:4*1024*1024*1024]显然是内存效率低下的,因为底层数组不仅需要为整个范围分配,而且仍然分配。甚至不起作用,因为之后以索引3 * 1024 * 1024 * 1024的数据现在位于索引0,而我的目标是将其保持在原始索引。

我可以分配切片(或其底层数组),使切片开头以下的索引不被分配,理想情况下甚至不是最初的吗?

1 个答案:

答案 0 :(得分:4)

如果没有实际/不分配/未分配未使用的部分,这是不可能的。 在Go中定义切片的方式是通过reflect.SliceHeader

type SliceHeader struct {
    Data uintptr
    Len  int
    Cap  int
}

它不包含起始索引字段。仅仅是对底层固定大小数组的引用。 正是这个底层数组保存了您的实际数据。切片只是该数组的“窗口”,它总是从索引0开始。 0可能位于基础数组中的任何位置。

例如,考虑following code

a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
b := a[2:8]
c := a[8:]
d := b[2:4]

这会产生如下内存布局:

fixed array: [ 0 1 2 3 4 5 6 7 8 9 ]  > [10]int at address 273785072
slice a    :   . . . . . . . . . .    > SliceHeader{Data:273785072 Len:10 Cap:10}
slice b    :       . . . . . .        > SliceHeader{Data:273785080 Len:6 Cap:8}
slice c    :                   . .    > SliceHeader{Data:273785104 Len:2 Cap:2}
slice d    :           . .            > SliceHeader{Data:273785088 Len:2 Cap:6}

Data的值只是固定数组的地址偏移量,所有四个切片共享底层存储。

a =:= $273785072
b =:= $273785080 =:= $a + sizeof(int)*2 =:= $a + 8
c =:= $273785104 =:= $a + sizeof(int)*8 =:= $a + 32
d =:= $273785088 =:= $b + sizeof(int)*2 =:= $a + sizeof(int)*4 =:= $a + 16

无论您重新切片现有切片的索引如何,新切片将始终从0索引到len(s),因为它指向的基础固定数组中的地址将其放在那里。 / p>

内存映射

如果您的数据是从磁盘上的文件加载的,则可以使用不同的选项:使用syscall.Mmap通过切片提供对数据的访问,从所需的索引开始。返回的切片现在是0的索引,它仅涵盖您指定的范围。

func mmap(fd *os.File, start, size int) ([]byte, error) {
    _, err := fd.Seek(0, 0)
    if err != nil {
        return nil, err
    }

    return syscall.Mmap(int(fd.Fd()), start, size,
        syscall.PROT_READ, syscall.MAP_SHARED)
}

完成使用后,不要忘记在返回的切片上调用syscall.Munmap