惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Fortinet All Blogs
博客园 - 聂微东
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hugging Face - Blog
Hugging Face - Blog
V
Visual Studio Blog
小众软件
小众软件
有赞技术团队
有赞技术团队
雷峰网
雷峰网
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
AWS News Blog
AWS News Blog
C
Cisco Blogs
美团技术团队
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
人人都是产品经理
人人都是产品经理
宝玉的分享
宝玉的分享
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
W
WeLiveSecurity
D
DataBreaches.Net
博客园 - 司徒正美
Blog — PlanetScale
Blog — PlanetScale
IT之家
IT之家
云风的 BLOG
云风的 BLOG
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Simon Willison's Weblog
Simon Willison's Weblog
Google DeepMind News
Google DeepMind News
T
The Blog of Author Tim Ferriss
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Vercel News
Vercel News
月光博客
月光博客
T
Tailwind CSS Blog
H
Help Net Security
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Cisco Talos Blog
Cisco Talos Blog
Microsoft Security Blog
Microsoft Security Blog
V
V2EX
WordPress大学
WordPress大学
Cyberwarzone
Cyberwarzone
Recent Announcements
Recent Announcements

kmcd.dev

gRPC-Web Should Have Fixed gRPC Making Dynamic Protobuf Fast in Go Proxy, Record, and Mock gRPC APIs with FauxRPC Exploring Protocol Buffers Interactively Introducing ProtoDocs Ghost in the Shell: The Manga Behind the Anime The Hidden Cost of google.protobuf.Value Why Networking Built Its Own Data Modeling Language Zero-Friction Demos with WASM Let's Learn About BGP ConnectRPC: Where is it now? Building APIs with Contracts The Case for Greppable Code Unknown Fields in Protobuf IRC Log: Reactionary Faking protobuf data in Go Y'all are Sleeping on Mise-en-Place IRC Log: Standup 2 HTTP/2 From Scratch: Part 4 IRC Log: rm -rf /var/opt/gitlab/postgresql/data HTTP/2 From Scratch: Part 3 Building a Live BGP Map HTTP/2 From Scratch: Part 2 IRC Log: The Cloud Scale Incident Visualizing the Internet (2026) Shell Log: Namaste HTTP/2 From Scratch: Part 1 IRC Log: Standup HTTP/1.1 From Scratch WHOIS is dead, long live RDAP Months Considered Harmful Encryption vs. Compression On Creating My Own Cover Art Traceroute Tool from Scratch in Go My Favorite Interview Question From JSON to Protobuf Breaking gRPC Morse Code Can You Hack a Phone with Your Voice? Visualizing the Internet (2025) HTTP QUERY and Go I made a daily word game Protovalidate: Can Input Validation Be This Easy? Behold! The Barcode Scanner Mixing CEL and Protobuf for Fun FauxRPC and Protovalidate The Call of the Monolithic Codebase FauxRPC + Test Containers Self-Documenting Connect Services gRPC Over HTTP/3: Followup JSON to Protobuf Conversion gRPC: The Ugly Parts Working with Protobuf in 2024 Introducing FauxRPC HTTP/1.0 From Scratch Y'all are sleeping on HTTP/3 HTTP/0.9 From Scratch What version of HTTP are you using? Texans in Denmark gRPC Over HTTP/3 gRPC: The Good Parts Leaving Texas for Greener Pastures gRPC: The Bad Parts Unit Testing ConnectRPC Servers Daily Prompts Adding chart.js to Hugo Why I'm Rebranding Blog Update gRPC From Scratch: Part 3 - Protobuf Encoding Tracking the Wins Visualizing the Internet (2024) Dropping Unknown Fields in ConnectRPC RESTless: Web APIs After REST Introducing unknownconnect-go Making gRPC more approachable with ConnectRPC Inspecting Protobuf Messages Introducing protoc-gen-connect-openapi gRPC From Scratch: Part 2 - Server gRPC From Scratch: Part 1 - Client Why you should use gNMI over SNMP in 2026 The Rollercoaster of Productivity in Side Projects Lessons from a Decades-Long Project How I learned to code Economists with (virtual) Guns Visualizing the Internet (2023) softlayer-python: language bindings/CLI for a cloud company SwFTP: SFTP/FTP Server For Openstack Swift Video: Morning Copenhagen Commute Goodbye Evepraisal Visualizing the spectrum of the sun (Part 2) Visualizing the Internet (2022) Evepraisal: A price estimation tool for Eve Online Visualizing the spectrum of the sun
Benchmarking gRPC (golang)
2024-05-21 · via kmcd.dev

