Skip to content
Closed
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
53 changes: 43 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions parquet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ rand = { version = "0.9", default-features = false, features = ["std", "std_rng"
object_store = { workspace = true, features = ["azure", "fs"] }
sysinfo = { version = "0.38.1", default-features = false, features = ["system"] }

# Used as the global allocator in the `arrow_writer` benchmark, with its
# page-decay policy pinned via a compiled-in `malloc_conf` symbol, to keep
# per-iteration buffer allocations on warm pages (see that bench). The
# `unprefixed_*` feature makes jemalloc read the unprefixed `malloc_conf`
# symbol defined there; `tikv-jemalloc-ctl` is used to assert at startup that
# the policy actually took effect (so a silently-ignored config fails loudly).
#
# Gated to Linux: jemalloc does not build on some targets (e.g. wasm, msvc), and
# the unprefixed `malloc_conf` symbol it relies on is not honored on others
# (e.g. apple, android). On every other target the bench falls back to the
# default allocator. Linux is where the canonical benchmark runner runs.
[target.'cfg(target_os = "linux")'.dev-dependencies]
tikv-jemallocator = { version = "0.7", default-features = false, features = ["unprefixed_malloc_on_supported_platforms"] }
tikv-jemalloc-ctl = { version = "0.7", default-features = false }

[package.metadata.docs.rs]
all-features = true

Expand Down
66 changes: 66 additions & 0 deletions parquet/benches/arrow_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,71 @@
#[macro_use]
extern crate criterion;

// Use jemalloc, with page decay disabled, for the writer benchmarks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this might be overfitting -- using jemalloc (vs other allocators)

I do't have any way to really evaluate the implications of switching allocators, and all these jemalloc tuning knobs. I fear they will have other hard to understand side effects,

I am happy to have us conclude that any particular benchmark run is allocation page heavy and thus we can ignore the effects

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reason to use jemallloc is not because of the allocator itself, rather because it's very configurable and we need the configuration.

//
// Each criterion iteration builds a fresh `ArrowWriter`, so the writer's
// internal encode buffers are allocated and freed every iteration. Whether
// those buffers are served from warm (already-faulted) pages or fresh pages
// depends on the heap state left by previously-run benchmarks in the same
// process. The cold-page case pays a per-page minor fault on every byte
// written, which roughly doubles the measured time for the byte-array writers
// (e.g. `string/parquet_2` swings between ~106ms and ~190ms purely on
// allocation order, with no code change).
//
// The retention policy is pinned, not left to the allocator default, via the
// compiled-in `malloc_conf` symbol below: `dirty_decay_ms:-1,muzzy_decay_ms:-1`
// disables jemalloc's page decay, so freed pages are kept mapped and reused
// warm instead of being returned to the OS. That removes the per-iteration
// fault tax and collapses the order-dependent bimodality, so the numbers
// reflect encoder work. Pinning the setting (rather than relying on an
// allocator's default) keeps the benchmark stable across allocator upgrades.
//
// Gated to Linux (see Cargo.toml): jemalloc does not build on some targets and
// its unprefixed `malloc_conf` symbol is not honored on others; elsewhere the
// bench just uses the default allocator (and `assert_page_decay_disabled` is a
// no-op). Linux is where the canonical benchmark runner runs.
#[cfg(target_os = "linux")]
#[global_allocator]
static GLOBAL: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc;

// jemalloc reads its options from a symbol named `malloc_conf`. The
// `unprefixed_malloc_on_supported_platforms` feature (see Cargo.toml) is what
// makes jemalloc look for the unprefixed name this defines; without it the
// symbol would be silently ignored. `assert_page_decay_disabled` below guards
// against exactly that, so a config that fails to apply fails loudly instead
// of quietly reintroducing the instability.
#[cfg(target_os = "linux")]
#[allow(non_upper_case_globals)]
#[unsafe(export_name = "malloc_conf")]
pub static malloc_conf: &[u8] = b"dirty_decay_ms:-1,muzzy_decay_ms:-1\0";

/// Assert the `malloc_conf` above actually took effect. If the symbol is ever
/// silently ignored (feature dropped, unsupported platform, renamed symbol,
/// allocator swapped), the byte-array writer benchmarks would quietly become
/// order-dependent again; failing loudly here prevents that.
#[cfg(target_os = "linux")]
fn assert_page_decay_disabled() {
// SAFETY: reading immutable `opt.*` mallctl values; the type matches
// jemalloc's `ssize_t`.
let (dirty, muzzy): (isize, isize) = unsafe {
(
tikv_jemalloc_ctl::raw::read(b"opt.dirty_decay_ms\0").unwrap(),
tikv_jemalloc_ctl::raw::read(b"opt.muzzy_decay_ms\0").unwrap(),
)
};
assert!(
dirty == -1 && muzzy == -1,
"malloc_conf did not take effect (dirty_decay_ms={dirty}, muzzy_decay_ms={muzzy}, \
expected -1/-1); the arrow_writer benchmark would be order-dependent. Ensure the \
`unprefixed_malloc_on_supported_platforms` jemalloc feature is enabled.",
);
}

/// On non-Linux targets the benchmark uses the default allocator, so there is
/// no jemalloc page-decay setting to check.
#[cfg(not(target_os = "linux"))]
fn assert_page_decay_disabled() {}

use criterion::{Bencher, Criterion, Throughput};
use parquet::arrow::ArrowWriter;
use parquet::basic::{Compression, ZstdLevel};
Expand Down Expand Up @@ -466,6 +531,7 @@ fn create_writer_props() -> Vec<(&'static str, WriterProperties)> {
}

fn bench_all_writers(c: &mut Criterion) {
assert_page_decay_disabled();
let batches = create_batches();
let props = create_writer_props();

Expand Down
Loading