Skip to content

Commit ab9f426

Browse files
committed
Preallocate buffers for parsing timestamp into DateTimeParts
1 parent 3451462 commit ab9f426

5 files changed

Lines changed: 143 additions & 24 deletions

File tree

‎Cargo.lock‎

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎encodings/datetime-parts/Cargo.toml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ vortex-mask = { workspace = true }
2626
vortex-session = { workspace = true }
2727

2828
[dev-dependencies]
29+
divan = { workspace = true }
30+
rand = { workspace = true }
2931
rstest = { workspace = true }
3032
vortex-array = { workspace = true, features = ["_test-harness"] }
3133
vortex-error = { workspace = true }
34+
35+
[[bench]]
36+
name = "split_temporal"
37+
harness = false
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
#![expect(clippy::unwrap_used)]
5+
6+
use std::sync::LazyLock;
7+
8+
use divan::Bencher;
9+
use rand::RngExt;
10+
use rand::SeedableRng as _;
11+
use rand::rngs::StdRng;
12+
use vortex_array::IntoArray;
13+
use vortex_array::VortexSessionExecute;
14+
use vortex_array::arrays::PrimitiveArray;
15+
use vortex_array::arrays::TemporalArray;
16+
use vortex_array::extension::datetime::TimeUnit;
17+
use vortex_array::validity::Validity;
18+
use vortex_buffer::Buffer;
19+
use vortex_datetime_parts::split_temporal;
20+
use vortex_session::VortexSession;
21+
22+
fn main() {
23+
divan::main();
24+
}
25+
26+
static SESSION: LazyLock<VortexSession> = LazyLock::new(vortex_array::array_session);
27+
28+
const BENCH_ARGS: &[(usize, TimeUnit)] = &[
29+
(65_536, TimeUnit::Seconds),
30+
(65_536, TimeUnit::Milliseconds),
31+
(65_536, TimeUnit::Microseconds),
32+
(65_536, TimeUnit::Nanoseconds),
33+
];
34+
35+
#[divan::bench(args = BENCH_ARGS)]
36+
fn split(bencher: Bencher, args: (usize, TimeUnit)) {
37+
let (n, unit) = args;
38+
let divisor: i64 = match unit {
39+
TimeUnit::Seconds => 1,
40+
TimeUnit::Milliseconds => 1_000,
41+
TimeUnit::Microseconds => 1_000_000,
42+
TimeUnit::Nanoseconds => 1_000_000_000,
43+
TimeUnit::Days => unreachable!(),
44+
};
45+
let mut rng = StdRng::seed_from_u64(0);
46+
let timestamps = Buffer::from_iter((0..n).map(|_| {
47+
rng.random_range(1_500_000_000i64..1_800_000_000) * divisor + rng.random_range(0..divisor)
48+
}));
49+
let array = TemporalArray::new_timestamp(
50+
PrimitiveArray::new(timestamps, Validity::NonNullable).into_array(),
51+
unit,
52+
Some("UTC".into()),
53+
);
54+
55+
bencher
56+
.with_inputs(|| (array.clone(), SESSION.create_execution_ctx()))
57+
.bench_values(|(array, mut ctx)| split_temporal(array, &mut ctx).unwrap())
58+
}

‎encodings/datetime-parts/src/compress.rs‎

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ use vortex_array::arrays::TemporalArray;
99
use vortex_array::builtins::ArrayBuiltins;
1010
use vortex_array::dtype::DType;
1111
use vortex_array::dtype::PType;
12+
use vortex_array::extension::datetime::TimeUnit;
1213
use vortex_buffer::BufferMut;
1314
use vortex_error::VortexResult;
15+
use vortex_error::vortex_bail;
1416

1517
use crate::timestamp;
1618
pub struct TemporalParts {
@@ -39,25 +41,41 @@ pub fn split_temporal(array: TemporalArray, ctx: &mut ExecutionCtx) -> VortexRes
3941
))?
4042
.execute::<PrimitiveArray>(ctx)?;
4143

42-
let length = timestamps.len();
43-
let mut days = BufferMut::with_capacity(length);
44-
let mut seconds = BufferMut::with_capacity(length);
45-
let mut subseconds = BufferMut::with_capacity(length);
46-
47-
for &ts in timestamps.as_slice::<i64>() {
48-
let ts_parts = timestamp::split(ts, array.temporal_metadata().time_unit())?;
49-
days.push(ts_parts.days);
50-
seconds.push(ts_parts.seconds);
51-
subseconds.push(ts_parts.subseconds);
52-
}
44+
let (days, seconds, subseconds) = match array.temporal_metadata().time_unit() {
45+
TimeUnit::Nanoseconds => split_slice::<1_000_000_000>(timestamps.as_slice::<i64>()),
46+
TimeUnit::Microseconds => split_slice::<1_000_000>(timestamps.as_slice::<i64>()),
47+
TimeUnit::Milliseconds => split_slice::<1_000>(timestamps.as_slice::<i64>()),
48+
TimeUnit::Seconds => split_slice::<1>(timestamps.as_slice::<i64>()),
49+
TimeUnit::Days => vortex_bail!("Cannot handle day-level data"),
50+
};
5351

