Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions benches/bench.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
use columnar::{Clear, Columnar};
use columnar::bytes::{EncodeDecode, Sequence};
use columnar::bytes::indexed;

fn empty_copy(bencher: &mut Bencher) { _bench_copy(bencher, vec![(); 1024]); }
fn option_copy(bencher: &mut Bencher) { _bench_copy(bencher, vec![Option::<String>::None; 1024]); }
Expand Down Expand Up @@ -61,7 +61,7 @@ fn _bench_copy<T: Columnar+Eq>(bencher: &mut Bencher, record: T) where T::Contai
arena.push(&record);
}
use columnar::Borrow;
bencher.bytes = Sequence::length_in_bytes(&arena.borrow()) as u64;
bencher.bytes = indexed::length_in_bytes(&arena.borrow()) as u64;
arena.clear();

bencher.iter(|| {
Expand All @@ -83,7 +83,7 @@ fn _bench_extend<T: Columnar+Eq>(bencher: &mut Bencher, record: T) where T::Cont
arena.push(&record);
}
use columnar::{Borrow, Container};
bencher.bytes = Sequence::length_in_bytes(&arena.borrow()) as u64;
bencher.bytes = indexed::length_in_bytes(&arena.borrow()) as u64;

let arena2 = arena.clone();

Expand Down
12 changes: 6 additions & 6 deletions benches/serde.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use bencher::{benchmark_group, benchmark_main, Bencher};
use columnar::{Columnar, Container, Clear, FromBytes};
use columnar::bytes::{EncodeDecode, Sequence};
use columnar::bytes::indexed;
use serde::{Serialize, Deserialize};

fn goser_new(b: &mut Bencher) {
Expand All @@ -19,7 +19,7 @@ fn goser_push(b: &mut Bencher) {
container.push(&log);
}
let mut words = vec![];
Sequence::encode(&mut words, &container.borrow());
indexed::encode(&mut words, &container.borrow());
b.bytes = 8 * words.len() as u64;
b.iter(|| {
container.clear();
Expand Down Expand Up @@ -50,11 +50,11 @@ fn goser_encode(b: &mut Bencher) {
container.push(&log);
}
let mut words = vec![];
Sequence::encode(&mut words, &container.borrow());
indexed::encode(&mut words, &container.borrow());
b.bytes = 8 * words.len() as u64;
b.iter(|| {
words.clear();
Sequence::encode(&mut words, &container.borrow());
indexed::encode(&mut words, &container.borrow());
bencher::black_box(&words);
});
}
Expand All @@ -67,10 +67,10 @@ fn goser_decode(b: &mut Bencher) {
for _ in 0..1024 {
container.push(&log);
}
Sequence::encode(&mut words, &container.borrow());
indexed::encode(&mut words, &container.borrow());
b.bytes = 8 * words.len() as u64;
b.iter(|| {
let mut slices = Sequence::decode(&mut words);
let mut slices = indexed::decode(&mut words);
let foo = <<Log as Columnar>::Container as Container>::Borrowed::from_bytes(&mut slices);
bencher::black_box(foo);
});
Expand Down
20 changes: 20 additions & 0 deletions columnar_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,10 @@ fn derive_struct(name: &syn::Ident, generics: &syn::Generics, data_struct: syn::
)*
Self { #(#names,)* }
}
#[inline(always)]
fn from_u64s(words: &mut impl Iterator<Item=(&'columnar [u64], u8)>) -> Self {
Self { #(#names: ::columnar::FromBytes::from_u64s(words),)* }
}
}
}
};
Expand Down Expand Up @@ -519,6 +523,11 @@ fn derive_unit_struct(name: &syn::Ident, _generics: &syn::Generics, vis: syn::Vi
fn from_byte_slices(bytes: &[&'columnar [u8]]) -> Self {
Self { count: &::columnar::bytemuck::try_cast_slice(bytes[0]).unwrap()[0] }
}
#[inline(always)]
fn from_u64s(words: &mut impl Iterator<Item=(&'columnar [u64], u8)>) -> Self {
let (w, _tail) = words.next().expect("Iterator exhausted prematurely");
Self { count: &w[0] }
}
}

impl ::columnar::Columnar for #name {
Expand Down Expand Up @@ -910,6 +919,13 @@ fn derive_enum(name: &syn::Ident, generics: &syn:: Generics, data_enum: syn::Dat
let indexes = <::columnar::Discriminant<CVar, COff, CC>>::from_byte_slices(&bytes[_offset ..]);
Self { #(#names,)* indexes }
}
#[inline(always)]
fn from_u64s(words: &mut impl Iterator<Item=(&'columnar [u64], u8)>) -> Self {
Self {
#(#names: ::columnar::FromBytes::from_u64s(words),)*
indexes: ::columnar::FromBytes::from_u64s(words),
}
}
}
}
};
Expand Down Expand Up @@ -1203,6 +1219,10 @@ fn derive_tags(name: &syn::Ident, _generics: &syn:: Generics, data_enum: syn::Da
fn from_byte_slices(bytes: &[&'columnar [u8]]) -> Self {
Self { variant: CVar::from_byte_slices(bytes) }
}
#[inline(always)]
fn from_u64s(words: &mut impl Iterator<Item=(&'columnar [u64], u8)>) -> Self {
Self { variant: ::columnar::FromBytes::from_u64s(words) }
}
}

impl ::columnar::Columnar for #name {
Expand Down
99 changes: 99 additions & 0 deletions examples/decode_asm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Assembly inspection for decode paths.
//!
//! Compares three approaches to accessing a single field of a k-tuple
//! stored in Indexed-encoded `&[u64]` data:
//!
//! 1. `from_bytes` + `decode`: constructs all k fields, O(k)
//! 2. `from_u64s` + `decode_u64s`: non-panicking, LLVM eliminates unused fields, O(1) in k
//! 3. `decode_field` (random access): decodes one field directly, O(1) in k and j
//!
//! Build with: `cargo rustc --example decode_asm --release -- --emit asm`

use columnar::*;
use columnar::bytes::indexed;

// ================================================================
// from_bytes path (construct all k fields, access field j)
// ================================================================

#[no_mangle] pub fn bytes_3_f0(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64]);
T::from_bytes(&mut indexed::decode(store)).0[i]
}
#[no_mangle] pub fn bytes_3_flast(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64]);
T::from_bytes(&mut indexed::decode(store)).2[i]
}
#[no_mangle] pub fn bytes_8_f0(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64], &'a [u64],
&'a [u64], &'a [u64], &'a [u64], &'a [u64]);
T::from_bytes(&mut indexed::decode(store)).0[i]
}
#[no_mangle] pub fn bytes_8_flast(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64], &'a [u64],
&'a [u64], &'a [u64], &'a [u64], &'a [u64]);
T::from_bytes(&mut indexed::decode(store)).7[i]
}

