Skip to content

Commit 15fa50a

Browse files
authored
perf: Use Cow as output for rechunk and add rechunk_mut (#21116)
1 parent 241d0c7 commit 15fa50a

File tree

54 files changed

+136
-123
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+136
-123
lines changed

crates/polars-core/src/chunked_array/cast.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ fn cast_list(
601601
// We still rechunk because we must bubble up a single data-type
602602
// TODO!: consider a version that works on chunks and merges the data-types and arrays.
603603
let ca = ca.rechunk();
604-
let arr = ca.downcast_iter().next().unwrap();
604+
let arr = ca.downcast_as_array();
605605
// SAFETY: inner dtype is passed correctly
606606
let s = unsafe {
607607
Series::from_chunks_and_dtype_unchecked(
@@ -630,7 +630,7 @@ fn cast_list(
630630
unsafe fn cast_list_unchecked(ca: &ListChunked, child_type: &DataType) -> PolarsResult<Series> {
631631
// TODO! add chunked, but this must correct for list offsets.
632632
let ca = ca.rechunk();
633-
let arr = ca.downcast_iter().next().unwrap();
633+
let arr = ca.downcast_as_array();
634634
// SAFETY: inner dtype is passed correctly
635635
let s = unsafe {
636636
Series::from_chunks_and_dtype_unchecked(
@@ -666,7 +666,7 @@ fn cast_fixed_size_list(
666666
options: CastOptions,
667667
) -> PolarsResult<(ArrayRef, DataType)> {
668668
let ca = ca.rechunk();
669-
let arr = ca.downcast_iter().next().unwrap();
669+
let arr = ca.downcast_as_array();
670670
// SAFETY: inner dtype is passed correctly
671671
let s = unsafe {
672672
Series::from_chunks_and_dtype_unchecked(

crates/polars-core/src/chunked_array/iterator/par/list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl ListChunked {
3030
// Get an indexed parallel iterator over the [`Series`] in this [`ListChunked`].
3131
// Also might be faster as it doesn't use `flat_map`.
3232
pub fn par_iter_indexed(&mut self) -> impl IndexedParallelIterator<Item = Option<Series>> + '_ {
33-
*self = self.rechunk();
33+
self.rechunk_mut();
3434
let arr = self.downcast_iter().next().unwrap();
3535

3636
let dtype = self.inner_dtype();

crates/polars-core/src/chunked_array/list/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ impl ListChunked {
135135
) -> PolarsResult<ListChunked> {
136136
// generated Series will have wrong length otherwise.
137137
let ca = self.rechunk();
138-
let arr = ca.downcast_iter().next().unwrap();
138+
let arr = ca.downcast_as_array();
139139

140140
// SAFETY:
141141
// Inner dtype is passed correctly

crates/polars-core/src/chunked_array/logical/categorical/from.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ impl CategoricalChunked {
2727
ArrowDataType::LargeUtf8
2828
};
2929
let keys = self.physical().rechunk();
30-
let keys = keys.downcast_iter().next().unwrap();
30+
let keys = keys.downcast_as_array();
3131
let map = &**self.get_rev_map();
3232
let dtype = ArrowDataType::Dictionary(IntegerType::UInt32, Box::new(values_dtype), false);
3333
match map {
@@ -40,7 +40,7 @@ impl CategoricalChunked {
4040
},
4141
RevMapping::Global(reverse_map, values, _uuid) => {
4242
let iter = keys
43-
.into_iter()
43+
.iter()
4444
.map(|opt_k| opt_k.map(|k| *reverse_map.get(k).unwrap()));
4545
let keys = PrimitiveArray::from_trusted_len_iter(iter);
4646

@@ -60,7 +60,7 @@ impl CategoricalChunked {
6060
ArrowDataType::LargeUtf8
6161
};
6262
let keys = self.physical().rechunk();
63-
let keys = keys.downcast_iter().next().unwrap();
63+
let keys = keys.downcast_as_array();
6464
let map = &**self.get_rev_map();
6565
let dtype = ArrowDataType::Dictionary(IntegerType::Int64, Box::new(values_dtype), false);
6666
match map {
@@ -85,7 +85,7 @@ impl CategoricalChunked {
8585
},
8686
RevMapping::Global(reverse_map, values, _uuid) => {
8787
let iter = keys
88-
.into_iter()
88+
.iter()
8989
.map(|opt_k| opt_k.map(|k| *reverse_map.get(k).unwrap() as i64));
9090
let keys = PrimitiveArray::from_trusted_len_iter(iter);
9191

crates/polars-core/src/chunked_array/mod.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ pub mod temporal;
4848
mod to_vec;
4949
mod trusted_len;
5050

51-
use std::mem;
5251
use std::slice::Iter;
5352

5453
use arrow::legacy::prelude::*;
@@ -154,13 +153,12 @@ impl<T: PolarsDataType> ChunkedArray<T> {
154153
self.chunks.len() > 1 && self.chunks.len() > self.len() / 3
155154
}
156155

157-
fn optional_rechunk(self) -> Self {
156+
fn optional_rechunk(mut self) -> Self {
158157
// Rechunk if we have many small chunks.
159158
if self.should_rechunk() {
160-
self.rechunk()
161-
} else {
162-
self
159+
self.rechunk_mut()
163160
}
161+
self
164162
}
165163

166164
pub(crate) fn as_any(&self) -> &dyn std::any::Any {
@@ -671,8 +669,7 @@ where
671669
T: PolarsNumericType,
672670
{
673671
fn as_single_ptr(&mut self) -> PolarsResult<usize> {
674-
let mut ca = self.rechunk();
675-
mem::swap(&mut ca, self);
672+
self.rechunk_mut();
676673
let a = self.data_views().next().unwrap();
677674
let ptr = a.as_ptr();
678675
Ok(ptr as usize)

crates/polars-core/src/chunked_array/ops/chunkops.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::cell::Cell;
23

34
use arrow::bitmap::{Bitmap, BitmapBuilder};
@@ -158,27 +159,43 @@ impl<T: PolarsDataType> ChunkedArray<T> {
158159
.sum::<usize>();
159160
}
160161

161-
pub fn rechunk(&self) -> Self {
162+
/// Rechunks this ChunkedArray, returning a new Cow::Owned ChunkedArray if it was
163+
/// rechunked or simply a Cow::Borrowed of itself if it was already a single chunk.
164+
pub fn rechunk(&self) -> Cow<'_, Self> {
162165
match self.dtype() {
163166
#[cfg(feature = "object")]
164167
DataType::Object(_, _) => {
165168
panic!("implementation error")
166169
},
167170
_ => {
168171
if self.chunks.len() == 1 {
169-
self.clone()
172+
Cow::Borrowed(self)
170173
} else {
171174
let chunks = vec![concatenate_unchecked(&self.chunks).unwrap()];
172175

173176
let mut ca = unsafe { self.copy_with_chunks(chunks) };
174177
use StatisticsFlags as F;
175178
ca.retain_flags_from(self, F::IS_SORTED_ANY | F::CAN_FAST_EXPLODE_LIST);
176-
ca
179+
Cow::Owned(ca)
177180
}
178181
},
179182
}
180183
}
181184

185+
/// Rechunks this ChunkedArray in-place.
186+
pub fn rechunk_mut(&mut self) {
187+
if self.chunks.len() > 1 {
188+
let rechunked = concatenate_unchecked(&self.chunks).unwrap();
189+
if self.chunks.capacity() <= 8 {
190+
// Reuse chunk allocation if not excessive.
191+
self.chunks.clear();
192+
self.chunks.push(rechunked);
193+
} else {
194+
self.chunks = vec![rechunked];
195+
}
196+
}
197+
}
198+
182199
pub fn rechunk_validity(&self) -> Option<Bitmap> {
183200
if self.chunks.len() == 1 {
184201
return self.chunks[0].validity().cloned();

crates/polars-core/src/chunked_array/ops/downcast.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ impl<T: PolarsDataType> ChunkedArray<T> {
108108
}
109109

110110
#[inline]
111-
pub fn downcast_into_array(self) -> T::Array {
111+
pub fn downcast_as_array(&self) -> &T::Array {
112112
assert_eq!(self.chunks.len(), 1);
113-
self.downcast_get(0).unwrap().clone()
113+
self.downcast_get(0).unwrap()
114114
}
115115

116116
#[inline]

crates/polars-core/src/chunked_array/ops/explode_and_offsets.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ impl ChunkExplode for ListChunked {
5151
let ca = self.rechunk();
5252
let listarr: &LargeListArray = ca.downcast_iter().next().unwrap();
5353
let offsets = listarr.offsets().clone();
54-
5554
Ok(offsets)
5655
}
5756

crates/polars-core/src/chunked_array/ops/extend.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ where
4141
// all to a single chunk
4242
if self.chunks.len() > 1 {
4343
self.append(other)?;
44-
*self = self.rechunk();
44+
self.rechunk_mut();
4545
return Ok(());
4646
}
4747
// Depending on the state of the underlying arrow array we
@@ -119,7 +119,7 @@ impl BooleanChunked {
119119
// make sure that we are a single chunk already
120120
if self.chunks.len() > 1 {
121121
self.append(other)?;
122-
*self = self.rechunk();
122+
self.rechunk_mut();
123123
return Ok(());
124124
}
125125
let arr = self.downcast_iter().next().unwrap();

crates/polars-core/src/chunked_array/ops/gather.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ impl IdxCa {
327327
impl ChunkTakeUnchecked<IdxCa> for ArrayChunked {
328328
unsafe fn take_unchecked(&self, indices: &IdxCa) -> Self {
329329
let chunks = vec![take_unchecked(
330-
&self.rechunk().downcast_into_array(),
331-
&indices.rechunk().downcast_into_array(),
330+
self.rechunk().downcast_as_array(),
331+
indices.rechunk().downcast_as_array(),
332332
)];
333333
self.copy_with_chunks(chunks)
334334
}
@@ -345,8 +345,8 @@ impl<I: AsRef<[IdxSize]> + ?Sized> ChunkTakeUnchecked<I> for ArrayChunked {
345345
impl ChunkTakeUnchecked<IdxCa> for ListChunked {
346346
unsafe fn take_unchecked(&self, indices: &IdxCa) -> Self {
347347
let chunks = vec![take_unchecked(
348-
&self.rechunk().downcast_into_array(),
349-
&indices.rechunk().downcast_into_array(),
348+
self.rechunk().downcast_as_array(),
349+
indices.rechunk().downcast_as_array(),
350350
)];
351351
self.copy_with_chunks(chunks)
352352
}

crates/polars-core/src/chunked_array/ops/reverse.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl ChunkReverse for ArrayChunked {
8888
todo!("reverse for FixedSizeList with non-numeric dtypes not yet supported")
8989
}
9090
let ca = self.rechunk();
91-
let arr = ca.downcast_iter().next().unwrap();
91+
let arr = ca.downcast_as_array();
9292
let values = arr.values().as_ref();
9393

9494
let mut builder =

crates/polars-core/src/chunked_array/ops/rolling_window.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ mod inner_mod {
9595
options.window_size = std::cmp::min(self.len(), options.window_size);
9696

9797
let len = self.len();
98-
let arr = ca.downcast_iter().next().unwrap();
98+
let arr = ca.downcast_as_array();
9999
let mut ca = ChunkedArray::<T>::from_slice(PlSmallStr::EMPTY, &[T::Native::zero()]);
100100
let ptr = ca.chunks[0].as_mut() as *mut dyn Array as *mut PrimitiveArray<T::Native>;
101101
let mut series_container = ca.into_series();
@@ -215,7 +215,7 @@ mod inner_mod {
215215
return Ok(Self::full_null(self.name().clone(), self.len()));
216216
}
217217
let ca = self.rechunk();
218-
let arr = ca.downcast_iter().next().unwrap();
218+
let arr = ca.downcast_as_array();
219219

220220
// We create a temporary dummy ChunkedArray. This will be a
221221
// container where we swap the window contents every iteration doing

crates/polars-core/src/chunked_array/ops/sort/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ impl ChunkSort<BinaryType> for BinaryChunked {
408408
// We will sort by the views and reconstruct with sorted views. We leave the buffers as is.
409409
// We must rechunk to ensure that all views point into the proper buffers.
410410
let ca = self.rechunk();
411-
let arr = ca.downcast_into_array();
411+
let arr = ca.downcast_as_array().clone();
412412

413413
let (views, buffers, validity, total_bytes_len, total_buffer_len) = arr.into_inner();
414414
let mut views = views.make_mut();
@@ -590,7 +590,7 @@ impl ChunkSort<BinaryOffsetType> for BinaryOffsetChunked {
590590
fn arg_sort(&self, mut options: SortOptions) -> IdxCa {
591591
options.multithreaded &= POOL.current_num_threads() > 1;
592592
let ca = self.rechunk();
593-
let arr = ca.downcast_into_array();
593+
let arr = ca.downcast_as_array();
594594
let mut idx = (0..(arr.len() as IdxSize)).collect::<Vec<_>>();
595595

596596
let argsort = |args| {

crates/polars-core/src/chunked_array/struct_/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ impl StructChunked {
411411
.zip(other.chunks())
412412
.any(|(a, b)| a.len() != b.len())
413413
{
414-
*self = self.rechunk();
414+
self.rechunk_mut();
415415
let other = other.rechunk();
416416
return self.zip_outer_validity(&other);
417417
}

crates/polars-core/src/frame/group_by/aggregations/agg_list.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ impl AggList for StructChunked {
287287
let out = ca.into_series().take_unchecked(&gather);
288288
out.struct_().unwrap().clone()
289289
} else {
290-
ca.rechunk()
290+
ca.rechunk().into_owned()
291291
};
292292

293293
let arr = gathered.chunks()[0].clone();

crates/polars-core/src/frame/group_by/aggregations/boolean.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::borrow::Cow;
2+
13
use super::*;
24
use crate::chunked_array::cast::CastOptions;
35

@@ -24,10 +26,11 @@ unsafe fn bitwise_agg(
2426
f: fn(&BooleanChunked) -> Option<bool>,
2527
) -> Series {
2628
// Prevent a rechunk for every individual group.
29+
2730
let s = if groups.len() > 1 {
2831
ca.rechunk()
2932
} else {
30-
ca.clone()
33+
Cow::Borrowed(ca)
3134
};
3235

3336
match groups {

crates/polars-core/src/frame/group_by/aggregations/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod boolean;
33
mod dispatch;
44
mod string;
55

6+
use std::borrow::Cow;
67
use std::cmp::Ordering;
78

89
pub use agg_list::*;
@@ -456,10 +457,11 @@ where
456457
ChunkTakeUnchecked<[IdxSize]> + ChunkBitwiseReduce<Physical = T::Native> + IntoSeries,
457458
{
458459
// Prevent a rechunk for every individual group.
460+
459461
let s = if groups.len() > 1 {
460462
ca.rechunk()
461463
} else {
462-
ca.clone()
464+
Cow::Borrowed(ca)
463465
};
464466

465467
match groups {

crates/polars-core/src/frame/group_by/aggregations/string.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ impl BinaryChunked {
3333
match groups {
3434
GroupsType::Idx(groups) => {
3535
let ca_self = self.rechunk();
36-
let arr = ca_self.downcast_iter().next().unwrap();
36+
let arr = ca_self.downcast_as_array();
3737
let no_nulls = arr.null_count() == 0;
3838
_agg_helper_idx_bin(groups, |(first, idx)| {
3939
debug_assert!(idx.len() <= ca_self.len());
@@ -95,7 +95,7 @@ impl BinaryChunked {
9595
match groups {
9696
GroupsType::Idx(groups) => {
9797
let ca_self = self.rechunk();
98-
let arr = ca_self.downcast_iter().next().unwrap();
98+
let arr = ca_self.downcast_as_array();
9999
let no_nulls = arr.null_count() == 0;
100100
_agg_helper_idx_bin(groups, |(first, idx)| {
101101
debug_assert!(idx.len() <= self.len());

crates/polars-core/src/frame/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3109,7 +3109,7 @@ impl DataFrame {
31093109
for ca in iter {
31103110
acc_ca.append(&ca)?;
31113111
}
3112-
Ok(acc_ca.rechunk())
3112+
Ok(acc_ca.rechunk().into_owned())
31133113
}
31143114

31153115
/// Get the supertype of the columns in this DataFrame

crates/polars-core/src/series/implementations/array.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl SeriesTrait for SeriesWrap<ArrayChunked> {
164164
}
165165

166166
fn rechunk(&self) -> Series {
167-
self.0.rechunk().into_series()
167+
self.0.rechunk().into_owned().into_series()
168168
}
169169

170170
fn new_from_index(&self, index: usize, length: usize) -> Series {

crates/polars-core/src/series/implementations/binary.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ impl SeriesTrait for SeriesWrap<BinaryChunked> {
167167
}
168168

169169
fn rechunk(&self) -> Series {
170-
self.0.rechunk().into_series()
170+
self.0.rechunk().into_owned().into_series()
171171
}
172172

173173
fn new_from_index(&self, index: usize, length: usize) -> Series {

crates/polars-core/src/series/implementations/binary_offset.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ impl SeriesTrait for SeriesWrap<BinaryOffsetChunked> {
139139
}
140140

141141
fn rechunk(&self) -> Series {
142-
self.0.rechunk().into_series()
142+
self.0.rechunk().into_owned().into_series()
143143
}
144144

145145
fn new_from_index(&self, index: usize, length: usize) -> Series {

crates/polars-core/src/series/implementations/boolean.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ impl SeriesTrait for SeriesWrap<BooleanChunked> {
193193
}
194194

195195
fn rechunk(&self) -> Series {
196-
self.0.rechunk().into_series()
196+
self.0.rechunk().into_owned().into_series()
197197
}
198198

199199
fn new_from_index(&self, index: usize, length: usize) -> Series {

0 commit comments

Comments
 (0)