Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

persist: "packed" encodings for Interval and Time #27336

Merged
merged 3 commits into from
May 29, 2024

Conversation

ParkMyCar
Copy link
Member

@ParkMyCar ParkMyCar commented May 28, 2024

This PR implements "packed" encodings for Interval and Time that will be used for writing structured data in Persist.

The packed encodings are designed to be as fast as possible and have the same sort order as the original types. They're based on discussion from #26175. I added benchmarks that measure throughput and locally I get the following results:

type encode decode
interval ~1.5 billion/second ~1.6 billion/second
time ~2.5 billion/second ~1.3 billion/second

I believe the implementations can be optimized further with SIMD, but right now that requires inline_asm! or the Nightly compiler.

Note: The code placement feels a bit weird since these impls should only be used by Persist, so I was thinking of putting the impl behind a trait like mz_persist_types::ColumnarCodec, but was curious what other folks thought.

Motivation

Progress towards https://github.com/MaterializeInc/database-issues/issues/7411

Checklist

const INTERVAL: Interval = Interval::new(1, 1, 0);
group.bench_function("encode", |b| {
b.iter(|| {
let packed = PackedInterval::from(INTERVAL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be totally off, but don't you need to black_box INTERVAL, too? Otherwise the optimizer might just constant-fold the expresion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That totally makes sense, the benchmarks also seemed too fast and the numbers look more reasonable now, thanks!

Copy link
Contributor

@bkirwi bkirwi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📈!

/// Interprets a slice of bytes as a [`PackedNaiveTime`].
///
/// Returns an error if the size of the slice is incorrect.
pub fn from_bytes(slice: &[u8]) -> Result<Self, String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be implemented a bit more directly via try_into: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b3c429d443727dbe68bc28817f9aea9d

I wasn't sure how I felt about this leaving data only partially validated, but I think it's fine to panic at read time. Hard to imagine we'd ever do anything else in production...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh nice! updated to use try_into

@ParkMyCar ParkMyCar enabled auto-merge (squash) May 29, 2024 18:29
@ParkMyCar ParkMyCar merged commit 30aa94a into MaterializeInc:main May 29, 2024
76 checks passed
ParkMyCar added a commit that referenced this pull request May 30, 2024
…e` and `MzAclType` (#27360)

This PR does a few things:

1. Introduces a `ColumnarCodec` trait that is defines how to encode a
type `T` so it can be durably persisted in an
`arrow::array::FixedSizeBinaryArray`.
2. Refactors the impls of `PackedInterval` and `PackedNaiveTime`
(introduced in #27336)
to use `ColumnarCodec`.
3. Add `PackedAclItem` and `PackedMzAclItem` which implement
`ColumnarCodec`
4. Refactor existing and add more benchmarks to measure the throughput
of encoding and decoding for all of the "packed" types vs the existing
`ProtoDatum` types.

#### Benchmarks

The existing benchmarks have been reworked a bit to include encoding
into an existing buffer and reading from a slice. Also included are
benchmarks for the same workflow but using the existing protobuf types,
since the motivation for this work is to improve throughput compared to
protobuf.

 type              | encode                 | decode
-------------------|------------------------|--------
interval/packed    | ~1,172 mil/second      | ~1,000 mil/second
interval/proto     | ~239.3 mil/second      | ~120.4 mil/second
time/packed        | ~722.1 mil/second      | ~1,038 mil/second
time/proto         | ~336.9 mil/second      | ~187.3 mil/second
acl_item/packed    | ~1,050 mil/second      | ~1,033 mil/second
acl_item/proto     | ~117.7 mil/second      | ~56.5 mil/second
mz_acl_item/packed | ~385.6 mil/second      | ~590.0 mil/second
mz_acl_item/proto  | ~58.9 mil/second       | ~28.5 mil/second

In general, the "packed" representations have a 5x - 20x higher
throughput than their protobuf alternatives. FWIW I believe the packed
representations are so much faster because protobuf encodes integers
with LEB128 and encodes fields one at a time. Whereas the packed
representations use a bit more memory but encode integers in their true
size and encodes and entire type "all at once".

### Motivation

Progress towards
https://github.com/MaterializeInc/materialize/issues/24830

### Checklist

- [ ] This PR has adequate test coverage / QA involvement has been duly
considered. ([trigger-ci for additional test/nightly
runs](https://trigger-ci.dev.materialize.com/))
- [ ] This PR has an associated up-to-date [design
doc](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/README.md),
is a design doc
([template](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/design/00000000_template.md)),
or is sufficiently small to not require a design.
  <!-- Reference the design in the description. -->
- [ ] If this PR evolves [an existing `$T ⇔ Proto$T`
mapping](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/command-and-response-binary-encoding.md)
(possibly in a backwards-incompatible way), then it is tagged with a
`T-proto` label.
- [ ] If this PR will require changes to cloud orchestration or tests,
there is a companion cloud PR to account for those changes that is
tagged with the release-blocker label
([example](MaterializeInc/cloud#5021)).
<!-- Ask in #team-cloud on Slack if you need help preparing the cloud
PR. -->
- [x] This PR includes the following [user-facing behavior
changes](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/guide-changes.md#what-changes-require-a-release-note):
  - N/a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants