分类 技术志 下的文章

后端程序员一定要看的语言大比拼:Java vs. Go vs. Rust

这是JavaGo和Rust之间的比较。这不是基准测试,更多是对可执行文件大小、内存使用率、CPU使用率、运行时要求等的比较,当然还有一个小的基准测试,可以看到每秒处理的请求数量,我将尝试对这些数字进行有意义的解读。

为了尝试尽可能公平比较,我在此比较中使用每种语言编写了一个Web服务。Web服务非常简单,它提供了三个REST服务端点(endpoint)。


Web服务提供的服务端点

这三个Web服务的代码仓库托管在github上

编译后的二进制文件尺寸

有关如何构建二进制文件的一些信息。对于Java,我使用maven-shade-pluginmvn package命令将所有内容构建到一个大的jar中。对于Go,我使用go build。最后,我使用了cargo build –release构建Rust服务的二进制文件。


每个程序的大小(以兆字节为单位)

编译后的文件大小还取决于所选的库/依赖项,因此,如果依赖项的身躯臃肿,则编译后的程序也将难以幸免。在我的特定情况下,针对我选择的特定库,以上是程序编译后的大小。

在后续的一个单独小节中,我会把这三个程序都构建并打包为docker镜像,并列出它们的大小,以显示每种语言所需的运行时开销。下面有更多详细信息。

内存使用情况

空闲状态


每个应用程序在内存空闲时的内存使用情况

什么?Go和Rust版本显示空闲时内存占用量的条形图在哪里?好了,它们在那里,只有JVM启动的程序在空闲状态时消耗160 MB以上的内存,它什么也没做。Go应用程序仅使用0.86 MB,Rust应用也仅使用了0.36 MB。这是一个巨大的差异!在这里,Java使用的内存比Go和Rust应用使用的内存高出两个数量级,只是空占着内存却什么都不做。那是巨大的资源浪费。

服务REST请求

让我们使用wrk发起访问API的请求,并观察内存和CPU使用情况,以及在我的计算机上三个版本程序的每个端点每秒处理的请求数。

wrk -t2 -c400 -d30s http://127.0.0.1:8080/hello
wrk -t2 -c400 -d30s http://127.0.0.1:8080/greeting/Jane
wrk -t2 -c400 -d30s http://127.0.0.1:8080/fibonacci/35

上面的wrk命令使用两个线程并在连接池中保持400个打开的连接,并重复调用GET端点,持续30秒。这里我仅使用两个线程,因为wrk和被测程序都在同一台计算机上运行,所以我不希望它们在可用资源(尤其是CPU)上相互竞争(太多)。

每个Web服务都经过单独测试,并且在每次运行之间都重新启动了Web服务。

以下是该程序的每个版本的三个运行中的最佳结果。

  • /hello

该端点返回Hello,World!信息。它分配字符串“ Hello,World!” 并将其序列化并以JSON格式返回。


/hello端点的CPU使用率


/hello端点的内存使用情况


/hello端点处理的每秒请求数

  • /greeting/{name}

该端点接受一个段路径参数{name},然后格式化字符串“Hello,{name}!”,序列化并以JSON格式的问候消息返回。


/greeting端点的CPU使用率


/greeting端点的内存使用情况


/greeting端点处理的每秒请求数

  • /fibonacci/{number}

该端点接受一个段路径参数{number},并返回序列化为JSON格式的斐波纳契数和输入数。

对于这个特定的端点,我选择以递归形式实现它。我毫不怀疑,迭代实现会产生更好的性能结果,并且出于生产目的,应该选择一种迭代形式,但是在生产代码中,有些情况下必须使用递归(并非专门用于计算第n个斐波那契数 )。为此,我希望该实现涉及大量CPU栈分配。


/fibonacci端点的CPU使用率


/fibonacci端点的内存使用情况


/fibonacci端点处理的每秒请求数

在Fibonacci端点测试期间,Java是唯一一个有150个请求超时的实现,如下面wrk的输出所示。


超时时间


/fibonacci端点的延迟

运行时大小

为了模拟现实世界中的云原生应用程序,并避免“它仅可以在我的机器上运行!”,我分别为这三个应用程序创建了一个docker镜像。

Docker文件的源代码包含在代码库相应程序文件夹下。

作为我使用过的Java应用程序的基础镜像,openjdk:8-jre-alpine是已知大小最小的镜像之一,但是,这附带了一些警告,这些警告可能适用于您的应用程序,也可能不适用于您的应用程序,主要是alpine镜像在处理环境变量名称方面不是posix兼容的,因此您不能在Dockerfile中使用ENV中的(点)字符(不过这没什么大不了的),另一个是alpine Linux镜像是使用musl libc而不是glibc编译的,这意味着如果您的应用程序依赖于需要glibc,它可能无法正常工作。不过,在这里,alpine镜像工作是正常的。

至于应用程序的Go版本和Rust版本,我已经对其进行了静态编译,这意味着它们不希望在运行时镜像中存在libc(glibc,musl…等),这也意味着它们不需要运行OS的基本镜像。因此,我使用了scratch docker镜像,这是一个no-op镜像,以零开销托管已编译的可执行文件。

我使用的Docker镜像的命名约定为{lang}/webservice。该应用程序的Java,Go和Rust版本的镜像大小分别为113、8.68和4.24 MB。


最终Docker镜像大小

结论


三种语言的比较

在得出任何结论之前,我想指出这三种语言之间的关系。Java和Go都是支持垃圾回收的语言,但是Java会提前编译为在JVM上运行的字节码。启动Java应用程序时,JIT编译器会被调用以通过将字节码编译为本地代码来优化字节码,以提高应用程序的性能。

Go和Rust都提前编译为本地代码,并且在运行时不会进行进一步的优化。

Java和Go都是支持垃圾收集的语言,具有STW(停止世界)的副作用。这意味着,每当垃圾收集器运行时,它将停止应用程序,进行垃圾收集,并在完成后从停止的地方恢复应用程序。大多数垃圾收集器需要停止运行,但是有些实现似乎不需要这样做。

当Java语言在90年代创建时,其最大的卖点之一是一次编写,可在任何地方运行。当时这非常好,因为市场上没有很多虚拟化解决方案。如今,大多数CPU支持虚拟化,这种虚拟化抵消了使用某种语言进行开发的诱惑(该语言承诺可以运行在任何平台上)。Docker和其他解决方案以更为低廉的代价提供虚拟化。

在整个测试中,应用程序的Java版本比Go或Rust对应版本消耗了更多的内存,在前两个测试中,Java使用的内存大约增加了8000%。这意味着对于实际应用程序,Java应用程序的运行成本会更高。

对于前两个测试,Go应用程序使用的CPU比Java少20%,同时处理比java版多出38%的请求。另一方面,Rust版本使用的CPU比Go减少了57%,而处理的请求却增加了13%。

第三次测试在设计上是占用大量CPU的资源,因此我想从中挤出CPU的每一分。Go和Rust都比Java多使用了1%的CPU。而且我认为,如果wrk不是在同一台计算机上运行,那么这三个版本都会使CPU达到100%的上限值。在内存方面,Java使用的内存比Go和Rust多2000%。Java可以处理的请求比Go多出20%,而Rust可以处理的请求比Java多出15%。

在撰写本文时,Java编程语言已经存在了将近30年,这使得在市场上寻找Java开发人员变得相对容易。另一方面,Go和Rust都是相对较新的语言,因此与Java相比,自然而然的开发人员的数量更少些。不过,Go和Rust都拥有很大的吸引力,许多开发人员正在将它们用于新项目,并且有许多使用Go和Rust的生产中正在运行的项目,因为简单地说,就资源而言,它们比Java更有效。

在编写本文的程序时,我同时学习了Go和Rust。就我而言,Go的学习曲线很短,因为它是一种相对容易掌握的语言,并且与其他语言相比语法很小。我只用了几天就用Go编写了程序。关于Go需要注意的一件事是编译速度,我不得不承认,与Java/C/C++/Rust等其他语言相比,它的速度非常快。该程序的Rust版本花了我大约一个星期的时间来完成,我不得不说,大部分时间都花在弄清borrow checker向我要什么上。Rust具有严格的所有权规则,但是一旦掌握了Rust的所有权和借用概念,编译器错误消息就会突然变得更加有意义。违反借阅检查规则时,Rust编译器对您大吼的原因是因为编译器希望在编译时证明已分配内存的寿命和所有权。这样做可以保证程序的安全性(例如:没有悬挂的指针,除非使用了不安全(unsafe)的代码逃离检查),并且在编译时确定了释放位置,从而消除了垃圾收集器的需求和运行时成本。当然,这是以学习Rust的所有权系统为代价的。

