0.0
The project is in a healthy, maintained state
Protocol Buffers wire format for Ruby: declare messages, encode and decode bytes, no runtime.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies
 Project Readme

fast-protowire

The Protocol Buffers wire format for Ruby, with nothing on top of it: declare a message's fields, get encode and decode for exactly those bytes.

It is not a replacement for google-protobuf. There are no descriptors, no reflection, no JSON mapping and no generated code. It exists for libraries that emit or read a fixed, known schema and want the memory cost of doing so to be roughly the size of the encoded output, rather than a native message object and arena for every field, as google-protobuf allocates.

Installation

gem "fast-protowire"

require "fast/protowire" has no dependencies.

Quickstart

Declarations mirror the .proto text. Run this with bundle exec ruby from a project that has the gem:

require "fast/protowire"

class LabelPair < Fast::Protowire::Message
  field :name, :string, 1
  field :value, :string, 2
end

class Counter < Fast::Protowire::Message
  field :value, :double, 1
end

class Metric < Fast::Protowire::Message
  repeated :label, LabelPair, 1
  field :counter, Counter, 3
end

metric = Metric.new(label: [{ name: "method", value: "GET" }], counter: { value: 12.0 })
bytes = metric.encode
Metric.decode(bytes) == metric # => true

Fields encode in field-number order, unknown fields survive a decode/encode round trip, and the output is byte-identical to what protoc-generated code and google-protobuf produce for the same values. The tutorial walks through a full schema and proves that.

Performance

Measured against google-protobuf on the Prometheus client model, a family of 36,000 metrics with twelve labels each (Ruby 4.0.7; conditions and every table on the benchmarks page):

  • Encoding allocates one object, the output, whatever the message's size or depth; 0.1.0 allocated 647,000 for this family. Decoding allocates only the messages, containers and Strings it returns, 5x fewer than 0.1.0 and 1.3x faster.
  • Built a message at a time, the way an exposition builds series, google-protobuf leaves 504,001 native arenas and 225 MiB behind for an 11.76 MB body and spends 1.77 s of every ten builds in GC; fast-protowire leaves 16.5 MiB, no arenas, and 0.27 s.
  • google-protobuf is native, and 9 to 25x faster per operation on an existing tree or one nested Hash. This gem trades that speed for memory that is roughly the size of the output; fast-prometheus's scrape path goes further and writes series with Wire directly, with no message per series at all.

Documentation

Tutorials

How-to guides

Reference

Explanation

  • Design: the wire format and nothing else — what google-protobuf costs per message, what this gem does instead, and what it leaves out.
  • How encoding works — compiled encoders, buffers, field order, presence, and decoding.
  • Benchmarks — encode, build and decode against google-protobuf, what changed since 0.1.0, and how to reproduce them.

Development

bundle exec sus
bundle exec rubocop
BENCH_QUICK=1 bundle exec ruby benchmark/messages.rb   # encode, build and decode against google-protobuf