Skip to content

Commit c071d40

Browse files
authored
Merge pull request #43 from marshallpierce/serialize-v2-deflate
V2 + Deflate serialization support with flate2's stream wrappers
2 parents 246d6a9 + 3061676 commit c071d40

File tree

7 files changed

+294
-67
lines changed

7 files changed

+294
-67
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ bench_private = [] # for enabling nightly-only feature(test) on the main crate t
2727
[dependencies]
2828
num = "0.1"
2929
byteorder = "1.0.0"
30+
flate2 = "0.2.17"
3031
#criterion = { git = "https://github.com/japaric/criterion.rs.git", optional = true }
3132

3233
[dev-dependencies]

benches/serialization.rs

+88-37
Original file line numberDiff line numberDiff line change
@@ -9,107 +9,131 @@ use hdrsample::serialization::*;
99
use self::rand::distributions::range::Range;
1010
use self::rand::distributions::IndependentSample;
1111
use self::test::Bencher;
12-
use std::io::Cursor;
12+
use std::io::{Cursor, Write};
13+
use std::fmt::Debug;
1314

1415
#[bench]
15-
fn serialize_tiny_dense(b: &mut Bencher) {
16+
fn serialize_tiny_dense_v2(b: &mut Bencher) {
1617
// 256 + 3 * 128 = 640 counts
17-
do_serialize_bench(b, 1, 2047, 2, 1.5)
18+
do_serialize_bench(b, &mut V2Serializer::new(), 1, 2047, 2, 1.5)
1819
}
1920

2021
#[bench]
21-
fn serialize_tiny_sparse(b: &mut Bencher) {
22+
fn serialize_tiny_sparse_v2(b: &mut Bencher) {
2223
// 256 + 3 * 128 = 640 counts
23-
do_serialize_bench(b, 1, 2047, 2, 0.1)
24+
do_serialize_bench(b, &mut V2Serializer::new(), 1, 2047, 2, 0.1)
2425
}
2526

2627
#[bench]
27-
fn serialize_small_dense(b: &mut Bencher) {
28+
fn serialize_small_dense_v2(b: &mut Bencher) {
2829
// 2048 counts
29-
do_serialize_bench(b, 1, 2047, 3, 1.5)
30+
do_serialize_bench(b, &mut V2Serializer::new(), 1, 2047, 3, 1.5)
3031
}
3132

3233
#[bench]
33-
fn serialize_small_sparse(b: &mut Bencher) {
34+
fn serialize_small_sparse_v2(b: &mut Bencher) {
3435
// 2048 counts
35-
do_serialize_bench(b, 1, 2047, 3, 0.1)
36+
do_serialize_bench(b, &mut V2Serializer::new(), 1, 2047, 3, 0.1)
3637
}
3738

3839
#[bench]
39-
fn serialize_medium_dense(b: &mut Bencher) {
40+
fn serialize_medium_dense_v2(b: &mut Bencher) {
4041
// 56320 counts
41-
do_serialize_bench(b, 1, u64::max_value(), 3, 1.5)
42+
do_serialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 3, 1.5)
4243
}
4344

4445
#[bench]
45-
fn serialize_medium_sparse(b: &mut Bencher) {
46+
fn serialize_medium_sparse_v2(b: &mut Bencher) {
4647
// 56320 counts
47-
do_serialize_bench(b, 1, u64::max_value(), 3, 0.1)
48+
do_serialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 3, 0.1)
4849
}
4950

5051
#[bench]
51-
fn serialize_large_dense(b: &mut Bencher) {
52+
fn serialize_large_dense_v2(b: &mut Bencher) {
5253
// 6291456 buckets
53-
do_serialize_bench(b, 1, u64::max_value(), 5, 1.5)
54+
do_serialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 5, 1.5)
5455
}
5556

5657
#[bench]
57-
fn serialize_large_sparse(b: &mut Bencher) {
58+
fn serialize_large_sparse_v2(b: &mut Bencher) {
5859
// 6291456 buckets
59-
do_serialize_bench(b, 1, u64::max_value(), 5, 0.1)
60+
do_serialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 5, 0.1)
6061
}
6162

6263
#[bench]
63-
fn deserialize_tiny_dense(b: &mut Bencher) {
64+
fn serialize_large_dense_v2_deflate(b: &mut Bencher) {
65+
// 6291456 buckets
66+
do_serialize_bench(b, &mut V2DeflateSerializer::new(), 1, u64::max_value(), 5, 1.5)
67+
}
68+
69+
#[bench]
70+
fn serialize_large_sparse_v2_deflate(b: &mut Bencher) {
71+
// 6291456 buckets
72+
do_serialize_bench(b, &mut V2DeflateSerializer::new(), 1, u64::max_value(), 5, 0.1)
73+
}
74+
75+
#[bench]
76+
fn deserialize_tiny_dense_v2(b: &mut Bencher) {
6477
// 256 + 3 * 128 = 640 counts
65-
do_deserialize_bench(b, 1, 2047, 2, 1.5)
78+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, 2047, 2, 1.5)
6679
}
6780

6881
#[bench]
69-
fn deserialize_tiny_sparse(b: &mut Bencher) {
82+
fn deserialize_tiny_sparse_v2(b: &mut Bencher) {
7083
// 256 + 3 * 128 = 640 counts
71-
do_deserialize_bench(b, 1, 2047, 2, 0.1)
84+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, 2047, 2, 0.1)
7285
}
7386

7487
#[bench]
75-
fn deserialize_small_dense(b: &mut Bencher) {
88+
fn deserialize_small_dense_v2(b: &mut Bencher) {
7689
// 2048 counts
77-
do_deserialize_bench(b, 1, 2047, 3, 1.5)
90+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, 2047, 3, 1.5)
7891
}
7992

8093
#[bench]
81-
fn deserialize_small_sparse(b: &mut Bencher) {
94+
fn deserialize_small_sparse_v2(b: &mut Bencher) {
8295
// 2048 counts
83-
do_deserialize_bench(b, 1, 2047, 3, 0.1)
96+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, 2047, 3, 0.1)
8497
}
8598

8699
#[bench]
87-
fn deserialize_medium_dense(b: &mut Bencher) {
100+
fn deserialize_medium_dense_v2(b: &mut Bencher) {
88101
// 56320 counts
89-
do_deserialize_bench(b, 1, u64::max_value(), 3, 1.5)
102+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 3, 1.5)
90103
}
91104

92105
#[bench]
93-
fn deserialize_medium_sparse(b: &mut Bencher) {
106+
fn deserialize_medium_sparse_v2(b: &mut Bencher) {
94107
// 56320 counts
95-
do_deserialize_bench(b, 1, u64::max_value(), 3, 0.1)
108+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 3, 0.1)
109+
}
110+
111+
#[bench]
112+
fn deserialize_large_dense_v2(b: &mut Bencher) {
113+
// 6291456 buckets
114+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 5, 1.5)
96115
}
97116

98117
#[bench]
99-
fn deserialize_large_dense(b: &mut Bencher) {
118+
fn deserialize_large_sparse_v2(b: &mut Bencher) {
100119
// 6291456 buckets
101-
do_deserialize_bench(b, 1, u64::max_value(), 5, 1.5)
120+
do_deserialize_bench(b, &mut V2Serializer::new(), 1, u64::max_value(), 5, 0.1)
102121
}
103122

104123
#[bench]
105-
fn deserialize_large_sparse(b: &mut Bencher) {
124+
fn deserialize_large_dense_v2_deflate(b: &mut Bencher) {
106125
// 6291456 buckets
107-
do_deserialize_bench(b, 1, u64::max_value(), 5, 0.1)
126+
do_deserialize_bench(b, &mut V2DeflateSerializer::new(), 1, u64::max_value(), 5, 1.5)
108127
}
109128

129+
#[bench]
130+
fn deserialize_large_sparse_v2_deflate(b: &mut Bencher) {
131+
// 6291456 buckets
132+
do_deserialize_bench(b, &mut V2DeflateSerializer::new(), 1, u64::max_value(), 5, 0.1)
133+
}
110134

111-
fn do_serialize_bench(b: &mut Bencher, low: u64, high: u64, digits: u8, fraction_of_counts_len: f64) {
112-
let mut s = V2Serializer::new();
135+
fn do_serialize_bench<S>(b: &mut Bencher, s: &mut S, low: u64, high: u64, digits: u8, fraction_of_counts_len: f64)
136+
where S: TestOnlyHypotheticalSerializerInterface {
113137
let mut h = Histogram::<u64>::new_with_bounds(low, high, digits).unwrap();
114138
let random_counts = (fraction_of_counts_len * h.len() as f64) as usize;
115139
let mut vec = Vec::with_capacity(random_counts);
@@ -128,8 +152,8 @@ fn do_serialize_bench(b: &mut Bencher, low: u64, high: u64, digits: u8, fraction
128152
});
129153
}
130154

131-
fn do_deserialize_bench(b: &mut Bencher, low: u64, high: u64, digits: u8, fraction_of_counts_len: f64) {
132-
let mut s = V2Serializer::new();
155+
fn do_deserialize_bench<S>(b: &mut Bencher, s: &mut S, low: u64, high: u64, digits: u8, fraction_of_counts_len: f64)
156+
where S: TestOnlyHypotheticalSerializerInterface {
133157
let mut h = Histogram::<u64>::new_with_bounds(low, high, digits).unwrap();
134158
let random_counts = (fraction_of_counts_len * h.len() as f64) as usize;
135159
let mut vec = Vec::with_capacity(random_counts);
@@ -149,3 +173,30 @@ fn do_deserialize_bench(b: &mut Bencher, low: u64, high: u64, digits: u8, fracti
149173
let _: Histogram<u64> = d.deserialize(&mut cursor).unwrap();
150174
});
151175
}
176+
177+
// Maybe someday there will be an obvious right answer for what serialization should look like, at
178+
// least to the user, but for now we'll only take an easily reversible step towards that. There are
179+
// still several ways the serializer interfaces could change to achieve better performance, so
180+
// committing to anything right now would be premature.
181+
trait TestOnlyHypotheticalSerializerInterface {
182+
type SerializeError: Debug;
183+
184+
fn serialize<T: Counter, W: Write>(&mut self, h: &Histogram<T>, writer: &mut W)
185+
-> Result<usize, Self::SerializeError>;
186+
}
187+
188+
impl TestOnlyHypotheticalSerializerInterface for V2Serializer {
189+
type SerializeError = V2SerializeError;
190+
191+
fn serialize<T: Counter, W: Write>(&mut self, h: &Histogram<T>, writer: &mut W) -> Result<usize, Self::SerializeError> {
192+
self.serialize(h, writer)
193+
}
194+
}
195+
196+
impl TestOnlyHypotheticalSerializerInterface for V2DeflateSerializer {
197+
type SerializeError = V2DeflateSerializeError;
198+
199+
fn serialize<T: Counter, W: Write>(&mut self, h: &Histogram<T>, writer: &mut W) -> Result<usize, Self::SerializeError> {
200+
self.serialize(h, writer)
201+
}
202+
}

src/serialization/deserializer.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use super::V2_COOKIE;
1+
use super::{V2_COOKIE, V2_COMPRESSED_COOKIE};
22
use super::super::{Counter, Histogram, RestatState};
33
use super::super::num::ToPrimitive;
44
use std::io::{self, Cursor, ErrorKind, Read};
55
use std::marker::PhantomData;
66
use std;
77
use super::byteorder::{BigEndian, ReadBytesExt};
8+
use super::flate2::read::DeflateDecoder;
89

910
/// Errors that can happen during deserialization.
1011
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@@ -57,10 +58,29 @@ impl Deserializer {
5758
-> Result<Histogram<T>, DeserializeError> {
5859
let cookie = reader.read_u32::<BigEndian>()?;
5960

60-
if cookie != V2_COOKIE {
61+
return match cookie {
62+
V2_COOKIE => self.deser_v2(reader),
63+
V2_COMPRESSED_COOKIE => self.deser_v2_compressed(reader),
64+
_ => Err(DeserializeError::InvalidCookie)
65+
}
66+
}
67+
68+
fn deser_v2_compressed<T: Counter, R: Read>(&mut self, reader: &mut R) -> Result<Histogram<T>, DeserializeError> {
69+
let payload_len = reader.read_u32::<BigEndian>()?.to_usize()
70+
.ok_or(DeserializeError::UsizeTypeTooSmall)?;
71+
72+
// TODO reuse deflate buf, or switch to lower-level flate2::Decompress
73+
let mut deflate_reader = DeflateDecoder::new(reader.take(payload_len as u64));
74+
let inner_cookie = deflate_reader.read_u32::<BigEndian>()?;
75+
if inner_cookie != V2_COOKIE {
6176
return Err(DeserializeError::InvalidCookie);
6277
}
6378

79+
self.deser_v2(&mut deflate_reader)
80+
}
81+
82+
83+
fn deser_v2<T: Counter, R: Read>(&mut self, reader: &mut R) -> Result<Histogram<T>, DeserializeError> {
6484
let payload_len = reader.read_u32::<BigEndian>()?.to_usize()
6585
.ok_or(DeserializeError::UsizeTypeTooSmall)?;
6686
let normalizing_offset = reader.read_u32::<BigEndian>()?;

src/serialization/serialization.rs

+28-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
//! # Serialization/deserialization
22
//!
33
//! The upstream Java project has established several different types of serialization. We have
4-
//! currently implemented one (the "V2" format, following the names used by the Java
5-
//! implementation), and will add others as time goes on. These formats are compact binary
6-
//! representations of the state of the histogram. They are intended to be used
7-
//! for archival or transmission to other systems for further analysis. A typical use case would be
8-
//! to periodically serialize a histogram, save it somewhere, and reset the histogram.
4+
//! currently implemented V2 and V2 + DEFLATE (following the names used by the Java implementation).
5+
//!
6+
//! These formats are compact binary representations of the state of the histogram. They are
7+
//! intended to be used for archival or transmission to other systems for further analysis. A
8+
//! typical use case would be to periodically serialize a histogram, save it somewhere, and reset
9+
//! the histogram.
910
//!
1011
//! Histograms are designed to be added, subtracted, and otherwise manipulated, and an efficient
1112
//! storage format facilitates this. As an example, you might be capturing histograms once a minute
@@ -18,22 +19,27 @@
1819
//!
1920
//! # Performance concerns
2021
//!
21-
//! Serialization is quite fast; serializing a histogram that represents 1 to `u64::max_value()`
22-
//! with 3 digits of precision with tens of thousands of recorded counts takes about 40
23-
//! microseconds on an E5-1650v3 Xeon. Deserialization is about 3x slower, but that will improve as
24-
//! there are still some optimizations to perform.
22+
//! Serialization is quite fast; serializing a histogram in V2 format that represents 1 to
23+
//! `u64::max_value()` with 3 digits of precision with tens of thousands of recorded counts takes
24+
//! about 40 microseconds on an E5-1650v3 Xeon. Deserialization is about 3x slower, but that will
25+
//! improve as there are still some optimizations to perform.
2526
//!
2627
//! For the V2 format, the space used for a histogram will depend mainly on precision since higher
2728
//! precision will reduce the extent to which different values are grouped into the same bucket.
2829
//! Having a large value range (e.g. 1 to `u64::max_value()`) will not directly impact the size if
2930
//! there are many zero counts as zeros are compressed away.
3031
//!
32+
//! V2 + DEFLATE is significantly slower to serialize (around 10x) but only a little bit slower to
33+
//! deserialize (less than 2x). YMMV depending on the compressibility of your histogram data, the
34+
//! speed of the underlying storage medium, etc. Naturally, you can always compress at a later time:
35+
//! there's no reason why you couldn't serialize as V2 and then later re-serialize it as V2 +
36+
//! DEFLATE on another system (perhaps as a batch job) for better archival storage density.
37+
//!
3138
//! # API
3239
//!
3340
//! Each serialization format has its own serializer struct, but since each format is reliably
3441
//! distinguishable from each other, there is only one `Deserializer` struct that will work for
35-
//! any of the formats this library implements. For now there is only one serializer
36-
//! (`V2Serializer`) but more will be added.
42+
//! any of the formats this library implements.
3743
//!
3844
//! Serializers and deserializers are intended to be re-used for many histograms. You can use them
3945
//! for one histogram and throw them away; it will just be less efficient as the cost of their
@@ -84,9 +90,11 @@
8490
//!
8591
//! impl Serialize for V2HistogramWrapper {
8692
//! fn serialize<S: Serializer>(&self, serializer: S) -> Result<(), ()> {
87-
//! // not optimal to not re-use the vec and serializer, but it'll work
93+
//! // Not optimal to not re-use the vec and serializer, but it'll work
8894
//! let mut vec = Vec::new();
89-
//! // map errors as appropriate for your use case
95+
//! // Pick the serialization format you want to use. Here, we use plain V2, but V2 +
96+
//! // DEFLATE is also available.
97+
//! // Map errors as appropriate for your use case.
9098
//! V2Serializer::new().serialize(&self.histogram, &mut vec)
9199
//! .map_err(|_| ())?;
92100
//! serializer.serialize_bytes(&vec)?;
@@ -163,6 +171,7 @@
163171
//!
164172
165173
extern crate byteorder;
174+
extern crate flate2;
166175

167176
#[path = "tests.rs"]
168177
#[cfg(test)]
@@ -176,13 +185,19 @@ mod benchmarks;
176185
mod v2_serializer;
177186
pub use self::v2_serializer::{V2Serializer, V2SerializeError};
178187

188+
#[path = "v2_deflate_serializer.rs"]
189+
mod v2_deflate_serializer;
190+
pub use self::v2_deflate_serializer::{V2DeflateSerializer, V2DeflateSerializeError};
191+
179192
#[path = "deserializer.rs"]
180193
mod deserializer;
181194
pub use self::deserializer::{Deserializer, DeserializeError};
182195

183196
const V2_COOKIE_BASE: u32 = 0x1c849303;
197+
const V2_COMPRESSED_COOKIE_BASE: u32 = 0x1c849304;
184198

185199
const V2_COOKIE: u32 = V2_COOKIE_BASE | 0x10;
200+
const V2_COMPRESSED_COOKIE: u32 = V2_COMPRESSED_COOKIE_BASE | 0x10;
186201

187202
const V2_HEADER_SIZE: usize = 40;
188203

0 commit comments

Comments
 (0)