热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么这个简单的Go程序比它的Node.js对应程序慢?》经验,为你挑选了1个好方法。

我试图使用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代码go build test.go并运行它time ./test.我已经运行了Node.js代码node test.js.经过多次测试后,Go程序2.5 平均运行大约几1.0秒钟,而Node.js则运行几秒钟.

这使得2.5x这个简单程序的Go 慢于Node.js,这是不正确的,因为Go是一个带有成熟编译器的静态类型的编译语言,而Javascript是一个无类型的,解释的.

为什么我的Go程序这么慢?我错过了一些编译器标志,还是代码有问题?



1> Timothy Jone..:

摘要

由于类型断言和reduntant数据,此代码较慢.

Go不鼓励你在热门地方写类型断言:

tree.Value.(int) 

取出这种类型的断言(并相应地更改Valueint类型),并且您的代码将执行大约两倍的速度(这应该是您的节点示例的速度).

同时取出冗余数据,您的代码执行速度大约是原来的三倍.请参阅帖子末尾的游乐场示例.

细节

我认为这是设计的错误,而不是实施.阅读你的问题,我认为Go的类型系统如何运作存在一些混乱.

Go的对象模型不鼓励你使用catch-all类型进行多态性(有关Go的多态性的讨论,请参阅这个优秀答案的上半部分).

在Javascript世界中,每个对象都是特定类型.在Go中,struct如果a符合interface合同,则可以将其视为特定的接口类型.请注意,structs这不是对象 - 您所谓的构造函数只是struct初始化器.

可以编写interface{}作为所有类型的占位符进行操作的Go代码,但该语言并不真正鼓励您以这种方式编写代码(正如您在问题中指出的那样,在代码中编写干净代码是一项挑战.你会用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)
  }
}

并且在我的机器上实现了2倍的加速.

可能还有其他优化 - 您可能能够删除IsLeaf,并且您不需要在非叶节点上存储值(或者,您可以在整个树中分配值,因此永远不要浪费Value).我不知道Javascript是否优化了这些不必要Value的,但我不相信Go会这样做.

所以,我认为你的代码使用的内存比它需要的多得多,这对性能也无济于事.

有关系吗?

我个人并不相信"我在X和Y中编写了这个程序,发现Y更慢",特别是因为很难在框架之间进行公平比较.还有很多其他方差来源 - 程序员知识,机器负载,启动时间等.

要做一个公平的测试,你需要编写每种语言惯用的代码,但也要使用相同的代码.我认为实现这两者并不现实.

如果此代码是您的特定方案,并且性能是主要目标,那么此测试可能会有所帮助.但是,否则我不认为这是一个非常有意义的比较.

在规模上,我希望其他考虑因素能够超越您创建和遍历树的速度.存在技术问题,例如数据吞吐量和负载下的性能,以及程序员时间和维护工作等更软的问题.

不过,学术练习很有意思.编写这样的代码是查找框架边缘的好方法.


编辑:我尝试使你的代码更像Go,它具有比原始版本快3倍的额外优势:

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

这个树对于游乐场来说有点沉重,但您可以复制并粘贴代码以在本地运行.

我没有尝试过更多可能的优化,例如树的并行构造.

您可以扩展此设计以获得所需的多态行为(通过提供替代Leaf实现),但我不确定Sum()非数字类型的含义.不知道如何定义Sum()是一种很好的例子,这种思维导致不决定通过泛型包含多态性.


推荐阅读
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • Final关键字的含义及用法详解
    本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文介绍了包的基础知识,包是一种模块,本质上是一个文件夹,与普通文件夹的区别在于包含一个init文件。包的作用是从文件夹级别组织代码,提高代码的维护性。当代码抽取到模块中后,如果模块较多,结构仍然混乱,可以使用包来组织代码。创建包的方法是右键新建Python包,使用方式与模块一样,使用import来导入包。init文件的使用是将文件夹变成一个模块的方法,通过执行init文件来导入包。一个包中通常包含多个模块。 ... [详细]
  • 本文详细介绍了如何创建和使用VUE uni-app开发环境,包括通过HBuilderX可视化界面和通过vue-cli命令执行的方法。文章内容简单清晰,易于学习与理解。通过学习本文,读者可以深入了解VUE uni-app开发环境,并通过实践验证掌握具体的使用情况。编程笔记将为读者推送更多相关知识点的文章,欢迎关注! ... [详细]
  • 如何利用 Myflash 解析 binlog ?
    本文主要介绍了对Myflash的测试,从准备测试环境到利用Myflash解析binl ... [详细]
  • 本文介绍了在go语言中利用(*interface{})(nil)传递参数类型的原理及应用。通过分析Martini框架中的injector类型的声明,解释了values映射表的作用以及parent Injector的含义。同时,讨论了该技术在实际开发中的应用场景。 ... [详细]
  • 这篇文章给大家介绍怎么从源码启动和编译IoTSharp ,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。IoTSharp项目是 ... [详细]
  • Node.js详细安装及环境配置
    1、下载安装根据自己电脑系统及位数选择,我这里选择windows64位.msi格式安装包(官网:https:odejs.orgzh-cndownload).msi和.zip格式区别 ... [详细]
author-avatar
黄智铭铭铭铭_216
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有