在竞争方面,我认为Go是Java(通常是JVM语言)的直接竞争对手,但不是Rust的竞争对手。另一方面,Rust是Java,Go,C和C ++的重要竞争对手。

由于他们的效率,我看到了自己将会在Go和Rust中编写更多的程序,但是很可能在Rust中编写更多的程序。两者都非常适合Web服务,CLI,系统程序(..etc)开发。但是,Rust比Go具有根本优势。它不是垃圾收集的语言,与C和C++相比,它可以安全地编写代码。例如,Go并不是特别适合用于编写OS内核,而这里又是Rust的亮点,并与C/C ++竞争,因为它们是使用OS编写的长期存在和事实上的语言。Rust与C/C++竞争的另一种方式在嵌入式世界中,我将继续进行讨论。

感谢您的阅读!

本文翻译自《Comparison between Java, Go, and Rust》


我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线了,感谢小伙伴们学习支持!

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

微博:https://weibo.com/bigwhite20xx
微信公众号:iamtonybai
博客:tonybai.com
github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

go protobuf v1败给了gogo protobuf,那v2呢?

近期的一个项目有对结构化数据进行序列化和反序列化的需求,该项目具有performance critical属性,因此我们在选择序列化库包时是要考虑包的性能的。

github上有一个有关Go序列化方法性能比较的repo:go_serialization_benchmarks,这个repo横向比较了数十种数据序列化方法的正确性、性能、内存分配等,并给出了一个结论:推荐gogo protobuf。对于这样一个粗选的结果,我们是直接笑纳的^_^。接下来就是进一步对gogo protobuf做进一步探究。

img{512x368}

一. go protobuf v1 vs. gogo protobuf

gogo protobuf是既go protobuf官方api之外的另一个go protobuf的api实现,它兼容go官方protobuf api(更准确的说是v1版本)。gogo protobuf提供了三种代码生成方式:protoc-gen-gogofast、protoc-gen-gogofaster和protoc-gen-gogoslick。究竟选择哪一个呢?这里我也写了一些benchmark来比较,并顺便将官方go protobuf api也一并加入比较了。

我们首先安装一下gogo protobuf实现的protoc的三个插件,用于生成proto文件对应的Go包源码文件:

go get github.com/gogo/protobuf/protoc-gen-gofast
go get github.com/gogo/protobuf/protoc-gen-gogofaster

go get github.com/gogo/protobuf/protoc-gen-gogoslick

安装后,我们在$GOPATH/bin下将看到这三个文件(protoc-gen-go是go protobuf官方实现的代码生成插件):

$ls -l $GOPATH/bin|grep proto
-rwxr-xr-x   1 tonybai  staff   6252344  4 24 14:43 protoc-gen-go*
-rwxr-xr-x   1 tonybai  staff   9371384  2 28 09:35 protoc-gen-gofast*
-rwxr-xr-x   1 tonybai  staff   9376152  2 28 09:40 protoc-gen-gogofaster*
-rwxr-xr-x   1 tonybai  staff   9380728  2 28 09:40 protoc-gen-gogoslick*

为了对采用不同插件生成的数据序列化和反序列化方法进行性能基准测试,我们建立了下面repo。在repo中,每一种方法生成的代码放入独立的module中:

$tree -L 2 -F
.
├── IDL/
│   └── submit.proto
├── Makefile
├── gogoprotobuf-fast/
│   ├── go.mod
│   ├── go.sum
│   ├── submit/
│   └── submit_test.go
├── gogoprotobuf-faster/
│   ├── go.mod
│   ├── go.sum
│   ├── submit/
│   └── submit_test.go
├── gogoprotobuf-slick/
│   ├── go.mod
│   ├── go.sum
│   ├── submit/
│   └── submit_test.go
└── goprotobuf/
    ├── go.mod
    ├── go.sum
    ├── submit/
    └── submit_test.go

我们的proto文件如下:

$cat IDL/submit.proto
syntax = "proto3";

option go_package = ".;submit";

package submit;

message request {
        int64 recvtime = 1;
        string uniqueid = 2;
        string token = 3;
        string phone = 4;
        string content = 5;
        string sign = 6;
        string type = 7;
        string extend = 8;
        string version = 9;
}

我们还建立了Makefile,用于简化操作:

$cat Makefile

gen-protobuf: gen-goprotobuf gen-gogoprotobuf-fast gen-gogoprotobuf-faster gen-gogoprotobuf-slick

