为什么这个简单的Go程序比它的Node.js对应程序慢?

huangapple go评论79阅读模式
英文:

Why this simple Go program is slower than its Node.js counterpart?

问题

我正在尝试使用Go语言实现一个具有叶子节点值的二叉树,即等同于:

data Tree a 
  = Node {left: Tree, right: Tree} 
  | Leaf {value: a}

我遇到了两个问题:1.我无法找到一种方法来创建具有多个构造函数的类型,所以我不得不将所有数据放在一个构造函数中。2.我无法使其具有多态性,所以我不得不使用interface{}(我猜这是一种"绕过"类型系统的方式?)。这是我能做到的最好的:

package main

import ("fmt")

type Tree struct {
  IsLeaf bool
  Left *Tree
  Value interface{}
  Right *Tree
}

func build(n int) *Tree {
  if (n == 0) {
    return &Tree{IsLeaf: true, Left: nil, Value: 1, Right: nil}
  } else {
    return &Tree{IsLeaf: false, Left: build(n - 1), Value: 0, Right: build(n - 1)}
  }
}

func sum(tree *Tree) int {
  if (tree.IsLeaf) {
    return tree.Value.(int)
  } else {
    return sum(tree.Left) + sum(tree.Right)
  }
}

func main() {
  fmt.Println(sum(build(23)))
}

它实现了该类型,并通过对生成的大型树进行求和测试。我已经继续在JavaScript中进行了等效的实现(包括构造函数上的冗余数据,以保持公平性):

const build = n => {
  if (n === 0) {
    return {IsLeaf: true, Value: 1, Left: null, Right: null};
  } else {
    return {IsLeaf: false, Value: 0, Left: build(n - 1), Right: build(n - 1)};
  }
}

const sum = tree => {
  if (tree.IsLeaf) {
    return tree.Value;
  } else {
    return sum(tree.Left) + sum(tree.Right);
  }
}

console.log(sum(build(23)));

我使用go build test.go编译了Go代码,并使用time ./test运行了它。我使用node test.js运行了Node.js代码。经过多次测试,Go程序平均运行时间约为2.5秒,而Node.js程序为1.0秒。

这使得Go比Node.js慢了2.5倍,这对于这个简单的程序来说是不正确的,因为Go是一种静态类型、编译语言,具有成熟的编译器,而JavaScript是一种无类型、解释型语言。

为什么我的Go程序如此慢?我是否漏掉了一些编译器标志,或者代码有问题?

英文:

I'm attempting to use Go to implement a binary tree with values on the leaf, i.e., equivalent to:

data Tree a 
  = Node {left: Tree, right: Tree} 
  | Leaf {value: a}

I had two problems: 1, I couldn't figure a way to make a type with multiple constructors, so I had to fit all data in one. 2, I couldn't make it polymorphic, so I had to use interface{} (which I guess is an "opt-out" of the type-system?). This is the best I could make:

package main

import ("fmt")

type Tree struct {
  IsLeaf bool
  Left *Tree
  Value interface{}
  Right *Tree
}

func build(n int) *Tree {
  if (n == 0) {
    return &Tree{IsLeaf: true, Left: nil, Value: 1, Right: nil}
  } else {
    return &Tree{IsLeaf: false, Left: build(n - 1), Value: 0, Right: build(n - 1)}
  }
}

func sum(tree *Tree) int {
  if (tree.IsLeaf) {
    return tree.Value.(int)
  } else {
    return sum(tree.Left) + sum(tree.Right)
  }
}

func main() {
  fmt.Println(sum(build(23)))
}

It implements the type and tests it by summing over a huge generated tree. I've proceeded to make an equivalent implementation in JavaScript (including the redundant data on constructors, for fairness):

const build = n => {
  if (n === 0) {
    return {IsLeaf: true, Value: 1, Left: null, Right: null};
  } else {
    return {IsLeaf: false, Value: 0, Left: build(n - 1), Right: build(n - 1)};
  }
}

const sum = tree => {
  if (tree.IsLeaf) {
    return tree.Value;
  } else {
    return sum(tree.Left) + sum(tree.Right);
  }
}

console.log(sum(build(23)));

I've compiled the Go code with go build test.go and ran it with time ./test. I've ran the Node.js code with node test.js. After several tests, the Go program ran in about 2.5 seconds in average, versus 1.0 seconds of the Node.js one.