Hey everyone, as you know from my previous posts I’m a big fan of gRPC especially when working with Go. It’s been my go-to tool for building remote procedure calls (RPCs) for a while now. It has been extremely reliable for providing high-performance RPC systems. Originally, there was only one choice: Google’s grpc-go library. It came before HTTP/2 support landed in the Go standard library, and being from Google, the creators of gRPC, it seemed like the natural fit. But is it actually the best gRPC library for Go?

As time marches on, and with it, some concerns about grpc-go have emerged:

  • Experimental Features: Many important features are labeled experimental, meaning they could change or even disappear in future updates. This can cause compatibility issues down the road.
  • Design Quirks: Some design choices feel a bit odd. Because it doesn’t integrate well with any standard HTTP middleware or tooling then it makes for a more fragmented series of libraries that are specific to gRPC. Furthermore, widely used features are often deprecated, moved and removed, breaking compatibility for existing projects. There’s also been a few missteps around API management.
  • Separate Server Dependency: grpc-go requires a separate server instead of leveraging the standard Go HTTP server. This might not be ideal if you need to serve other functionalities alongside your gRPC endpoints, like OAuth or large file uploads.

The lineup

Because of these reasons, I decided to explore some alternatives to grpc-go and if I require similar performance, I should make sure that the alternatives are in the same ballpark of performance. This post dives into benchmarks comparing the performance of three different ways to run a gRPC server in Go:

  • grpc-go: The official library from Google. See my implementation here.
  • grpc-go-servehttp: A variant of grpc-go that allows using the standard Go HTTP server. This is useful when you want to add extra HTTP routes alongside the gRPC ones without having to manage another HTTP server instance on a different port. See my implementation here.
  • connectrpc: A third-party gRPC library that supports the Go HTTP server (and gRPC-Web and the Connect protocol). I wrote about this previously. See my implementation here.

Before diving into writing my own benchmarks, I wanted to see what exists today:

  • The gRPC authors have published their own benchmark results. In these results, it looks like grpc-go is among the top performers.
  • LesnyRumcajs/grpc_bench has a very good suite of gRPC frameworks in many different languages. The latest “official” results are here. This paints a significantly different picture where grpc-go is in the “middle of the pack”.

I wanted to reproduce what happens in grpc_bench but with Go’s CPU profiling enabled so that I can dig in and see what parts of the code are taking longer. After trying to bootstrap it to grpc_bench for a while I ended up just making my own repo with benchmark tests just for Go libraries.

Aside: Don’t worry, before I did this I contributed back a change that updates all of the relevant libraries and the version of Go being used. I guessed that this might be some of the reason for the low ConnectRPC numbers because it was using a super old version from before they moved to connectrpc.com/connect as the canonical import.

In my new benchmark repo, I used ghz to run the benchmarks. I added the ability to capture pprof profiles while running the benchmark.

Test One: The Empty Message Test

This scenario uses an empty message to gauge the overall performance of handling requests, without any of the overhead of protobuf parsing.

{
    "proto": "proto/flex.proto",
    "call": "flex.FlexService/NormalRPC",
    "total": 101000,
    "skipFirst": 1000,
    "concurrency": 50,
    "connections": 20,
    "data": {},
    "max-duration": "300s",
    "host": "0.0.0.0:6660",
    "insecure": true
}

(source)

Results

The results are measured in Requests Per Second (RPS) which means that higher is better. This was run on an Intel NUC:

  • Intel(R) Core(TM) i7-8705G CPU @ 3.10GHz
  • 8GB of DDR4 RAM