gen-goprotobuf:
    protoc -I ./IDL submit.proto --go_out=./goprotobuf/submit

gen-gogoprotobuf-fast:
    protoc -I ./IDL submit.proto --gofast_out=./gogoprotobuf-fast/submit

gen-gogoprotobuf-faster:
    protoc -I ./IDL submit.proto --gogofaster_out=./gogoprotobuf-faster/submit

gen-gogoprotobuf-slick:
    protoc -I ./IDL submit.proto --gogoslick_out=./gogoprotobuf-slick/submit

benchmark: goprotobuf-bench gogoprotobuf-fast-bench gogoprotobuf-faster-bench  gogoprotobuf-slick-bench

goprotobuf-bench:
    cd goprotobuf && go test -bench .

gogoprotobuf-fast-bench:
    cd gogoprotobuf-fast && go test -bench .

gogoprotobuf-faster-bench:
    cd gogoprotobuf-faster && go test -bench .

gogoprotobuf-slick-bench:
    cd gogoprotobuf-slick && go test -bench .

针对每一种方法,我们建立一个benchmark test。benchmark test代码都是一样的,我们以gogoprotobuf-fast为例:

// submit_test.go

package protobufbench

import (
    "fmt"
    "os"
    "testing"

    "github.com/bigwhite/protobufbench_gogoprotofast/submit"
    "github.com/gogo/protobuf/proto"
)

var request = submit.Request{
    Recvtime: 170123456,
    Uniqueid: "a1b2c3d4e5f6g7h8i9",
    Token:    "xxxx-1111-yyyy-2222-zzzz-3333",
    Phone:    "13900010002",
    Content:  "Customizing the fields of the messages to be the fields that you actually want to use removes the need to copy between the structs you use and structs you use to serialize. gogoprotobuf also offers more serialization formats and generation of tests and even more methods.",
    Sign:     "tonybaiXZYDFDS",
    Type:     "submit",
    Extend:   "",
    Version:  "v1.0.0",
}

var requestToUnMarshal []byte

func init() {
    var err error
    requestToUnMarshal, err = proto.Marshal(&request)
    if err != nil {
        fmt.Printf("marshal err:%s\n", err)
        os.Exit(1)
    }
}

func BenchmarkMarshal(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        _, _ = proto.Marshal(&request)
    }
}
func BenchmarkUnmarshal(b *testing.B) {
    b.ReportAllocs()
    var request submit.Request
    for i := 0; i < b.N; i++ {
        _ = proto.Unmarshal(requestToUnMarshal, &request)

    }
}

func BenchmarkMarshalInParalell(b *testing.B) {
    b.ReportAllocs()

    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            _, _ = proto.Marshal(&request)
        }
    })
}
func BenchmarkUnmarshalParalell(b *testing.B) {
    b.ReportAllocs()
    var request submit.Request
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            _ = proto.Unmarshal(requestToUnMarshal, &request)
        }
    })
}

我们看到,对每种方法生成的代码,我们都会进行顺序和并行的marshal和unmarshal基准测试。

我们首先分别使用不同方式生成对应的go代码:

$make gen-protobuf
protoc -I ./IDL submit.proto --go_out=./goprotobuf/submit
protoc -I ./IDL submit.proto --gofast_out=./gogoprotobuf-fast/submit
protoc -I ./IDL submit.proto --gogofaster_out=./gogoprotobuf-faster/submit
protoc -I ./IDL submit.proto --gogoslick_out=./gogoprotobuf-slick/submit

然后运行基准测试(使用macos上的go 1.14):

$make benchmark
cd goprotobuf && go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_goproto
BenchmarkMarshal-8                  2437068           483 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                2262229           529 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8        7592120           162 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8        5306744           225 ns/op         400 B/op           7 allocs/op
PASS
ok      github.com/bigwhite/protobufbench_goproto    6.239s
cd gogoprotobuf-fast && go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_gogoprotofast
BenchmarkMarshal-8                  7186828           164 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                4706794           251 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8       15107896            83.0 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8        6258507           179 ns/op         400 B/op           7 allocs/op
PASS
ok      github.com/bigwhite/protobufbench_gogoprotofast    5.449s
cd gogoprotobuf-faster && go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_gogoprotofaster
BenchmarkMarshal-8                  7036842           166 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                4666698           256 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8       15444961            83.2 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8        6936337           202 ns/op         400 B/op           7 allocs/op
PASS
ok      github.com/bigwhite/protobufbench_gogoprotofaster    5.750s
cd gogoprotobuf-slick && go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_gogoprotoslick
BenchmarkMarshal-8                  6529311           176 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                4737463           252 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8       15700746            81.8 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8        6528390           202 ns/op         400 B/op           7 allocs/op
PASS
ok      github.com/bigwhite/protobufbench_gogoprotoslick    5.668s

