Skip to content

Commit 6fea7aa

Browse files
authored
Merge pull request #797 from rust-ndarray/zip-apply-collect
Add methods to collect Zip into an array
2 parents c9d2e4f + f7319b3 commit 6fea7aa

12 files changed

+306
-26
lines changed

benches/bench1.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -258,23 +258,35 @@ fn add_2d_zip(bench: &mut test::Bencher) {
258258
}
259259

260260
#[bench]
261-
fn add_2d_alloc(bench: &mut test::Bencher) {
261+
fn add_2d_alloc_plus(bench: &mut test::Bencher) {
262262
let a = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));
263263
let b = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));
264264
bench.iter(|| &a + &b);
265265
}
266266

267267
#[bench]
268-
fn add_2d_zip_alloc(bench: &mut test::Bencher) {
268+
fn add_2d_alloc_zip_uninit(bench: &mut test::Bencher) {
269269
let a = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));
270270
let b = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));
271271
bench.iter(|| unsafe {
272272
let mut c = Array::uninitialized(a.dim());
273-
azip!((&a in &a, &b in &b, c in &mut c) *c = a + b);
273+
azip!((&a in &a, &b in &b, c in c.raw_view_mut())
274+
std::ptr::write(c, a + b)
275+
);
274276
c
275277
});
276278
}
277279

280+
#[bench]
281+
fn add_2d_alloc_zip_collect(bench: &mut test::Bencher) {
282+
let a = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));
283+
let b = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));
284+
bench.iter(|| {
285+
Zip::from(&a).and(&b).apply_collect(|&x, &y| x + y)
286+
});
287+
}
288+
289+
278290
#[bench]
279291
fn add_2d_assign_ops(bench: &mut test::Bencher) {
280292
let mut a = Array::<i32, _>::zeros((ADD2DSZ, ADD2DSZ));

src/argument_traits.rs

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use std::cell::Cell;
2+
use std::mem::MaybeUninit;
3+
4+
5+
/// A producer element that can be assigned to once
6+
pub trait AssignElem<T> {
7+
/// Assign the value `input` to the element that self represents.
8+
fn assign_elem(self, input: T);
9+
}
10+
11+
/// Assignable element, simply `*self = input`.
12+
impl<'a, T> AssignElem<T> for &'a mut T {
13+
fn assign_elem(self, input: T) {
14+
*self = input;
15+
}
16+
}
17+
18+
/// Assignable element, simply `self.set(input)`.
19+
impl<'a, T> AssignElem<T> for &'a Cell<T> {
20+
fn assign_elem(self, input: T) {
21+
self.set(input);
22+
}
23+
}
24+
25+
/// Assignable element, the item in the MaybeUninit is overwritten (prior value, if any, is not
26+
/// read or dropped).
27+
impl<'a, T> AssignElem<T> for &'a mut MaybeUninit<T> {
28+
fn assign_elem(self, input: T) {
29+
*self = MaybeUninit::new(input);
30+
}
31+
}

src/impl_constructors.rs

+20
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#![allow(clippy::match_wild_err_arm)]
1414

1515
use num_traits::{Float, One, Zero};
16+
use std::mem::MaybeUninit;
1617

1718
use crate::dimension;
1819
use crate::error::{self, ShapeError};
@@ -517,3 +518,22 @@ where
517518
Self::from_shape_vec_unchecked(shape, v)
518519
}
519520
}
521+
522+
impl<S, A, D> ArrayBase<S, D>
523+
where
524+
S: DataOwned<Elem = MaybeUninit<A>>,
525+
D: Dimension,
526+
{
527+
pub(crate) fn maybe_uninit<Sh>(shape: Sh) -> Self
528+
where
529+
Sh: ShapeBuilder<Dim = D>,
530+
{
531+
unsafe {
532+
let shape = shape.into_shape();
533+
let size = size_of_shape_checked_unwrap!(&shape.dim);
534+
let mut v = Vec::with_capacity(size);
535+
v.set_len(size);
536+
Self::from_shape_vec_unchecked(shape, v)
537+
}
538+
}
539+
}

src/impl_owned_array.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
use std::mem::MaybeUninit;
2+
use std::mem::transmute;
3+
14
use crate::imp_prelude::*;
5+
use crate::OwnedRepr;
26