This is a pretty outdated machine so if you run these benchmarks locally, you should see better performance.

This benchmark measured the performance of handling requests with an empty message. This means no actual data is being exchanged, so it focuses on the overhead of handling gRPC requests themselves.

  • grpc-go was the clear winner, processing over 20,000 requests per second.
  • connectrpc followed closely behind at around 16,000 requests per second.
  • grpc-go (with ServeHTTP) lagged behind at roughly 14,000 requests per second.

Test Two: Something slightly more realistic

This scenario uses a larger message with most of the protobuf types to exercise all of the different code paths when marshaling and unmarshalling protobuf messages.

{
    "proto": "proto/flex.proto",
    "call": "flex.FlexService/NormalRPC",
    "total": 101000,
    "skipFirst": 1000,
    "concurrency": 50,
    "connections": 20,
    "data": {
        "msg": {
            "doubleField": 123.4567,
            "floatField": 123.4567,
            "int32Field": 1234,
            "int64Field": 1234567,
            "uint32Field": 1234,
            "uint64Field": 1234567,
            "sint32Field": 1234,
            "sint64Field": 1234567,
            "fixed32Field": 1234,
            "fixed64Field": 1234567,
            "sfixed32Field": 1234,
            "sfixed64Field": 1234567,
            "boolField": true,
            "stringField": "hello world",
            "msgField": {},
            "repeatedMsgField": [{}, {}],
            "optionalMsgField": {}
        }
    },
    "max-duration": "300s",
    "host": "0.0.0.0:6660",
    "insecure": true
}

(source)

In this scenario, I also tested with and without an alternative marshal/unmarshal implementation provided by vtprotobuf, which can further optimize performance. This was omitted from the last test because there shouldn’t really be much performance difference when you’re parsing an empty message.

Results

Now here are the results of a test that uses a relatively complex message so we can see how protobuf parsing factors into the benchmarks:

The goal of this benchmark was to introduce a larger message with various data types to better simulate real-world gRPC communication. It examines how well each library handles message marshaling and unmarshaling on top of the request processing.

  • Again, grpc-go came out on top, processing over 16,000 requests per second.
  • Even with a complex message, connectrpc stayed competitive at around 14,000 requests per second.
  • grpc-go (with ServeHTTP) stayed in last place with nearly 13,000.
  • Note that vtprotobuf does help out a decent amount. vtprotobuf is being used in a mode that has zero extra hand-written code. The plugin also allows another optimization using pools which does require code changes. I may add that setup to these benchmarks at some point.

The Takeaway

If performance is the most important aspect of your application, stick with grpc-go. But if you want to add extra HTTP endpoints, add gRPC-Web support without an extra proxy, support for HTTP/1.1 or be able to just use curl your gRPC endpoints you might want to look into ConnectRPC.

I wouldn’t recommend using grpc-go with ServeHTTP because it’s slower than ConnectRPC while offering fewer features. Maybe only if you’re really entrenched into the grpc-go ecosystem should you consider using it.

Open Questions and Next Steps

I am curious about exactly why supporting the ServeHTTP interface causes such a drastic difference in performance for grpc-go. There must be some cost to supporting the ServeHTTP interface that you don’t incur when implementing the HTTP/2 server or some kind of optimization that you can’t do in a general case that grpc-go can do. If you have any ideas about this, I’m happy to hear them.

I do have to give the standard disclaimer about benchmarks. This is an artificial benchmark that was run on my underpowered, hobbyist Intel NUC and while grpc-go performs well, the difference between it and the other methods is probably negligible for use cases that don’t require peak performance.

The approach that ConnectRPC has with using ServeHTTP with a normal http.Server provided by the standard library means that it’s likely to “just work” with http/3 when it lands in the Go standard library. This is exciting for these artificial benchmarks (and some real use cases) where creating new connections is a significant part of the overhead.

See the full benchmark source here at github.com/sudorandom/go-grpc-bench. The repo does contain CPU profile captures alongside the results which I was attempting to use to narrow down the performance differences, but I haven’t found anything interesting there yet. If I succeed at finding anything interesting, I may post another update here!