That makes Go 2.5x slower than Node.js for this simple program, which can't be correct, given that Go is a statically-typed, compiled language with a mature compiler, whereas JavaScript is an untyped, interpreted one.

Why is my Go program so slow? Am I missing some compiler flag, or is the code problematic?

答案1

得分: 12

概述

这段代码由于类型断言和冗余数据而变慢。

Go 不鼓励在热点位置写类型断言:

tree.Value.(int) 

去掉这个类型断言(并相应地将 Value 改为 int 类型),你的代码将运行大约快两倍(应该接近你的节点示例的速度)。

同时去掉冗余数据,你的代码将运行大约快三倍。请参考帖子末尾的 playground 示例。

详细信息

我认为这是设计上的错误,而不是实现上的错误。根据你的问题,我认为你对 Go 的类型系统工作原理有些困惑。

Go 的对象模型不鼓励使用通用类型进行多态(参见这个优秀答案的前半部分,讨论了 Go 的多态性)。

在 JavaScript 的世界中,每个对象都是特定类型的。在 Go 中,如果一个 struct 满足了 interface 的约定,它可以被视为特定的接口类型。请注意,struct 不是对象——你所称之为构造函数只是 struct 的初始化器。

在 Go 中,可以编写操作 interface{} 的代码,将其作为所有类型的占位符,但是语言并不鼓励以这种方式编写代码(正如你在问题中指出的,以 JavaScript 的方式编写干净的代码是一种挑战)。

由于 Go 实际上没有对象,因此在 Go 中编写具有面向对象感觉的代码将是具有挑战性的(此外,Go 没有标准的继承或方法重载)。因此,我认为你的代码不是 Go 鼓励程序员编写的那种代码。所以,这不是一个公平的测试。

类型断言是慢的(我对 Go 内部设计不太了解,但这确实表明程序员不应该写很多类型断言)。因此,你的代码性能不佳并不令人意外。我将你的代码改为:

type Tree struct {
  IsLeaf bool
  Left *Tree
  Value int
  Right *Tree
} 
 ......
func sum(tree *Tree) int {
  if (tree.IsLeaf) {
    return tree.Value
  } else {
    return sum(tree.Left) + sum(tree.Right)
  }
}

在我的机器上,这样做可以提高两倍的速度。

可能还有其他优化方法——你可以删除 IsLeaf,并且不需要在非叶节点存储值(或者你可以在整个树中分布值,从而不浪费 Value)。我不知道 JavaScript 是否会优化掉这些不必要的 Value,但我不认为 Go 会这样做。

所以,我认为你的代码使用的内存比它需要的要多得多,这也不会有助于性能。

这重要吗?

我个人对“我用 X 和 Y 编写了这个程序,并发现 Y 更慢”并不太信服,特别是在跨框架进行公平比较很困难的情况下。还有很多其他的变量来源——程序员的知识、机器负载、启动时间等等。

要进行公平测试,你需要编写符合每种语言习惯的代码,但同时使用相同的代码。我认为同时实现这两点是不现实的。

如果这段代码是你特定的场景,并且性能是主要目标,那么这个测试可能有帮助。但是,否则我认为这不是一个非常有意义的比较。

在大规模情况下,我预计其他考虑因素会超过创建和遍历树的速度。有技术上的考虑,如数据吞吐量和负载下的性能,还有像程序员时间和维护工作这样的软性考虑。

不过,这个学术练习很有趣。编写这样的代码是发现框架边界的好方法。


编辑:我尝试使你的代码更符合 Go 的风格,这还有一个额外的好处,就是比原来的代码快三倍:

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

这个树对于 playground 来说有点重,但你可以复制粘贴代码在本地运行。

还有更多可能的优化,比如并行构建树。

你可能能够扩展这个设计,以实现你想要的多态行为(通过提供替代的 Leaf 实现),但我不确定非数字类型的 Sum() 是什么意思。不知道如何定义 Sum() 是一个好例子,说明了不决定通过泛型包含多态性的思考方式。

英文:

Summary

This code is slower because of the type assertion, and reduntant data.

Go doesn't encourage you to write type assertions in hot places:

tree.Value.(int) 

Take out this type assertion (and accordingly change Value to an int type), and your code will perform about twice as fast (which should be around the speed of your node example).

Take out the redundant data as well, and your code will perform about three times as fast. See the playground example at the end of the post.

Details

I think this is a mistake of design, rather than implementation. Reading your question, I think there is some confusion about how Go's type system works.

Go's object model doesn't encourage you to do polymorphism using catch-all types (see the top half of this excellent answer for a discussion of Go's polymorphism).

In a JavaScript world, each object is a specific type. In Go, a struct can be treated as a specific interface type if it fulfils the interface's contract. Note that structs are not objects - what you called constructors are just struct initialisers.

It is possible to write Go code that operates on interface{} as a placeholder for all types, but the language doesn't really encourage you to write code this way (as you pointed out in your question, it was a challenge to write clean code in the way you would write it in JavaScript).

Because Go doesn't really have objects, trying to write code that feels very object-oriented in Go will be challenging (additionally, Go doesn't have standard inheritance or method overloading). For this reason, I don't think that your code is the kind of code that Go encourages the programmer to write. So, it's not a fair test.

Type assertion is slow. (I'm not across the design of Go's internals, but certainly this indicates that the programmer is not expected to write a lot of type assertions). Because of this, it's not surprising that your code is not performant. I changed your code to:

type Tree struct {
  IsLeaf bool
  Left *Tree
  Value int
  Right *Tree
} 
 .....
func sum(tree *Tree) int {
  if (tree.IsLeaf) {
    return tree.Value
  } else {
    return sum(tree.Left) + sum(tree.Right)
  }
}

And achieved a 2x speed up on my machine.

There are probably other optimisations - you might be able to remove IsLeaf, and you don't need to store values at non-leaf nodes (or alternatively, you could distribute values throughout the tree, so never waste the Value). I don't know whether JavaScript optimises out these unnecessary Values, but I don't believe Go does.

So, I think your code is using much more memory than it needs, which won't help performance either.

Does it matter?

I'm not personally convinced by "I wrote this program in X and Y, and found that Y was slower", especially as it's hard to compare fairly across frameworks. There are so many other sources of variance - programmer knowledge, machine load, spin-up time, etc.

To do a fair test you'd need to write code that's idiomatic in each language, but also use the same code. I don't think it's realistic to achieve both.

If this code is your specific scenario, and performance is the primary goal, then this test might be helpful. But, otherwise I don't think it's a very meaningful comparison.

At scale, I would expect other considerations to beat how fast you can create and traverse a tree. There are technical concerns like data throughput and performance under load, but also softer concerns like programmer time, and maintenance effort.

The academic exercise is interesting, though. And writing code like this is a good way to find the edges of a framework.


Edit: I tried making your code more Go-like, which has the added advantage of a 3x speedup over the original.:

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

The tree is a bit heavy for the playground, but you can copy and paste the code to run locally.

There are more optimisations possible that I haven't tried, such as parallel construction of the tree.

You may be able to extend this design to have the polymorphic behaviour that you want (by providing alternative Leaf implementations), but I'm not sure what Sum() means for non-number types. Not knowing how to define Sum() is a good example of the kind of thinking that leads to not deciding to include polymorphism through generics.

答案2

得分: 4

我认为这可能是有益的。这是我实现的一个平衡二叉树,它使用递归、Go协程和通道。它被设计为一个包,所以我使用了导出和未导出的函数。导出的函数是你应该使用/修改等的函数。我很久以前写的...有很多地方可以写得更好...不过我刚刚为你添加了一个Sum函数。
我添加了23个节点,并在1/4秒内得到了它们的总和。

更新 我添加了一个名为GetTreeTotal()的新函数,如果你查看Tree结构体,我现在保留了一个Total字段。在Add()函数中,我在添加节点时更新了该字段。现在,sum()不需要进行大量计算,它只是树的元数据的一部分。所以从这个意义上说,非常快。使用类似的逻辑,树上的节点数也可以作为元数据保存。知道这些信息,可以加快诸如TreeToArray()之类的函数,因为可以预先定义切片的大小。减少内存分配等等。

更新2 这个问题让我很好奇,我重新编写了下面的代码,并将其转换为一个包。https://github.com/marcsantiago/GoTree 迭代插入的速度几乎快了3倍(包括基准测试),尽管只有在插入的数量非常大时才能看到这种差异。

package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"sync"
	"time"
)

