我可以进一步优化它以使其运行得更快吗?

时间:2017-10-31 17:16:22

标签: performance go complexity-theory

正如您在下面的pprof输出中看到的,我有这些嵌套的for循环,它占用了我程序的大部分时间。来源是golang,但代码解释如下:

  8.55mins    1.18hrs     20:   for k := range mapSource {
  4.41mins    1.20hrs     21:           if positions, found := mapTarget[k]; found {
         .          .     22:                   // save all matches
  1.05mins   1.05mins     23:                   for _, targetPos := range positions {
  2.25mins   2.33mins     24:                           for _, sourcePos := range mapSource[k] {
     1.28s     15.78s     25:                                   matches = append(matches, match{int32(targetPos), int32(sourcePos)})
         .          .     26:                           }
         .          .     27:                   }
         .          .     28:           }
         .          .     29:   }

目前我使用的结构是2 map[int32][]int32,targetMap和sourceMap。

对于给定的键,这些映射包含一个int数组。现在我想在两个映射中找到匹配的键,并保存数组中元素的组合。

例如:

sourceMap[1] = [3,4]
sourceMap[5] = [9,10]

targetMap[1] = [1,2,3]
targetMap[2] = [2,3]
targetMap[3] = [1,2]

唯一的共同点是1,结果将是[(3,1), (3,2), (3,3), (4,1), (4,2), (4,3)]

是否有任何可能的方法(更合适的数据结构或其他方式)可以提高程序的速度?

在我的情况下,地图可以包含1000到150000个键,而内部的数组通常很小。

编辑:并发不是一个选项,因为它已经在多个线程中同时运行了几次。

4 个答案:

答案 0 :(得分:5)

  

我可以进一步优化它以便它运行得更快吗?

     

有没有可行的方法(更合适的数据结构或   什么,这可以提高我的程序的速度?

可能。

  

XY problem询问你的问题   尝试解决而不是你的实际问题。这导致   人们都浪费了大量的时间和精力   寻求帮助,以及那些提供帮助的人。

我们甚至没有关于您的问题的最基本信息,原始输入数据的形式,内容和频率的描述,以及您想要的输出。什么原始数据应该推动基准测试?

我创建了一些虚构的原始数据,这些数据产生了一些虚构的输出和结果:

BenchmarkPeterSO-4   30    44089894 ns/op    5776666 B/op      31 allocs/op
BenchmarkIvan-4      10   152300554 ns/op   26023924 B/op    6022 allocs/op

您的算法可能很慢。

答案 1 :(得分:1)

我可能会这样做,以便我可以同时完成一些工作:

https://play.golang.org/p/JHAmPRh7jr

package main

import (
    "fmt"
    "sync"
)

var final [][]int32
var wg sync.WaitGroup
var receiver chan []int32
func main() {
    final = [][]int32{}
    mapTarget := make(map[int32][]int32)
    mapSource := make(map[int32][]int32)
    mapSource[1] = []int32{3, 4}
    mapSource[5] = []int32{9, 10}

    mapTarget[1] = []int32{1, 2, 3}
    mapTarget[2] = []int32{2, 3}
    mapTarget[3] = []int32{1, 2}
    wg = sync.WaitGroup{}
    receiver = make(chan []int32)
    go func() {
        for elem := range receiver {
            final = append(final, elem)
            wg.Done()
        }
    }()
    for k := range mapSource {
        if _, ok := mapTarget[k]; ok {
            wg.Add(1)
            go permutate(mapSource[k], mapTarget[k])
        }
    }
    wg.Wait()
    fmt.Println(final)

}

func permutate(a, b []int32) {
    for i := 0; i < len(a); i++ {
        for j := 0; j < len(b); j++ {
            wg.Add(1)
            receiver <- []int32{a[i], b[j]}
        }
    }
    wg.Done()
}

您甚至可能想知道您是否从中获益:

for k := range mapSource {
      wg.Add(1)
      go func(k int32) {
          if _, ok := mapTarget[k]; ok {
              wg.Add(1)
              go permutate(mapSource[k], mapTarget[k])
          }
          wg.Done()
      }(k)
 }

答案 2 :(得分:0)

最佳优化可能涉及首先更改源数据结构和目标数据结构,因此您不必进行尽可能多的迭代,但如果不了解您正在解决的潜在问题,则很难确定,以及如何生成地图。

然而,有一个优化应该可以让你大约2倍的提升(只是一个有根据的猜测),具体取决于确切的数字。

var sources, targets []int32

for k, srcPositions := range mapSource {
    if tgtPositions, found := mapTarget[k]; found {
        sources = append(sources, srcPositions...)
        targets = append(targets, tgtPositions...)
    }
}

matches = make([]match, len(sources) * len(targets))
i := 0
for _, s := range(sources) {
    for _, t := range(targets) {
        matches[i] = match{s, t}
        i++
    }
}

一般的想法是尽量减少必须完成的复制量,并改善内存引用的位置。我认为这是您使用此数据结构所能做的最好的事情。我的预感是,对于潜在的问题,这不是最好的数据结构,而且还有更大的收益。

答案 3 :(得分:-1)

起初我在想:

  1. 计算一批中的共同关键字,并计算最终切片大小。

  2. 制作具有第1步计算容量的切片。

  3. 逐个追加。

  4. 然后是下一个结构,但它不会生成最终结果作为数组,但所有追加工作都只是链接节点。

    type node struct {
        val    int
        parent *node
        next   *node
        child  *node
    }
    
    type tree struct {
        root  *node
        level int
    }
    
    var sourceMap map[int]*tree