37
/// Methods specific to `Array0`.
48
///
@@ -57,3 +61,33 @@ where
5761
self.data.0
5862
}
5963
}
64+
65+
/// Methods specific to `Array` of `MaybeUninit`.
66+
///
67+
/// ***See also all methods for [`ArrayBase`]***
68+
///
69+
/// [`ArrayBase`]: struct.ArrayBase.html
70+
impl<A, D> Array<MaybeUninit<A>, D>
71+
where
72+
D: Dimension,
73+
{
74+
/// Assert that the array's storage's elements are all fully initialized, and conver
75+
/// the array from element type `MaybeUninit<A>` to `A`.
76+
pub(crate) unsafe fn assume_init(self) -> Array<A, D> {
77+
// NOTE: Fully initialized includes elements not reachable in current slicing/view.
78+
//
79+
// Should this method be generalized to all array types?
80+
// (Will need a way to map the RawData<Elem=X> to RawData<Elem=Y> of same kind)
81+
82+
let Array { data, ptr, dim, strides } = self;
83+
let data = transmute::<OwnedRepr<MaybeUninit<A>>, OwnedRepr<A>>(data);
84+
let ptr = ptr.cast::<A>();
85+
86+
Array {
87+
data,
88+
ptr,
89+
dim,
90+
strides,
91+
}
92+
}
93+
}

src/layout/layoutfmt.rs

-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
// except according to those terms.
88

99
use super::Layout;
10-
use super::LayoutPriv;
1110

1211
const LAYOUT_NAMES: &[&str] = &["C", "F"];
1312

src/layout/mod.rs

+6-13
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,28 @@
11
mod layoutfmt;
22

3-
// public but users don't interact with it
3+
// public struct but users don't interact with it
44
#[doc(hidden)]
55
/// Memory layout description
66
#[derive(Copy, Clone)]
77
pub struct Layout(u32);
88