type node struct {
	Left  *node
	Right *node
	Data  int
}

// Tree ...
type Tree struct {
	Root  *node
	Total int
}

// FindNode ...
func (t *Tree) FindNode(data int) bool {
	newNode := node{
		Data: data,
	}
	if t.Root != nil {
		if t.findNode(t.Root, newNode) != nil {
			return true
		}
	}
	return false
}

func (t *Tree) findNode(search *node, target node) *node {
	var returnNode *node
	if search == nil {
		return returnNode
	}
	if search.Data == target.Data {
		return search
	}
	returnNode = t.findNode(search.Left, target)
	if returnNode == nil {
		returnNode = t.findNode(search.Right, target)
	}
	return returnNode
}

// Add ...
func (t *Tree) Add(data int) {
	t.Total += data
	if data < 0 {
		panic(errors.New("Only submit positive integers"))
	}
	nodeToAdd := node{
		Data: data,
	}
	if t.Root == nil {
		t.Root = new(node)
	}
	if t.Root.Data == 0 {
		t.Root = &nodeToAdd
		return
	}

	t.add(t.Root, nodeToAdd)
	return
}

func (t *Tree) add(oldnode *node, newNode node) {
	if newNode.Data < oldnode.Data {
		if oldnode.Left == nil {
			// t.Total += newNode.Data
			oldnode.Left = &newNode
		} else {
			// t.Total += newNode.Data
			t.add(oldnode.Left, newNode)
		}
	} else if newNode.Data > oldnode.Data {
		if oldnode.Right == nil {
			// t.Total += newNode.Data
			oldnode.Right = &newNode
		} else {
			// t.Total += newNode.Data
			t.add(oldnode.Right, newNode)
		}
	}
	return
}

// InOrderTraversal ...
func (t *Tree) InOrderTraversal() {
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			fmt.Println(currentNode.Data)
		} else {
			t.inOrderTraversal(currentNode)
		}
	}
	return
}

func (t *Tree) inOrderTraversal(n *node) {
	if n.Left != nil {
		t.inOrderTraversal(n.Left)
	}
	fmt.Println(n.Data)
	if n.Right != nil {
		t.inOrderTraversal(n.Right)
	}
	return
}

// Traversal ...
func (t *Tree) Traversal() {
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			fmt.Println(currentNode.Data)
		} else {
			t.traversal(currentNode)
		}
	}
	return
}

func (t *Tree) traversal(n *node) {
	fmt.Println(n.Data)
	if n.Left != nil {
		t.traversal(n.Left)
	}

	if n.Right != nil {
		t.traversal(n.Right)
	}
	return
}

// Sum ...
func (t *Tree) Sum() (total int) {
	var wg sync.WaitGroup
	c := make(chan int, 100)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return 1
		}
		wg.Add(1)
		t.sum(currentNode, c, &wg)
	}
	go func() {
		wg.Wait()
		close(c)
	}()
	for n := range c {
		total += n
	}
	return total
}

func (t *Tree) sum(n *node, counter chan int, wg *sync.WaitGroup) {
	defer wg.Done()

	if n.Left != nil {
		wg.Add(1)
		go t.sum(n.Left, counter, wg)
	}

	counter <- n.Data

	if n.Right != nil {
		wg.Add(1)
		go t.sum(n.Right, counter, wg)
	}

	return
}

// CountEdges ...
func (t *Tree) CountEdges() (edges int) {
	c := make(chan int, 10)
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return 1
		}
		t.countEdges(currentNode, c)
	}

	for {
		n := <-c
		if n == 0 {
			close(c)
			break
		}
		edges++
	}
	return edges
}

func (t *Tree) countEdges(n *node, counter chan int) {
	if n.Left != nil {
		go t.countEdges(n.Left, counter)
	}

	if n.Left == nil && n.Right == nil {
		counter <- 0
	} else {
		counter <- 1
	}

	if n.Right != nil {
		go t.countEdges(n.Right, counter)
	}
	return
}

// GenerateRandomTree ...
func (t *Tree) GenerateRandomTree() {
	u := time.Now()
	source := rand.NewSource(u.Unix())
	r := rand.New(source)
	arr := r.Perm(1000)
	for _, a := range arr {
		t.Add(a)
	}
	return
}

// GetRootData ...
func (t *Tree) GetRootData() int {
	return t.Root.Data
}

