【问题标题】:How to allocate memory to map pointing to slice in golang如何分配内存以映射指向golang中的切片
【发布时间】:2019-03-07 01:56:15
【问题描述】:

有没有办法分配映射的内存,它最多有Nmax 键,指向最大长度的切片Nmax

我目前只是通过 make(map[int][]int,Nmax) 指定最大键数,但我不确定如何告诉 Go 每个切片的最大长度 Nmax 因为我不知道键会是什么先验。

我基本上有一堆具有整数人口的网站。我使用地图来跟踪有多少站点有给定的人口N。我的程序中的瓶颈似乎是 runtime.memmove,我猜这来自于不断调整地图指向的切片的大小。

【问题讨论】:

  • 你的意思是像设定长度的切片,基本上就像make(map[int][Nmax]int) 即给定长度的数组?您也可以使用make(map[int][]int, Nmax) 预先分配一定大小的地图
  • 我不知道这个特定的解决方案是否适合我,因为我需要快速计算有多少站点拥有N 人口。检查切片的长度是 O(1),检查数组中有多少个非零值是 O(n)。
  • 我在这里遇到了 X-Y 问题...为什么首先切片中会有非零值?也许不导出字段,并使用接口来获取/设置值可以让您防御这种情况。只需在设置器中进行零值检查并仅附加实际值,因此您可以依靠 foo.Len() 调用来仅返回 len(f.slice)
  • 无论如何,我发布了一个答案,我认为它解决了len 问题,并且在不使用数组(具有给定长度)的情况下减少了分配调用

标签: go optimization memory-management


【解决方案1】:

因此,鉴于您对问题的描述确实相当模糊,我将首先说明我将如何“管理”地图。为简单起见,我将把所有逻辑封装在接收器函数中,因此将地图封装在自定义类型中:

type dataMap struct {
    data map[int][]int
    nmax int
}

func New(Nmax int) *dataMap {
    return &dataMap{
        data: make(map[int][]int, Nmax),
        nmax: Nmax,
    }
}

// Get - return slice for given key
func (d dataMap) Get(k int) []int {
    s, ok := d.data[k]
    if !ok {
        return nil // optionally return error
    }
    return s
}

// Set - set/append values to a given key - this is not safe for concurrent use
// if that's needed, add a RWMutex to the type
func (d *dataMap) Set(k int, vals ...int) error {
    s, ok := d.data[k]
    if !ok {
        s = make([]int, 0, d.nmax) // allocate slice of given length
    }
    // optionally check for nil-values + ensure we're not exceeding the nmax
    checked := make([]int, 0, len(vals))
    for i := range vals {
        if vals[i] != 0 {
            checked = append(checked, vals[i])
        }
    }
    if len(s) + len(checked) > d.nmax {
        return errors.New("max capacity exceeded")
    }
    s = append(s, checked...) // append values
    d.data[k] = s // update map
    return nil
}

这减少了不必要的内存(重新)分配调用。它还确保我可以在 O(1) 操作中获得地图中任何切片的长度,而不必担心 nil 值:

myData := New(10)
fmt.Println(myData.Set(4, 1, 2, 3, 4))
fmt.Println(len(myData.Get(4))) // 4
fmt.Println(cap(myData.Get(4))) // 10
// nil-values are filtered out
myData.Set(4, 5, 6, 7, 0, 0, 0, 0)
fmt.Println(len(myData.Get(4))) // 7
fmt.Println(cap(myData.Get(4))) // 10
// exceeding capacity of 10
fmt.Println(myData.Set(4, 8, 9, 10, 11)) // max capacity exceeded

工作demo


可以通过使用数组而不是切片来管理容量,但这确实需要您手动跟踪要开始附加值的索引/偏移量。一般来说,你不会在 golang 中使用数组,以免在非常非常特殊的情况下。在这种情况下,我只会选择带有固定上限的切片。这样做的好处是您可以拥有不同长度的切片。结果也很容易测试,因为像这样的类型很适合用接口类型替换它

type DataContainer interface {
    Get(k int) []int
    Set(k int, vals ...int) error
    Declare(k, capacity int) error // error if k is already in use?
}

【讨论】:

    【解决方案2】:

    您对问题的描述含糊不清,并且您没有提供代码来说明您的问题。

    如果地图切片容量为零,则将其设置为Nmax。例如,

    package main
    
    import "fmt"
    
    func main() {
        Nmax := 42
    
        m := make(map[int][]int, Nmax)
    
        k, e := 7, 11
        v := m[k]
        if cap(v) == 0 {
            v = make([]int, 0, Nmax)
        }
        m[k] = append(v, e)
    
        v = m[k]
        fmt.Println(k, len(v), cap(v), v)
    
        fmt.Println(m)
    }
    

    游乐场:https://play.golang.org/p/csoUCUvVDAp

    输出:

    7 1 42 [11]
    map[7:[11]]
    

    【讨论】:

      猜你喜欢
      • 2021-09-01
      • 1970-01-01
      • 2017-08-11
      • 1970-01-01
      • 2016-07-07
      • 1970-01-01
      • 2021-09-15
      • 2019-07-29
      • 1970-01-01
      相关资源
      最近更新 更多