// ================================================================
// from_u64s path (non-panicking, LLVM eliminates unused fields)
// ================================================================

#[no_mangle] pub fn u64s_3_f0(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64]);
T::from_u64s(&mut indexed::decode_u64s(store)).0[i]
}
#[no_mangle] pub fn u64s_3_flast(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64]);
T::from_u64s(&mut indexed::decode_u64s(store)).2[i]
}
#[no_mangle] pub fn u64s_8_f0(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64], &'a [u64],
&'a [u64], &'a [u64], &'a [u64], &'a [u64]);
T::from_u64s(&mut indexed::decode_u64s(store)).0[i]
}
#[no_mangle] pub fn u64s_8_flast(store: &[u64], i: usize) -> u64 {
type T<'a> = (&'a [u64], &'a [u64], &'a [u64], &'a [u64],
&'a [u64], &'a [u64], &'a [u64], &'a [u64]);
T::from_u64s(&mut indexed::decode_u64s(store)).7[i]
}

// ================================================================
// Random access (decode one field directly, O(1) in both k and j)
// ================================================================

/// Decode field `k` directly from store as `(&[u64], u8)`.
/// Each call is independent — no iterator state.
#[inline(always)]
fn decode_field(store: &[u64], k: usize) -> (&[u64], u8) {
let slices = store[0] as usize / 8 - 1;
let index = &store[..slices + 1];
let last = *index.last().unwrap_or(&0) as usize;
let last_w = (last + 7) / 8;
let words = &store[..last_w];
let upper = (*index.get(k + 1).unwrap_or(&0) as usize).min(last);
let lower = (((*index.get(k).unwrap_or(&0) as usize) + 7) & !7).min(upper);
let upper_w = ((upper + 7) / 8).min(words.len());
let lower_w = (lower / 8).min(upper_w);
let tail = (upper % 8) as u8;
(&words[lower_w..upper_w], tail)
}

#[no_mangle] pub fn field_3_f0(store: &[u64], i: usize) -> u64 {
decode_field(store, 0).0[i]
}
#[no_mangle] pub fn field_3_flast(store: &[u64], i: usize) -> u64 {
decode_field(store, 2).0[i]
}
#[no_mangle] pub fn field_8_f0(store: &[u64], i: usize) -> u64 {
decode_field(store, 0).0[i]
}
#[no_mangle] pub fn field_8_flast(store: &[u64], i: usize) -> u64 {
decode_field(store, 7).0[i]
}

fn main() {
let mut store = vec![0u64; 100];
store[0] = 32; store[1] = 32; store[2] = 32; store[3] = 32;
println!("{}", std::hint::black_box(field_3_f0(std::hint::black_box(&store), 0)));
}
Loading
Loading