内存有效方式

时间:2019-03-14 15:01:25

标签: go testing benchmarking

我有两个用Go编写的类似程序的示例。该代码的主要目的是使用结构中的值对结构进行排序。

带有指针的示例

package main

import (
    "fmt"
    "sort"
)

type payload struct {
    data string
    value  float64
}

type container struct {
    counter int
    storage map[int]*payload
}

type payloadSlice []*payload

// Len is part of sort.Interface.
func (p payloadSlice) Len() int {
    return len(p)
}

// Swap is part of sort.Interface.
func (p payloadSlice) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

// Less is part of sort.Interface. We use count as the value to sort by
func (p payloadSlice) Less(i, j int) bool {
    return p[i].value < p[j].value
}
func main() {
    name := "special_unique_name"
    var m = map[string]container{
        name: {counter: 10, storage: map[int]*payload{
            5: {data: "epsilon", value: 55},8: {data: "theta", value: 85},4: {data: "delta", value: 48},1: {data: "alpha", value: 14},10: {data: "kappa", value: 101},
            3: {data: "gamma", value: 31},6: {data: "zeta", value: 63},2: {data: "beta", value: 26},9: {data: "iota", value: 92},7: {data: "eta", value: 79},
        }},
    }
    s := make(payloadSlice, 0, len(m[name].storage))
    for _, v := range m[name].storage {
        s = append(s, v)
    }
    sort.Sort(s)

    for _, v := range s {
        fmt.Println(name, v)
    }
}

带有值的示例

package main

import (
    "fmt"
    "sort"
)

type payload struct {
    data string
    value  float64
}

type container struct {
    counter int
    storage map[int]payload
}

type payloadSlice []payload

// Len is part of sort.Interface.
func (p payloadSlice) Len() int {
    return len(p)
}

// Swap is part of sort.Interface.
func (p payloadSlice) Swap(i, j int) {
    p[i], p[j] = p[j], p[i]
}

// Less is part of sort.Interface. We use count as the value to sort by
func (p payloadSlice) Less(i, j int) bool {
    return p[i].value < p[j].value
}
func main() {
    name := "special_unique_name"
    var m = map[string]container{
        name: {counter: 10, storage: map[int]payload{
            5: {data: "epsilon", value: 55},8: {data: "theta", value: 85},4: {data: "delta", value: 48},1: {data: "alpha", value: 14},10: {data: "kappa", value: 101},
            3: {data: "gamma", value: 31},6: {data: "zeta", value: 63},2: {data: "beta", value: 26},9: {data: "iota", value: 92},7: {data: "eta", value: 79},
        }},
    }
    s := make(payloadSlice, 0, len(m[name].storage))
    for _, v := range m[name].storage {
        s = append(s, v)
    }
    sort.Sort(s)

    for _, v := range s {
        fmt.Println(name, v)
    }
}

我想知道2分钟:

  1. 哪个示例将提高内存效率? (我想这是一种指针方式)

  2. 如何使用地图中具有不同数量结构的测试数据来衡量这些示例的性能?您能帮我建立基准吗?

我认为地图中每个结构的大小平均在1-2kB之间。

1 个答案:

答案 0 :(得分:3)

“内存高效”是一个相当宽泛的术语,在诸如Go之类的垃圾收集语言中,可能有几处非常不同的东西,它们具有独立的堆和栈:

  • 什么使用最少的内存?
  • 什么使GC压力最小?

如果要最大程度地减少应用程序的占用空间,则可能可能希望在多个范围(例如,多个函数)中使用值的任何时间使用指针。这样可以减少复制,但会增加等于指针大小(在64位系统上为8个字节)的开销。

如果要最大程度地降低GC压力,则可能仅在需要指针语义或基础值很大时才希望使用指针。指针将值强制到堆上,该值将进行垃圾回收,而值可以保留在堆栈上,而不是(当函数返回时,堆栈会被完全破坏,这是线程安全的,并且需要没有参考跟踪。

“ GC压力”的想法是,在堆上创建和销毁的东西越多,垃圾收集器要做的工作就越多,这使处理器时间与应用程序正在执行的实际工作相去甚远。每次在堆上分配时,如果没有空间容纳新值,则垃圾收集器将通过在堆上查找不再需要的值来尝试释放空间。您在堆上分配的越多,GC必须运行的频率就越高,这些运行将花费的时间越长。

对于第二个问题,您可以(并且应该!)使用benchmarking facility of the testing package来评估针对特定情况的各种方法的效果。确保使用真实的数据和操作进行测试;使用“虚拟”数据类型的微基准或基准不太可能产生任何值的数据。该软件包的文档以及通过网络搜索可以轻松找到的无数博客文章和教程,应该指导您正确地学习如何在Go中编写和使用基准。

在您的特定情况下,请记住,就这个问题而言,您的数据类型比您想象的要小:在64位系统上为24个字节,与字符串的长度无关< / em>。为什么?因为string在内部是一个结构,其中包含长度的int和指向基础字节的指针。当您尝试优化内存使用时,请记住字符串,切片(而不是数组!)和映射都是非常小的结构,其中包含指向其基础数据的指针。

最重要的是过早的优化是万恶之源。您应该为两件事编写代码:功能和可读性。当指针提供您所需的功能并使用直观时,请使用指针语义。如果您测量资源问题(CPU或内存),请然后配置您的应用程序,以找出问题的根源,确定问题的优先级并对其进行优化。

直到您测量并分析了性能问题,才没有性能问题。