在我的macpro(4核8线程)上,我们看到两点结论:

  • 官方go protobuf实现生成的代码性能的确弱于gogo protobuf生成的代码,在顺序测试中,差距还较大;

  • 针对我预置的proto文件中数据格式,gogo protobuf的三种生成方法产生的代码的性能差异并不大,选择protoc-gen-gofast生成的代码在性能上即可满足。

二. go protobuf v2

今年三月份初,Go官方发布了protobuf的新API版本,这个版本与原go protobuf并不兼容。新版API旨在使protobuf的类型系统与go类型系统充分融合,提供反射功能和自定义消息实现。那么该版本生成的序列/反序列化代码在性能上有提升吗?我们将其加入我们的benchmark。

我们先下载go protobuf v2的代码生成插件(注意:由于go protobuf v1和go protobuf v2的插件名称相同,需要先备份好原先已经安装的protoc-gen-go):

$  go get google.golang.org/protobuf/cmd/protoc-gen-go
go: found google.golang.org/protobuf/cmd/protoc-gen-go in google.golang.org/protobuf v1.21.0

然后将新安装的插件名称改为protoc-gen-gov2,这样$GOPATH/bin下的插件文件列表如下:

$ls -l $GOPATH/bin/|grep proto
-rwxr-xr-x   1 tonybai  staff   6252344  4 24 14:43 protoc-gen-go*
-rwxr-xr-x   1 tonybai  staff   9371384  2 28 09:35 protoc-gen-gofast*
-rwxr-xr-x   1 tonybai  staff   9376152  2 28 09:40 protoc-gen-gogofaster*
-rwxr-xr-x   1 tonybai  staff   9380728  2 28 09:40 protoc-gen-gogoslick*
-rwxr-xr-x   1 tonybai  staff   8716064  4 24 14:56 protoc-gen-gov2*

在Makefile中增加针对go protobuf v2的代码生成和Benchmark target:

gen-goprotobufv2:
        protoc -I ./IDL submit.proto --gov2_out=./goprotobufv2/submit

goprotobufv2-bench:
        cd goprotobufv2 && go test -bench .

由于go protobuf v2与v1版本不兼容,因此也无法与gogo protobuf兼容,我们需要修改一下go protobuf v2对应的submit_test.go,将导入的“github.com/gogo/protobuf/proto”包换为“google.golang.org/protobuf/proto”

重新生成代码:

$make gen-protobuf
protoc -I ./IDL submit.proto --go_out=./goprotobuf/submit
protoc -I ./IDL submit.proto --gov2_out=./goprotobufv2/submit
protoc -I ./IDL submit.proto --gofast_out=./gogoprotobuf-fast/submit
protoc -I ./IDL submit.proto --gogofaster_out=./gogoprotobuf-faster/submit
protoc -I ./IDL submit.proto --gogoslick_out=./gogoprotobuf-slick/submit

运行benchmark:

$make benchmark
cd goprotobuf && go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_goproto
BenchmarkMarshal-8                  2420620           485 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                2186240           538 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8        7334412           162 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8        4537429           222 ns/op         400 B/op           7 allocs/op
PASS
ok      github.com/bigwhite/protobufbench_goproto    6.052s
cd goprotobufv2 && go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_goprotov2
BenchmarkMarshal-8                  2404473           506 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                1901947           626 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8        6629139           171 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8       panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x11d4956]

goroutine 196 [running]:
google.golang.org/protobuf/internal/impl.(*messageState).protoUnwrap(0xc00007e210, 0xc000010360, 0xc00008ce01)
    /Users/tonybai/Go/pkg/mod/google.golang.org/protobuf@v1.21.0/internal/impl/message_reflect_gen.go:27 +0x26
google.golang.org/protobuf/internal/impl.(*messageState).Interface(0xc00007e210, 0xc00007e210, 0xc00012c000)
    /Users/tonybai/Go/pkg/mod/google.golang.org/protobuf@v1.21.0/internal/impl/message_reflect_gen.go:24 +0x2b