5452
Ok(TemporalParts {
55-
days: PrimitiveArray::new(days, temporal_values.validity()?).into_array(),
53+
days: PrimitiveArray::new(days.freeze(), temporal_values.validity()?).into_array(),
5654
seconds: seconds.into_array(),
5755
subseconds: subseconds.into_array(),
5856
})
5957
}
6058

59+
fn split_slice<const DIVISOR: i64>(
60+
timestamps: &[i64],
61+
) -> (BufferMut<i64>, BufferMut<i64>, BufferMut<i64>) {
62+
let mut days = BufferMut::zeroed(timestamps.len());
63+
let mut seconds = BufferMut::zeroed(timestamps.len());
64+
let mut subseconds = BufferMut::zeroed(timestamps.len());
65+
let (days_out, seconds_out, subseconds_out) = (
66+
days.as_mut_slice(),
67+
seconds.as_mut_slice(),
68+
subseconds.as_mut_slice(),
69+
);
70+
for (i, &ts) in timestamps.iter().enumerate() {
71+
let ts_parts = timestamp::split_with_divisor::<DIVISOR>(ts);
72+
days_out[i] = ts_parts.days;
73+
seconds_out[i] = ts_parts.seconds;
74+
subseconds_out[i] = ts_parts.subseconds;
75+
}
76+
(days, seconds, subseconds)
77+
}
78+
6179
#[cfg(test)]
6280
mod tests {
6381
use rstest::rstest;

‎encodings/datetime-parts/src/timestamp.rs‎

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,26 @@ pub struct TimestampParts {
3131
///
3232
/// Returns an error if `time_unit` is days, which cannot be split.
3333
pub fn split(timestamp: i64, time_unit: TimeUnit) -> VortexResult<TimestampParts> {
34-
let divisor = match time_unit {
35-
TimeUnit::Nanoseconds => 1_000_000_000,
36-
TimeUnit::Microseconds => 1_000_000,
37-
TimeUnit::Milliseconds => 1_000,
38-
TimeUnit::Seconds => 1,
34+
Ok(match time_unit {
35+
TimeUnit::Nanoseconds => split_with_divisor::<1_000_000_000>(timestamp),
36+
TimeUnit::Microseconds => split_with_divisor::<1_000_000>(timestamp),
37+
TimeUnit::Milliseconds => split_with_divisor::<1_000>(timestamp),
38+
TimeUnit::Seconds => split_with_divisor::<1>(timestamp),
3939
TimeUnit::Days => vortex_bail!("Cannot handle day-level data"),
40-
};
41-
42-
let ticks_per_day = SECONDS_PER_DAY * divisor;
43-
Ok(TimestampParts {
44-
days: timestamp / ticks_per_day,
45-
seconds: (timestamp % ticks_per_day) / divisor,
46-
subseconds: (timestamp % ticks_per_day) % divisor,
4740
})
4841
}
4942

43+
/// Split a Unix timestamp
44+
pub(crate) fn split_with_divisor<const DIVISOR: i64>(timestamp: i64) -> TimestampParts {
45+
let days = timestamp / (SECONDS_PER_DAY * DIVISOR);
46+
let total_seconds = timestamp / DIVISOR;
47+
TimestampParts {
48+
days,
49+
seconds: total_seconds - days * SECONDS_PER_DAY,
50+
subseconds: timestamp - total_seconds * DIVISOR,
51+
}
52+
}
53+
5054
/// Combines timestamp parts back into a Unix timestamp.
5155
///
5256
/// # Arguments
@@ -71,8 +75,39 @@ pub fn combine(ts_parts: TimestampParts, time_unit: TimeUnit) -> i64 {
7175

7276
#[cfg(test)]
7377
mod tests {
78+
use rstest::rstest;
79+
7480
use super::*;
7581

82+
#[rstest]
83+
#[case(TimeUnit::Seconds, 1)]
84+
#[case(TimeUnit::Milliseconds, 1_000)]
85+
#[case(TimeUnit::Microseconds, 1_000_000)]
86+
#[case(TimeUnit::Nanoseconds, 1_000_000_000)]
87+
fn test_split_divmod(#[case] unit: TimeUnit, #[case] divisor: i64) -> VortexResult<()> {
88+
let ticks_per_day = SECONDS_PER_DAY * divisor;
89+
for ts in [
90+
0,
91+
1,
92+
-1,
93+
divisor - 1,
94+
3723 * divisor + divisor / 2,
95+
-(3723 * divisor + divisor / 2),
96+
ticks_per_day - 1,
97+
ticks_per_day,
98+
-ticks_per_day,
99+
i64::MAX,
100+
i64::MIN,
101+
] {
102+
let parts = split(ts, unit)?;
103+
assert_eq!(parts.days, ts / ticks_per_day);
104+
assert_eq!(parts.seconds, (ts % ticks_per_day) / divisor);
105+
assert_eq!(parts.subseconds, (ts % ticks_per_day) % divisor);
106+
assert_eq!(combine(parts, unit), ts);
107+
}
108+
Ok(())
109+
}
110+
76111
#[test]
77112
fn test_split_seconds() {
78113
// 1970-01-02 01:02:03 UTC

0 commit comments

Comments
 (0)