// GetTreeTotal ...
func (t *Tree) GetTreeTotal() int {
	return t.Total
}

// TreeToArray ...
func (t *Tree) TreeToArray() []int {
	ch := make(chan int, 10)
	arr := []int{}
	if t.Root != nil {
		currentNode := t.Root
		if currentNode.Left == nil && currentNode.Right == nil {
			return []int{currentNode.Data}
		}
		t.traversalGetVals(currentNode, ch)
	}

	for {
		n := <-ch
		if n == -1 {
			close(ch)
			break
		}
		arr = append(arr, n)
	}
	return arr
}

func (t *Tree) traversalGetVals(n *node, ch chan int) {
	if n.Left != nil {
		ch <- n.Left.Data
		go t.traversalGetVals(n.Left, ch)
	}

	if n.Right != nil {
		ch <- n.Right.Data
		go t.traversalGetVals(n.Right, ch)
	}
	if n.Left == nil && n.Right == nil {
		ch <- -1
	}
	return
}

// ShiftRoot ...
func (t *Tree) ShiftRoot(newRoot int) {
	arr := t.TreeToArray()
	n := Tree{}
	n.Add(newRoot)
	for _, i := range arr {
		n.Add(i)
	}
	*t = n
}

// PrintTree ...
func (t *Tree) PrintTree() {
	b, err := json.MarshalIndent(t, "", " ")
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

func main() {
	// t := Tree{}
	// t.GenerateRandomTree()
	// t.PrintTree()
	// fmt.Println("total:", t.Sum())

	t := Tree{}
	t.Add(10)
	t.Add(100)
	t.Add(2)
	t.Add(3)

	fmt.Println(t.Sum()) // 应该是115
	fmt.Println(t.GetTreeTotal())

	// t := Tree{}
	// for i := 1; i <= 23; i++ {
	// 	t.Add(i)
	// }
	// fmt.Println("total:", t.Sum())

}
英文:

I thought that this might be beneficial. This is my implementation of a balanced binary tree, which uses recursion, go routines, and channels. It was meant to be used as a package, which is why i use Exported and Un-exported functions. The exported functions are what you should use/mod etc.. I wrote it a long time ago... there are plenty of things that could have been written better.. I added a Sum function just now however for you.
I added 23 nodes and got the sum in 1/4 a second..

UPDATE I've added a new function called GetTreeTotal() if you look at the Tree struct i keep a Total field now. In the Add() function I update that field as the node is being added. Now sum() doesn't have to be calculated in mass, thats just part is the Tree's meta data now.. So in that sense. super fast. Using similar logic, number of nodes on the tree can be kept as meta data as well.. Knowing that information, would speed up functions like TreeToArray() because one could then define the size of the slice before hand. Less allocations.. etc

UPDATE2 This question got me curious, I rewrote the code below and turned it into a package. https://github.com/marcsantiago/GoTree Iterative inserts are almost 3x faster (benchmarks included), though you really see this difference when the amount of inserts is really high.

package main
import (
&quot;encoding/json&quot;
&quot;errors&quot;
&quot;fmt&quot;
&quot;math/rand&quot;
&quot;sync&quot;
&quot;time&quot;
)
type node struct {
Left  *node
Right *node
Data  int
}
// Tree ...
type Tree struct {
Root  *node
Total int
}
// FindNode ...
func (t *Tree) FindNode(data int) bool {
newNode := node{
Data: data,
}
if t.Root != nil {
if t.findNode(t.Root, newNode) != nil {
return true
}
}
return false
}
func (t *Tree) findNode(search *node, target node) *node {
var returnNode *node
if search == nil {
return returnNode
}
if search.Data == target.Data {
return search
}
returnNode = t.findNode(search.Left, target)
if returnNode == nil {
returnNode = t.findNode(search.Right, target)
}
return returnNode
}
// Add ...
func (t *Tree) Add(data int) {
t.Total += data
if data &lt; 0 {
panic(errors.New(&quot;Only submit positive integers&quot;))
}
nodeToAdd := node{
Data: data,
}
if t.Root == nil {
t.Root = new(node)
}
if t.Root.Data == 0 {
t.Root = &amp;nodeToAdd
return
}
t.add(t.Root, nodeToAdd)
return
}
func (t *Tree) add(oldnode *node, newNode node) {
if newNode.Data &lt; oldnode.Data {
if oldnode.Left == nil {
// t.Total += newNode.Data
oldnode.Left = &amp;newNode
} else {
// t.Total += newNode.Data
t.add(oldnode.Left, newNode)
}
} else if newNode.Data &gt; oldnode.Data {
if oldnode.Right == nil {
// t.Total += newNode.Data
oldnode.Right = &amp;newNode
} else {
// t.Total += newNode.Data
t.add(oldnode.Right, newNode)
}
}
return
}
// InOrderTraversal ...
func (t *Tree) InOrderTraversal() {
if t.Root != nil {
currentNode := t.Root
if currentNode.Left == nil &amp;&amp; currentNode.Right == nil {
fmt.Println(currentNode.Data)
} else {
t.inOrderTraversal(currentNode)
}
}
return
}
func (t *Tree) inOrderTraversal(n *node) {
if n.Left != nil {
t.inOrderTraversal(n.Left)
}
fmt.Println(n.Data)
if n.Right != nil {
t.inOrderTraversal(n.Right)
}
return
}
// Traversal ...
func (t *Tree) Traversal() {
if t.Root != nil {
currentNode := t.Root
if currentNode.Left == nil &amp;&amp; currentNode.Right == nil {
fmt.Println(currentNode.Data)
} else {
t.traversal(currentNode)
}
}
return
}
func (t *Tree) traversal(n *node) {
fmt.Println(n.Data)
if n.Left != nil {
t.traversal(n.Left)
}
if n.Right != nil {
t.traversal(n.Right)
}
return
}
// Sum ...
func (t *Tree) Sum() (total int) {
var wg sync.WaitGroup
c := make(chan int, 100)
if t.Root != nil {
currentNode := t.Root
if currentNode.Left == nil &amp;&amp; currentNode.Right == nil {
return 1
}
wg.Add(1)
t.sum(currentNode, c, &amp;wg)
}
go func() {
wg.Wait()
close(c)
}()
for n := range c {
total += n
}
return total
}
func (t *Tree) sum(n *node, counter chan int, wg *sync.WaitGroup) {
defer wg.Done()
if n.Left != nil {
wg.Add(1)
go t.sum(n.Left, counter, wg)
}
counter &lt;- n.Data
if n.Right != nil {
wg.Add(1)
go t.sum(n.Right, counter, wg)
}
return
}
// CountEdges ...
func (t *Tree) CountEdges() (edges int) {
c := make(chan int, 10)
if t.Root != nil {
currentNode := t.Root
if currentNode.Left == nil &amp;&amp; currentNode.Right == nil {
return 1
}
t.countEdges(currentNode, c)
}
for {
n := &lt;-c
if n == 0 {
close(c)
break
}
edges++
}
return edges
}
func (t *Tree) countEdges(n *node, counter chan int) {
if n.Left != nil {
go t.countEdges(n.Left, counter)
}
if n.Left == nil &amp;&amp; n.Right == nil {
counter &lt;- 0
} else {
counter &lt;- 1
}
if n.Right != nil {
go t.countEdges(n.Right, counter)
}
return
}
// GenerateRandomTree ...
func (t *Tree) GenerateRandomTree() {
u := time.Now()
source := rand.NewSource(u.Unix())
r := rand.New(source)
arr := r.Perm(1000)
for _, a := range arr {
t.Add(a)
}
return
}
// GetRootData ...
func (t *Tree) GetRootData() int {
return t.Root.Data
}
// GetTreeTotal ...
func (t *Tree) GetTreeTotal() int {
return t.Total
}
// TreeToArray ...
func (t *Tree) TreeToArray() []int {
ch := make(chan int, 10)
arr := []int{}
if t.Root != nil {
currentNode := t.Root
if currentNode.Left == nil &amp;&amp; currentNode.Right == nil {
return []int{currentNode.Data}
}
t.traversalGetVals(currentNode, ch)
}
for {
n := &lt;-ch
if n == -1 {
close(ch)
break
}
arr = append(arr, n)
}
return arr
}
func (t *Tree) traversalGetVals(n *node, ch chan int) {
if n.Left != nil {
ch &lt;- n.Left.Data
go t.traversalGetVals(n.Left, ch)
}
if n.Right != nil {
ch &lt;- n.Right.Data
go t.traversalGetVals(n.Right, ch)
}
if n.Left == nil &amp;&amp; n.Right == nil {
ch &lt;- -1
}
return
}
// ShiftRoot ...
func (t *Tree) ShiftRoot(newRoot int) {
arr := t.TreeToArray()
n := Tree{}
n.Add(newRoot)
for _, i := range arr {
n.Add(i)
}
*t = n
}
// PrintTree ...
func (t *Tree) PrintTree() {
b, err := json.MarshalIndent(t, &quot;&quot;, &quot; &quot;)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}
func main() {
// t := Tree{}
// t.GenerateRandomTree()
// t.PrintTree()
// fmt.Println(&quot;total:&quot;, t.Sum())
t := Tree{}
t.Add(10)
t.Add(100)
t.Add(2)
t.Add(3)
fmt.Println(t.Sum()) // should be 115
fmt.Println(t.GetTreeTotal())
// t := Tree{}
// for i := 1; i &lt;= 23; i++ {
// 	t.Add(i)
// }
// fmt.Println(&quot;total:&quot;, t.Sum())
}

答案3

得分: 1

问题主要在于分段内存分配(通过递归堆栈)。这导致了大量的小内存分配,进而垃圾收集器的工作量很大。你可以通过预先分配一个包含所有节点的数组,并保持一个运行中的索引来避免这个问题:

bar.go

package bar
type Tree struct {
Left   *Tree
Value  int
Right  *Tree
IsLeaf bool
}
func build(level int, curridx *int, src *[]Tree) *Tree {
if level == 0 {
(*src)[*curridx] = Tree{Left: nil, Value: 1, Right: nil, IsLeaf: true}
*curridx++
return &(*src)[*curridx-1]
} else {
(*src)[*curridx] = Tree{Left: build(level-1, curridx, src), Value: 1, Right: build(level-1, curridx, src)}
*curridx++
return &(*src)[*curridx-1]
}
}
func sum(tree *Tree) int {
if tree.IsLeaf {
return tree.Value
} else {
return sum(tree.Left) + sum(tree.Right)
}
}

bar_test.go

package bar
import "testing"
import "math"
func TestMe(t *testing.T) {
for x := 0; x < 10; x++ {
levels := 23
nrnodes := int(math.Pow(2.0, float64(levels+1))) //there are actually 24 levels
mapping := make([]Tree, nrnodes, nrnodes)
index := 0
t.Error(sum(build(levels, &index, &mapping)))
}
}

这将加快每次迭代的速度为0.5秒。

注意这里的性能分析构建:

go test -cpuprofile cpu.out

go tool pprof cpu.out + web

英文:

The problem is mainly in the fragmented memory allocation (via the recursive stack). This causes a lot of small allocations and subsequently the garbage collector has a hefty job. You can circumvent this by pre allocate an array that holds all nodes and keep a running index for assignment:

bar.go

package bar
type Tree struct {
Left  *Tree
Value int
Right *Tree
IsLeaf bool
}
func build(level int, curridx *int, src *[]Tree) *Tree {
if level == 0 {
(*src)[*curridx] = Tree{Left: nil, Value: 1, Right: nil, IsLeaf:true}
*curridx++
return &amp;(*src)[*curridx-1]
} else {
(*src)[*curridx] = Tree{Left: build(level-1, curridx, src), Value: 1, Right: build(level-1, curridx, src)}
*curridx++
return &amp;(*src)[*curridx-1]
}
}
func sum(tree *Tree) int {
if (tree.IsLeaf) {
return tree.Value.(int)
} else {
return sum(tree.Left) + sum(tree.Right)
}
}

bar_test.go

package bar
import &quot;testing&quot;
import &quot;math&quot;
func TestMe(t *testing.T) {
for x := 0; x &lt; 10; x++ {
levels := 23
nrnodes := int(math.Pow(2.0, float64(levels+1))) //there are actually 24 levels
mapping := make([]Tree, nrnodes, nrnodes)
index := 0
t.Error(sum(build(levels, &amp;index, &amp;mapping)))
}
}

This will speed things up to 0.5 sec per iteration.

Note the build in profiling of this:

go test -cpuprofile cpu.out
and
go tool pprof cpu.out + web

huangapple
  • 本文由 发表于 2017年6月20日 09:53:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/44642570.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定