google.golang.org/protobuf/proto.UnmarshalOptions.unmarshal(0x0, 0x12acc00, 0xc000010360, 0xc00012c000, 0x177, 0x177, 0x12b23e0, 0xc00007e210, 0xc000200001, 0x0, ...)
    /Users/tonybai/Go/pkg/mod/google.golang.org/protobuf@v1.21.0/proto/decode.go:71 +0x2c5
google.golang.org/protobuf/proto.Unmarshal(0xc00012c000, 0x177, 0x177, 0x12ac180, 0xc00007e210, 0x0, 0x0)
    /Users/tonybai/Go/pkg/mod/google.golang.org/protobuf@v1.21.0/proto/decode.go:48 +0x89
github.com/bigwhite/protobufbench_goprotov2.BenchmarkUnmarshalParalell.func1(0xc0004a8000)
    /Users/tonybai/test/go/protobuf/goprotobufv2/submit_test.go:65 +0x6a
testing.(*B).RunParallel.func1(0xc0000161b0, 0xc0000161a8, 0xc0000161a0, 0xc00010c700, 0xc00004a000)
    /Users/tonybai/.bin/go1.14/src/testing/benchmark.go:763 +0x99
created by testing.(*B).RunParallel
    /Users/tonybai/.bin/go1.14/src/testing/benchmark.go:756 +0x192
exit status 2
FAIL    github.com/bigwhite/protobufbench_goprotov2    4.878s
make: *** [goprotobufv2-bench] Error 1

我们看到go protobuf v2并未完成所有benchmark test,在运行并行unmarshal测试中panic了。目前go protobuf v2官方并未在github开通issue,因此尚不知道哪里去提issue。于是回到test代码,再仔细看一下submit_test.go中 BenchmarkUnmarshalParalell的代码:

func BenchmarkUnmarshalParalell(b *testing.B) {
        b.ReportAllocs()
        var request submit.Request
        b.RunParallel(func(pb *testing.PB) {
                for pb.Next() {
                        _ = proto.Unmarshal(requestToUnMarshal, &request)
                }
        })
}

这里存在一个“问题”,那就是多goroutine会共享一个request。但在其他几个测试中同样的代码并未引发panic。我修改一下代码,将其放入for循环中:

func BenchmarkUnmarshalParalell(b *testing.B) {
        b.ReportAllocs()
        b.RunParallel(func(pb *testing.PB) {
                for pb.Next() {
                        var request submit.Request
                        _ = proto.Unmarshal(requestToUnMarshal, &request)
                }
        })
}

再运行go protobuf v2的benchmark:

$go test -bench .
goos: darwin
goarch: amd64
pkg: github.com/bigwhite/protobufbench_goprotov2
BenchmarkMarshal-8                  2348630           509 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshal-8                1913904           627 ns/op         400 B/op           7 allocs/op
BenchmarkMarshalInParalell-8        7133936           175 ns/op         384 B/op           1 allocs/op
BenchmarkUnmarshalParalell-8        4841752           232 ns/op         576 B/op           8 allocs/op
PASS
ok      github.com/bigwhite/protobufbench_goprotov2    6.355s

看来的确是这个问题。

从Benchmark结果来看,即便是与go protobuf v1相比,go protobuf v2生成的代码性能也要逊色一些,更不要说与gogo protobuf相比了。

三. 小结

从性能角度考虑,如果要使用go protobuf api,首选gogo protobuf。

如果从功能角度考虑,显然go protobuf v2在成熟稳定了以后,会成为Go语言功能上最为强大的protobuf API。

本文涉及源码可以在这里下载。


我的网课“Kubernetes实战:高可用集群搭建、配置、运维与应用”在慕课网上线了,感谢小伙伴们学习支持!

我爱发短信:企业级短信平台定制开发专家 https://tonybai.com/
smspush : 可部署在企业内部的定制化短信平台,三网覆盖,不惧大并发接入,可定制扩展; 短信内容你来定,不再受约束, 接口丰富,支持长短信,签名可选。

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻)归档仓库 – https://github.com/bigwhite/gopherdaily

我的联系方式:

微博:https://weibo.com/bigwhite20xx
微信公众号:iamtonybai
博客:tonybai.com
github: https://github.com/bigwhite

微信赞赏:
img{512x368}

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats