Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CowArray feature #632

Merged
merged 12 commits into from May 30, 2019
98 changes: 98 additions & 0 deletions src/data_traits.rs
Expand Up @@ -16,6 +16,7 @@ use crate::{
Dimension,
RawViewRepr,
ViewRepr,
CowRepr,
OwnedRepr,
OwnedRcRepr,
OwnedArcRepr,
Expand Down Expand Up @@ -412,3 +413,100 @@ unsafe impl<A> DataOwned for OwnedArcRepr<A> {
self
}
}

unsafe impl<'a, A> RawData for CowRepr<'a, A> {
type Elem = A;
fn _data_slice(&self) -> Option<&[A]> {
match self {
CowRepr::View(view) => view._data_slice(),
CowRepr::Owned(data) => data._data_slice(),
}
}
private_impl!{}
}

unsafe impl<'a, A> RawDataMut for CowRepr<'a, A>
where A: Clone
{
#[inline]
fn try_ensure_unique<D>(array: &mut ArrayBase<Self, D>)
where Self: Sized,
D: Dimension
{
match array.data {
CowRepr::View(_) => {
let owned = array.to_owned();
array.data = CowRepr::Owned(owned.data);
array.ptr = owned.ptr;
array.dim = owned.dim;
array.strides = owned.strides;
}
CowRepr::Owned(_) => {}
}
}

#[inline]
fn try_is_unique(&mut self) -> Option<bool> {
Some(self.is_owned())
}
}

unsafe impl<'a, A> RawDataClone for CowRepr<'a, A>
where A: Clone
{
unsafe fn clone_with_ptr(&self, ptr: *mut Self::Elem) -> (Self, *mut Self::Elem) {
match self {
CowRepr::View(view) => {
let (new_view, ptr) = view.clone_with_ptr(ptr);
(CowRepr::View(new_view), ptr)
},
CowRepr::Owned(data) => {
let (new_data, ptr) = data.clone_with_ptr(ptr);
(CowRepr::Owned(new_data), ptr)
},
}
}

#[doc(hidden)]
unsafe fn clone_from_with_ptr(&mut self, other: &Self, ptr: *mut Self::Elem) -> *mut Self::Elem {
match (&mut *self, other) {
(CowRepr::View(self_), CowRepr::View(other)) => {
self_.clone_from_with_ptr(other, ptr)
},
(CowRepr::Owned(self_), CowRepr::Owned(other)) => {
self_.clone_from_with_ptr(other, ptr)
},
(_, CowRepr::Owned(other)) => {
let (cloned, ptr) = other.clone_with_ptr(ptr);
*self = CowRepr::Owned(cloned);
ptr
},
(_, CowRepr::View(other)) => {
let (cloned, ptr) = other.clone_with_ptr(ptr);
*self = CowRepr::View(cloned);
ptr
}
}
}
}

unsafe impl<'a, A> Data for CowRepr<'a, A> {
#[inline]
fn into_owned<D>(self_: ArrayBase<CowRepr<'a, A>, D>) -> ArrayBase<OwnedRepr<Self::Elem>, D>
where
A: Clone,
D: Dimension,
{
match self_.data {
CowRepr::View(_) => self_.to_owned(),
CowRepr::Owned(data) => ArrayBase {
data,
ptr: self_.ptr,
dim: self_.dim,
strides: self_.strides,
},
}
}
}

unsafe impl<'a, A> DataMut for CowRepr<'a, A> where A: Clone {}
55 changes: 55 additions & 0 deletions src/impl_cow.rs
@@ -0,0 +1,55 @@
// Copyright 2019 ndarray developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use crate::imp_prelude::*;

/// Methods specific to `ArrayCow`.
///
/// ***See also all methods for [`ArrayBase`]***
///
/// [`ArrayBase`]: struct.ArrayBase.html
impl<'a, A, D> ArrayCow<'a, A, D>
where
D: Dimension,
{
pub fn is_view(&self) -> bool {
self.data.is_view()
}

pub fn is_owned(&self) -> bool {
self.data.is_owned()
}
}

impl<'a, A, D> From<ArrayView<'a, A, D>> for ArrayCow<'a, A, D>
where
D: Dimension,
{
fn from(view: ArrayView<'a, A, D>) -> ArrayCow<'a, A, D> {
ArrayBase {
data: CowRepr::View(view.data),
ptr: view.ptr,
dim: view.dim,
strides: view.strides,
}
}
}

impl<'a, A, D> From<Array<A, D>> for ArrayCow<'a, A, D>
where
D: Dimension,
{
fn from(array: Array<A, D>) -> ArrayCow<'a, A, D> {
ArrayBase {
data: CowRepr::Owned(array.data),
ptr: array.ptr,
dim: array.dim,
strides: array.strides,
}
}
}
36 changes: 36 additions & 0 deletions src/lib.rs
Expand Up @@ -205,6 +205,7 @@ mod imp_prelude {
DataShared,
RawViewRepr,
ViewRepr,
CowRepr,
Ix, Ixs,
};
pub use crate::dimension::DimensionExt;
Expand Down Expand Up @@ -1230,6 +1231,20 @@ pub type ArcArray<A, D> = ArrayBase<OwnedArcRepr<A>, D>;
/// and so on.
pub type Array<A, D> = ArrayBase<OwnedRepr<A>, D>;

/// An array with copy-on-write behavior.
///
/// An `ArrayCow` represents either a uniquely owned array or a view of an
/// array. The `'a` corresponds to the lifetime of the view variant.
///
/// Array views have all the methods of an array (see [`ArrayBase`][ab]).
///
/// See also [`ArcArray`](type.ArcArray.html), which also provides
/// copy-on-write behavior but has a reference-counted pointer to the data
/// instead of either a view or a uniquely owned copy.
///
/// [ab]: struct.ArrayBase.html
pub type ArrayCow<'a, A, D> = ArrayBase<CowRepr<'a, A>, D>;

/// A read-only array view.
///
/// An array view represents an array or a part of it, created from
Expand Down Expand Up @@ -1374,6 +1389,24 @@ impl<A> ViewRepr<A> {
}
}

pub enum CowRepr<'a, A> {
View(ViewRepr<&'a A>),
Owned(OwnedRepr<A>),
}

impl<'a, A> CowRepr<'a, A> {
pub fn is_view(&self) -> bool {
match self {
CowRepr::View(_) => true,
CowRepr::Owned(_) => false,
}
}

pub fn is_owned(&self) -> bool {
!self.is_view()
}
}

mod impl_clone;

mod impl_constructors;
Expand Down Expand Up @@ -1494,6 +1527,9 @@ mod impl_views;
// Array raw view methods
mod impl_raw_views;

// Copy-on-write array methods
mod impl_cow;

/// A contiguous array shape of n dimensions.
///
/// Either c- or f- memory ordered (*c* a.k.a *row major* is the default).
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Expand Up @@ -25,6 +25,7 @@ pub use crate::{
Array,
ArcArray,
RcArray,
ArrayCow,
ArrayView,
ArrayViewMut,
RawArrayView,
Expand Down
116 changes: 116 additions & 0 deletions tests/array.rs
Expand Up @@ -1891,3 +1891,119 @@ fn array_macros() {
let empty2: Array<f32, Ix2> = array![[]];
assert_eq!(empty2, array![[]]);
}

#[cfg(test)]
mod array_cow_tests {
use super::*;
use ndarray::Data;

fn is_content_identical<A, S1, S2, D>(arr1: &ArrayBase<S1, D>, arr2: &ArrayBase<S2, D>) -> bool
where A: Clone + PartialEq,
S1: Data<Elem=A>,
S2: Data<Elem=A>,
D: Dimension
{
arr1.iter().zip(arr2.iter()).all(|(x1, x2)| x1 == x2)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason why you can't use the good old assert_eq!? Your first usage looks like

assert!(is_content_identical(&arr_cow, &expected_arr));

and I don't understand why assert_eq! can't do the job.

}

#[test]
fn test_is_view() {
let arr: Array<i32, Ix2> = array![[1, 2], [3, 4]];
let arr_cow = ArrayCow::<i32, Ix2>::from(arr.view());
assert!(arr_cow.is_view());
let arr_cow = ArrayCow::<i32, Ix2>::from(arr.clone());
assert!(!arr_cow.is_view());
}

#[test]
fn test_is_owned() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's only a suggestion, but I would merge test_is_view and test_is_owned because one is simply the reverse of the other,

let arr: Array<i32, Ix2> = array![[1, 2], [3, 4]];
let arr_cow = ArrayCow::<i32, Ix2>::from(arr.clone());
assert!(arr_cow.is_owned());
let arr_cow = ArrayCow::<i32, Ix2>::from(arr.view());
assert!(!arr_cow.is_owned());
}

#[test]
fn test_element_mutation() {
let arr: Array2<i32> = array![[1, 2], [3, 4]];
let mut arr_cow = ArrayCow::<i32, Ix2>::from(arr.view());
arr_cow[(1, 1)] = 2;
let expected_arr: Array2<i32> = array![[1, 2], [3, 2]];
assert!(arr_cow.is_owned());
assert!(is_content_identical(&arr_cow, &expected_arr));

let mut arr_cow = ArrayCow::<i32, Ix2>::from(arr.clone());
let prev_ptr = arr_cow.as_ptr();
arr_cow[(1, 1)] = 2;
assert_eq!(arr_cow.as_ptr(), prev_ptr);
assert!(is_content_identical(&arr_cow, &expected_arr));
}

#[test]
fn test_clone() {
let arr: Array2<i32> = array![[1, 2], [3, 4]];
let arr_cow = ArrayCow::<i32, Ix2>::from(arr.view());
let arr_cow_clone = arr_cow.clone();
assert!(arr_cow_clone.is_view());
assert!(is_content_identical(&arr_cow, &arr_cow_clone));
assert_eq!(arr_cow.dim(), arr_cow_clone.dim());
assert_eq!(arr_cow.strides(), arr_cow_clone.strides());

let arr_cow = ArrayCow::<i32, Ix2>::from(arr.clone());
let arr_cow_clone = arr_cow.clone();
assert!(arr_cow_clone.is_owned());
assert!(is_content_identical(&arr_cow, &arr_cow_clone));
assert_eq!(arr_cow.dim(), arr_cow_clone.dim());
assert_eq!(arr_cow.strides(), arr_cow_clone.strides());
}

#[test]
fn test_clone_from() {
let arr: Array2<i32> = array![[1, 2], [3, 4]];
let other_arr: Array2<i32> = array![[11, 12], [13, 14]];

fn perform_checks(arr1: &ArrayCow<i32, Ix2>, arr2: &ArrayCow<i32, Ix2>) {
assert!(is_content_identical(arr1, arr2));
assert_eq!(arr1.dim(), arr2.dim());
assert_eq!(arr1.strides(), arr2.strides());
}

let arr_cow_src = ArrayCow::<i32, Ix2>::from(arr.view());
let mut arr_cow_dst = ArrayCow::<i32, Ix2>::from(other_arr.clone());
arr_cow_dst.clone_from(&arr_cow_src);
assert!(arr_cow_dst.is_view());
perform_checks(&arr_cow_src, &arr_cow_dst);

let arr_cow_src = ArrayCow::<i32, Ix2>::from(arr.view());
let mut arr_cow_dst = ArrayCow::<i32, Ix2>::from(other_arr.view());
arr_cow_dst.clone_from(&arr_cow_src);
assert!(arr_cow_dst.is_view());
perform_checks(&arr_cow_src, &arr_cow_dst);

let arr_cow_src = ArrayCow::<i32, Ix2>::from(arr.clone());
let mut arr_cow_dst = ArrayCow::<i32, Ix2>::from(other_arr.view());
arr_cow_dst.clone_from(&arr_cow_src);
assert!(arr_cow_dst.is_owned());
perform_checks(&arr_cow_src, &arr_cow_dst);

let arr_cow_src = ArrayCow::<i32, Ix2>::from(arr.clone());
let mut arr_cow_dst = ArrayCow::<i32, Ix2>::from(other_arr.clone());
arr_cow_dst.clone_from(&arr_cow_src);
assert!(arr_cow_dst.is_owned());
perform_checks(&arr_cow_src, &arr_cow_dst);
}

#[test]
fn test_into_owned() {
let arr: Array2<i32> = array![[1, 2], [3, 4]];
let cont_arr = ArrayCow::<i32, Ix2>::from(arr.view()).into_owned();
assert!(is_content_identical(&arr, &cont_arr));

let cont_arr = ArrayCow::<i32, Ix2>::from(arr.clone());
let prev_ptr = cont_arr.as_ptr();
let cont_arr = cont_arr.into_owned();
assert_eq!(cont_arr.as_ptr(), prev_ptr);
assert!(is_content_identical(&arr, &cont_arr));
}
}