go程序中函数中的神秘和过多的内存分配

时间:2014-06-20 09:57:19

标签: memory-management go profiling

我有以下代码,它使用内存的音调,这比预期的要高。
我曾经使用pprof工具,它表明函数NewEdge分配的程序超过了程序分配的所有内存的94%。

我的问题是,这段代码有什么问题,就是使用了这么多内存:

type Vertex struct {
    Id                        string              `json:"id"`         // must be unique
    Properties                map[string]string   `json:"properties"` // to be implemented soon
    verticesThisIsConnectedTo map[string][]string `json:"-"`          //id for the edges *Edge // keys are Vertex ids, each pair of vertices can be connected to each other with multiple edges
    verticesConnectedToThis   map[string][]string `json:"_"`          //id for the edges *Edge // keys are Vertex ids,
}
type Edge struct {
    id         string            `json:"-"` // for internal use, unique
    Label      string            `json:"label"`
    SourceId   string            `json:"source-id"`
    TargetId   string            `json:"terget-id"`
    Type       string            `json:"type"`
    Properties map[string]string `json:"properties"` // to be implemented soon
}
func (v *Vertex) isPartof(g *Graph) bool {
    _, b := g.Vertices[v.Id]
    return b
}
func (g *Graph) NewEdge(source, target *Vertex, label, edgeType string) (Edge, error) {
    if source.Id == target.Id {
        return Edge{}, ERROR_NO_EDGE_TO_SELF_ALLOWED
    }
    if !source.isPartof(g) || !target.isPartof(g) {
        return Edge{}, errors.New("InvalidEdge, source or target not in this graph")
    }

    e := Edge{id: <-nextId, Label: label, SourceId: source.Id, TargetId: target.Id, Type: edgeType}
    g.Edges[e.id] = &e

    source.verticesThisIsConnectedTo[target.Id] = append(source.verticesThisIsConnectedTo[target.Id], e.id)
    target.verticesConnectedToThis[source.Id] = append(target.verticesConnectedToThis[source.Id], e.id)
    return e, nil
}

通过以下呼叫进行分配:fakeGraph(Aragog, 2000, 1)其中:

func fakeGraph(g Graph, nodesCount, followratio int) error {
    var err error
    // create the vertices
    for i := 0; i < nodesCount; i++ {
            v := NewVertex("") //FH.RandStr(10))
            g.AddVertex(v)
    }
    // create some "follow edges"
    followcount := followratio * nodesCount / 100
    vkeys := []string{}
    for pk := range g.Vertices {
            vkeys = append(vkeys, pk)
    }
    for ki := range g.Vertices {
            pidx := rand.Perm(nodesCount)
            followcounter := followcount
            for j := 0; j < followcounter; j++ {
                    _, err := g.NewEdge(g.Vertices[ki], g.Vertices[vkeys[pidx[j]]], <-nextId, EDGE_TYPE_FOLLOW)
                    if err != nil {
                            followcounter++ // to compensate for references to self
                    }
            }
    }
    return err
    }

问题/谜团

我可以创建数千个Vertex并且内存使用非常合理。但是对NewEdge的调用非常耗费内存。我首先注意到代码是使用内存的音调。我使用pprof运行了-memprofile,然后使用了go tool pprof并获得了此信息:

(pprof) top10
Total: 9.9 MB
     8.9  89.9%  89.9%      8.9  89.9% main.(*Graph).NewEdge
     0.5   5.0%  95.0%      0.5   5.0% allocg
     0.5   5.0% 100.0%      0.5   5.0% fmt.Sprintf
     0.0   0.0% 100.0%      0.5   5.0% _rt0_go
     0.0   0.0% 100.0%      8.9  89.9% main.fakeGraph
     0.0   0.0% 100.0%      0.5   5.0% main.func·003
     0.0   0.0% 100.0%      8.9  89.9% main.main
     0.0   0.0% 100.0%      0.5   5.0% mcommoninit
(pprof)

非常感谢任何帮助。

1 个答案:

答案 0 :(得分:1)

@ali我认为这种记忆分析没有神秘感。 首先,如果检查结构的大小,您将看到Edge结构比Vertex结构大2倍。 (你可以通过unsafe.Sizeof()来检查结构的大小) 所以,如果你打电话给fakeGraph(Aragog,2000,1),Go会分配:

  • 2000 Vertex structs
  • 至少2000 * 20 = 40 000个边缘结构
    正如您所看到的,NewEdge()将分配至少40倍于fakeGraph()
  • 的内存

此外,每次尝试创建新边缘时,都会分配新的Edge结构 - 即使NewEdge()返回错误。

另一个因素是 - 你返回struct本身,而不是指向struct的指针。在Go中,struct是值类型,因此一旦从NewEdge()返回整个结构将被复制,它也会导致新的分配。 是的,我看到你从未使用过返回的结构,但我不确定Go编译器是否会检查调用者的上下文并跳过边缘复制