9-
pub trait LayoutPriv: Sized {
10-
fn new(x: u32) -> Self;
11-
fn and(self, flag: Self) -> Self;
12-
fn is(self, flag: u32) -> bool;
13-
fn flag(self) -> u32;
14-
}
15-
16-
impl LayoutPriv for Layout {
9+
impl Layout {
1710
#[inline(always)]
18-
fn new(x: u32) -> Self {
11+
pub(crate) fn new(x: u32) -> Self {
1912
Layout(x)
2013
}
2114

2215
#[inline(always)]
23-
fn is(self, flag: u32) -> bool {
16+
pub(crate) fn is(self, flag: u32) -> bool {
2417
self.0 & flag != 0
2518
}
2619
#[inline(always)]
27-
fn and(self, flag: Layout) -> Layout {
20+
pub(crate) fn and(self, flag: Layout) -> Layout {
2821
Layout(self.0 & flag.0)
2922
}
3023

3124
#[inline(always)]
32-
fn flag(self) -> u32 {
25+
pub(crate) fn flag(self) -> u32 {
3326
self.0
3427
}
3528
}

src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ mod array_approx;
149149
mod array_serde;
150150
mod arrayformat;
151151
mod arraytraits;
152+
mod argument_traits;
153+
pub use crate::argument_traits::AssignElem;
152154
mod data_traits;
153155

154156
pub use crate::aliases::*;

src/parallel/impl_par_methods.rs

+45-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::{ArrayBase, DataMut, Dimension, NdProducer, Zip};
1+
use crate::{Array, ArrayBase, DataMut, Dimension, IntoNdProducer, NdProducer, Zip};
2+
use crate::AssignElem;
23

34
use crate::parallel::prelude::*;
45

@@ -43,7 +44,7 @@ where
4344
// Zip
4445

4546
macro_rules! zip_impl {
46-
($([$($p:ident)*],)+) => {
47+
($([$notlast:ident $($p:ident)*],)+) => {
4748
$(
4849
#[allow(non_snake_case)]
4950
impl<D, $($p),*> Zip<($($p,)*), D>
@@ -63,16 +64,52 @@ macro_rules! zip_impl {
6364
{
6465
self.into_par_iter().for_each(move |($($p,)*)| function($($p),*))
6566
}
67+
68+
expand_if!(@bool [$notlast]
69+
70+
/// Apply and collect the results into a new array, which has the same size as the
71+
/// inputs.
72+
///
73+
/// If all inputs are c- or f-order respectively, that is preserved in the output.
74+
///
75+
/// Restricted to functions that produce copyable results for technical reasons; other
76+
/// cases are not yet implemented.
77+
pub fn par_apply_collect<R>(self, f: impl Fn($($p::Item,)* ) -> R + Sync + Send) -> Array<R, D>
78+
where R: Copy + Send
79+
{
80+
let mut output = self.uninitalized_for_current_layout::<R>();
81+
self.par_apply_assign_into(&mut output, f);
82+
unsafe {
83+
output.assume_init()
84+
}
85+
}
86+
87+
/// Apply and assign the results into the producer `into`, which should have the same
88+
/// size as the other inputs.
89+
///
90+
/// The producer should have assignable items as dictated by the `AssignElem` trait,
91+
/// for example `&mut R`.
92+
pub fn par_apply_assign_into<R, Q>(self, into: Q, f: impl Fn($($p::Item,)* ) -> R + Sync + Send)
93+
where Q: IntoNdProducer<Dim=D>,
94+
Q::Item: AssignElem<R> + Send,
95+
Q::Output: Send,
96+
{
97+
self.and(into)
98+
.par_apply(move |$($p, )* output_| {
99+
output_.assign_elem(f($($p ),*));
100+
});
101+
}
102+
);
66103
}
67104
)+
68105
}
69106
}
70107

71108
zip_impl! {
72-
[P1],
73-
[P1 P2],
74-
[P1 P2 P3],
75-
[P1 P2 P3 P4],
76-
[P1 P2 P3 P4 P5],
77-
[P1 P2 P3 P4 P5 P6],
109+
[true P1],
110+
[true P1 P2],
111+
[true P1 P2 P3],
112+
[true P1 P2 P3 P4],
113+
[true P1 P2 P3 P4 P5],
114+
[false P1 P2 P3 P4 P5 P6],
78115
}

src/zip/mod.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@
99
#[macro_use]
1010
mod zipmacro;
1111

12+
use std::mem::MaybeUninit;
13+
1214
use crate::imp_prelude::*;
15+
use crate::AssignElem;
1316
use crate::IntoDimension;
1417
use crate::Layout;
1518
use crate::NdIndex;
1619

1720
use crate::indexes::{indices, Indices};
18-
use crate::layout::LayoutPriv;
1921
use crate::layout::{CORDER, FORDER};
2022

2123
/// Return if the expression is a break value.
@@ -579,6 +581,7 @@ pub struct Zip<Parts, D> {
579581
layout: Layout,
580582
}
581583

584+
582585
impl<P, D> Zip<(P,), D>
583586
where
584587
D: Dimension,
@@ -735,6 +738,12 @@ where
735738
self.dimension[unroll_axis] = inner_len;
736739
FoldWhile::Continue(acc)
737740
}
741+
742+
pub(crate) fn uninitalized_for_current_layout<T>(&self) -> Array<MaybeUninit<T>, D>
743+
{
744+
let is_f = !self.layout.is(CORDER) && self.layout.is(FORDER);
745+
Array::maybe_uninit(self.dimension.clone().set_f(is_f))
746+
}
738747
}
739748

740749
/*
@@ -982,6 +991,42 @@ macro_rules! map_impl {
982991
dimension: self.dimension,
983992
}
984993
}
994+
995+
/// Apply and collect the results into a new array, which has the same size as the
996+
/// inputs.
997+
///
998+
/// If all inputs are c- or f-order respectively, that is preserved in the output.
999+
///
1000+
/// Restricted to functions that produce copyable results for technical reasons; other
1001+
/// cases are not yet implemented.
1002+
pub fn apply_collect<R>(self, f: impl FnMut($($p::Item,)* ) -> R) -> Array<R, D>
1003+
where R: Copy,
1004+
{
1005+
// To support non-Copy elements, implementation of dropping partial array (on
1006+
// panic) is needed
1007+
let mut output = self.uninitalized_for_current_layout::<R>();
1008+
self.apply_assign_into(&mut output, f);
1009+
unsafe {
1010+
output.assume_init()
1011+
}
1012+
}
1013+
1014+
/// Apply and assign the results into the producer `into`, which should have the same
1015+
/// size as the other inputs.
1016+
///
1017+
/// The producer should have assignable items as dictated by the `AssignElem` trait,
1018+
/// for example `&mut R`.
1019+
pub fn apply_assign_into<R, Q>(self, into: Q, mut f: impl FnMut($($p::Item,)* ) -> R)
1020+
where Q: IntoNdProducer<Dim=D>,
1021+
Q::Item: AssignElem<R>
1022+
{
1023+
self.and(into)
1024+
.apply(move |$($p, )* output_| {
1025+
output_.assign_elem(f($($p ),*));
1026+
});
1027+
}
1028+
1029+
9851030
);
9861031

9871032
/// Split the `Zip` evenly in two.

src/zip/zipmacro.rs

+6
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ macro_rules! azip {
122122
$(.and($prod))*
123123
.$apply(|$first_pat, $($pat),*| $body)
124124
};
125+
126+
// Unindexed with one or more producer, no loop body
127+
(@build $apply:ident $first_prod:expr $(, $prod:expr)* $(,)?) => {
128+
$crate::Zip::from($first_prod)
129+
$(.and($prod))*
130+
};
125131
// catch-all rule
126132
(@build $($t:tt)*) => { compile_error!("Invalid syntax in azip!()") };
127133
($($t:tt)*) => {

0 commit comments

Comments
 (0)