Array

The array is a simple concept over the data in oneDAL. It represents a storage that:

  1. Holds the data allocated inside it or references to the external data. The data are organized as one homogeneous and contiguous memory block.

  2. Contains information about the memory block’s size.

  3. Supports both immutable and mutable data.

  4. Provides an ability to change the data state from immutable to mutable one.

  5. Holds ownership information on the data (see the data ownership requirements section).

  6. Ownership information on the data can be shared between several arrays. It is possible to create a new array from another one without any data copies.

Usage example

The following listing provides a brief introduction to the array API and an example of basic usage scenario:

#include <CL/sycl.hpp>
#include <iostream>
#include <string>
#include "oneapi/dal/array.hpp"

using namespace oneapi;

void print_property(const std::string& description, const auto& property) {
   std::cout << description << ": " << property << std::endl;
}

int main() {
   sycl::queue queue { sycl::default_selector() };

   constexpr std::int64_t data_count = 4;
   const float data[] = { 1.0f, 2.0f, 3.0f, 4.0f };

   // Creating an array from immutable user-defined memory
   auto arr_data = dal::array<float>::wrap(data, data_count);

   // Creating an array from internally allocated memory filled by ones
   auto arr_ones = dal::array<float>::full(queue, data_count, 1.0f);

   print_property("Is arr_data mutable", arr_data.has_mutable_data()); // false
   print_property("Is arr_ones mutable", arr_ones.has_mutable_data()); // true

   // Creating new array from arr_data without data copy - they share ownership information.
   dal::array<float> arr_mdata = arr_data;

   print_property("arr_mdata elements count", arr_mdata.get_count()); // equal to data_count
   print_property("Is arr_mdata mutable", arr_mdata.has_mutable_data()); // false

   /// Copying data inside arr_mdata to new mutable memory block.
   /// arr_data still refers to the original data pointer.
   arr_mdata.need_mutable_data(queue);

   print_property("Is arr_data mutable", arr_data.has_mutable_data()); // false
   print_property("Is arr_mdata mutable", arr_mdata.has_mutable_data()); // true

   queue.submit([&](sycl::handler& cgh){
      auto mdata = arr_mdata.get_mutable_data();
      auto cones = arr_ones.get_data();
      cgh.parallel_for<class array_addition>(sycl::range<1>(data_count), [=](sycl::id<1> idx) {
         mdata[idx[0]] += cones[idx[0]];
      });
   }).wait();

   std::cout << "arr_mdata values: ";
   for(std::int64_t i = 0; i < arr_mdata.get_count(); i++) {
      std::cout << arr_mdata[i] << ", ";
   }
   std::cout << std::endl;

   return 0;
}

Data ownership requirements

The array shall support the following requirements on the internal data management:

  1. An array shall own two properties representing raw pointers to the data:

    • data for a pointer to immutable data block

    • mutable_data for a pointer to mutable data block (see the programming interface)

  2. If an array owns mutable data, both properties shall point to the same memory block.

  3. If an array owns immutable data, mutable_data shall be nullptr.

  4. An array shall store the number of elements in the block it owns and shall update the count property when a new memory block is assigned to the array.

  5. An array shall store a pointer to the ownership structure of the data:

    • The reference count indicating how many array objects refer to the same memory block.

    • The deleter object used to free the memory block when reference count is zero.

  6. An array shall create the ownership structure for a new memory block not associated with such structure.

  7. An array shall decrement the number of references to the memory block when the array goes out of the scope. If the number of references is zero, the array shall call the deleter on this memory block and free the ownership structure.

  8. An array shall store the pointer to the ownership structure created by another array when they share the data. An array shall increment the reference count for it to be equal to the number of array objects sharing the same data.

Programming interface

All types and functions in this section shall be declared in the oneapi::dal namespace and be available via inclusion of the oneapi/dal/array.hpp header file.

All the array class methods can be divided into several groups:

  1. Constructors that are used to create an array from external, mutable or immutable memory.

  2. Constructors and assignment operators that are used to create an array that shares its data with another one.

  3. The group of reset() methods that are used to re-assign an array to another external memory block.

  4. The group of reset() methods that are used to re-assign an array to an internally allocated memory block.

  5. The methods that are used to access the data.

  6. Static methods that provide simplified ways to create an array either from external memory or by allocating it within a new object.

template <typename Data>
class array {
public:
    using data_t = Data;

public:
    static array<Data> empty(const sycl::queue& queue,
                             std::int64_t count,
                             const sycl::usm::alloc& alloc = sycl::usm::alloc::shared);

    template <typename Element>
    static array<Data> full(sycl::queue& queue,
                            std::int64_t count,
                            Element&& element,
                            const sycl::usm::alloc& alloc = sycl::usm::alloc::shared);

    static array<Data> zeros(sycl::queue& queue,
                             std::int64_t count,
                             const sycl::usm::alloc& alloc = sycl::usm::alloc::shared);

    static array<Data> wrap(Data* data,
                            std::int64_t count,
                            const sycl::vector_class<sycl::event>& dependencies = {});

    static array<Data> wrap(const Data* data,
                            std::int64_t count,
                            const sycl::vector_class<sycl::event>& dependencies = {});

public:
    array();

    array(const array<Data>& other);

    array(array<Data>&& other);

    template <typename Deleter>
    explicit array(const sycl::queue& queue,
                   Data* data,
                   std::int64_t count,
                   Deleter&& deleter,
                   const sycl::vector_class<sycl::event>& dependencies = {});

    template <typename ConstDeleter>
    explicit array(const sycl::queue& queue,
                   const Data* data,
                   std::int64_t count,
                   ConstDeleter&& deleter,
                   const sycl::vector_class<sycl::event>& dependencies = {});

    template <typename RefData, typename ExtData>
    explicit array(const array<RefData>& ref, ExtData* data, std::int64_t count);

    array<Data> operator=(const array<Data>& other);

    array<Data> operator=(array<Data>&& other);

    Data* get_mutable_data() const;

    const Data* get_data() const noexcept;

    bool has_mutable_data() const noexcept;

    array& need_mutable_data(sycl::queue& queue,
                             const sycl::usm::alloc& alloc = sycl::usm::alloc::shared);

    std::int64_t get_count() const noexcept;

    std::int64_t get_size() const noexcept;

    void reset();

    void reset(const sycl::queue& queue,
               std::int64_t count,
               const sycl::usm::alloc& alloc = sycl::usm::alloc::shared);

    template <typename Deleter>
    void reset(Data* data,
               std::int64_t count,
               Deleter&& deleter,
               const sycl::vector_class<sycl::event>& dependencies = {});

    template <typename ConstDeleter>
    void reset(const Data* data,
               std::int64_t count,
               ConstDeleter&& deleter,
               const sycl::vector_class<sycl::event>& dependencies = {});


    template <typename RefData>
    void reset(const array<RefData>& ref, Data* data, std::int64_t count);

    template <typename RefData>
    void reset(const array<RefData>& ref, const Data* data, std::int64_t count);

    const Data& operator[](std::int64_t index) const noexcept;
};
template<typename Data>
class array
Template Parameters

Data – The type of the memory block elements within the array. \(Data\) can represent any type.

Public Static Methods

static array<Data> empty(const sycl::queue &queue, std::int64_t count, const sycl::usm::alloc &alloc = sycl::usm::alloc::shared)

Allocates a new memory block for mutable data, does not initialize it, creates a new array instance by passing a pointer to the memory block. The array shall own the memory block (for details, see data ownership requirements).

Parameters
  • queue – The SYCL* queue object.

  • count – The number of elements of type \(Data\) to allocate memory for.

  • alloc – The kind of USM to be allocated.

Preconditions
count > 0
template<typename Element>
static array<Data> full(sycl::queue &queue, std::int64_t count, Element &&element, const sycl::usm::alloc &alloc = sycl::usm::alloc::shared)

Allocates a new memory block for mutable data, fills it with a scalar value, creates a new array instance by passing a pointer to the memory block. The array shall own the memory block (for details, see data ownership requirements).

Template Parameters

Element – The type from which array elements of type \(Data\) can be constructed.

Parameters
  • queue – The SYCL* queue object.

  • count – The number of elements of type \(Data\) to allocate memory for.

  • element – The value that is used to fill a memory block.

  • alloc – The kind of USM to be allocated.

Preconditions
count > 0
Elements of type Data are constructible from the Element type.
static array<Data> zeros(sycl::queue &queue, std::int64_t count, const sycl::usm::alloc &alloc = sycl::usm::alloc::shared)

Allocates a new memory block on mutable data, fills it with zeros, creates a new array instance by passing a pointer to the memory block. The array shall own the memory block (for details, see data ownership requirements).

Parameters
  • queue – The SYCL* queue object.

  • count – The number of elements of type \(Data\) to allocate memory for.

  • alloc – The kind of USM to be allocated.

Preconditions
count > 0
static array<Data> wrap(Data *data, std::int64_t count, const sycl::vector_class<sycl::event> &dependencies = {})

Creates a new array instance by passing the pointer to externally-allocated memory block for mutable data. It is the responsibility of the calling application to free the memory block as the array shall not free it when the reference count is zero.

Parameters
  • data – The pointer to externally-allocated memory block.

  • count – The number of elements of type \(Data\) in the memory block.

  • dependencies – Events indicating availability of the \(data\) for reading or writing.

Preconditions
data != nullptr
count > 0
static array<Data> wrap(const Data *data, std::int64_t count, const sycl::vector_class<sycl::event> &dependencies = {})

Creates a new array instance by passing the pointer to externally-allocated memory block for immutable data. It is the responsibility of the calling application to free the memory block as the array shall not free it when the reference count is zero.

Parameters
  • data – The pointer to externally-allocated memory block.

  • count – The number of elements of type \(Data\) in the memory block.

  • dependencies – Events indicating availability of the \(data\) for reading or writing.

Preconditions
data != nullptr
count > 0

Constructors

array()

Creates a new instance of the class without memory allocation: mutable_data and data pointers shall be set to nullptr, count shall be to zero; the pointer to the ownership structure shall be set to nullptr.

array(const array<Data> &other)

Creates a new array instance that shares an ownership with \(other\) on its memory block.

array(array<Data> &&other)

Moves data, mutable_data pointers, count, and pointer to the ownership structure in \(other\) to the new array instance.

template<typename Deleter>
array(const sycl::queue &queue, Data *data, std::int64_t count, Deleter &&deleter, const sycl::vector_class<sycl::event> &dependencies = {})

Creates a new array instance which owns a memory block of externally-allocated mutable data. The ownership structure shall be created for a block, the input \(deleter\) shall be assigned to it.

Template Parameters

Deleter – The type of a deleter used to free the \(data\). The deleter shall provide void operator()(Data*) member function.

Parameters
  • queue – The SYCL* queue object.

  • data – The pointer to externally-allocated memory block.

  • count – The number of elements of type \(Data\) in the memory block.

  • deleter – The object used to free \(data\).

  • dependencies – Events that indicate when \(data\) becomes ready to be read or written.

template<typename ConstDeleter>
array(const sycl::queue &queue, const Data *data, std::int64_t count, ConstDeleter &&deleter, const sycl::vector_class<sycl::event> &dependencies = {})

Creates a new array instance which owns a memory block of externally-allocated immutable data. The ownership structure shall be created for a block, the input \(deleter\) shall be assigned to it.

Template Parameters

ConstDeleter – The type of a deleter used to free the \(data\). The deleter shall implement void operator()(const Data*) member function.

Parameters
  • queue – The SYCL* queue object.

  • data – The pointer to externally-allocated memory block.

  • count – The number of elements of type \(Data\) in the \(data\).

  • deleter – The object used to free \(data\).

  • dependencies – Events indicating availability of the \(data\) for reading or writing.

template<typename RefData, typename ExtData>
array(const array<RefData> &ref, ExtData *data, std::int64_t count)

An aliasing constructor: creates a new array instance that stores \(data\) pointer, assigns the pointer to the ownership structure of \(ref\) to the new instance. Array shall return \(data\) pointer as its mutable or immutable block depending on the \(ExtData\) type.

Template Parameters
  • RefData – The type of elements in the referenced array.

  • ExtData – Either \(Data\) or \(const Data\) type.

Parameters
  • ref – The array which shares ownership structure with created one.

  • data – Mutable or immutable unmanaged pointer hold by created array.

  • count – The number of elements of type \(Data\) in the \(data\).

Preconditions
std::is_same_v<ExtData, const Data> || std::is_same_v<ExtData, Data>

Public Methods

array<Data> operator=(const array<Data> &other)

Replaces the data, mutable_data pointers, count, and pointer to the ownership structure in the array instance by the values in \(other\).

Postconditions
data == other.data
mutable_data == other.mutable_data
count == other.count
array<Data> operator=(array<Data> &&other)

Swaps the values of data, mutable_data pointers, count, and pointer to the ownership structure in the array instance and \(other\).

bool has_mutable_data() const noexcept

Returns whether array contains mutable_data or not.

array &need_mutable_data(sycl::queue &queue, const sycl::usm::alloc &alloc = sycl::usm::alloc::shared)

Returns mutable_data, if array contains it. Otherwise, allocates a memory block for mutable data and fills it with the data stored at data. Creates the ownership structure for allocated memory block and stores the pointer.

Parameters
  • queue – The SYCL* queue object.

  • alloc – The kind of USM to be allocated.

Postconditions
void reset()

Resets ownership structure pointer to nullptr, sets count to zero, data and mutable_data to nullptr.

void reset(const sycl::queue &queue, std::int64_t count, const sycl::usm::alloc &alloc = sycl::usm::alloc::shared)

Allocates a new memory block for mutable data, does not initialize it, creates ownership structure for this block, assigns the structure inside the array. The array shall own allocated memory block.

Parameters
  • queue – The SYCL* queue object.

  • count – The number of elements of type \(Data\) to allocate memory for.

  • alloc – The kind of USM to be allocated.

template<typename Deleter>
void reset(Data *data, std::int64_t count, Deleter &&deleter, const sycl::vector_class<sycl::event> &dependencies = {})

Creates the ownership structure for memory block of externally-allocated mutable data, assigns input \(deleter\) object to it, sets data and mutable_data pointers to this block.

Template Parameters

Deleter – The type of a deleter used to free the \(data\). The deleter shall implement void operator()(Data*) member function.

Parameters
  • data – The mutable memory block pointer to be assigned inside the array.

  • count – The number of elements of type \(Data\) into the block.

  • deleter – The object used to free \(data\).

  • dependencies – Events indicating availability of the \(data\) for reading or writing.

template<typename ConstDeleter>
void reset(const Data *data, std::int64_t count, ConstDeleter &&deleter, const sycl::vector_class<sycl::event> &dependencies = {})

Creates the ownership structure for memory block of externally-allocated immutable data, assigns input \(deleter\) object to it, sets data pointer to this block.

Template Parameters

ConstDeleter – The type of a deleter used to free. The deleter shall implement void operator()(const Data*)` member function.

Parameters
  • data – The immutable memory block pointer to be assigned inside the array.

  • count – The number of elements of type \(Data\) into the block.

  • deleter – The object used to free \(data\).

  • dependencies – Events indicating availability of the \(data\) for reading or writing.

template<typename RefData>
void reset(const array<RefData> &ref, Data *data, std::int64_t count)

Initializes data and mutable_data with data pointer, count with input \(count\) value, initializes the pointer to ownership structure with the one from ref. Array shall return \(data\) pointer as its mutable block.

Template Parameters

RefData – The type of elements in the referenced array.

Parameters
  • ref – The array which is used to share ownership structure with current one.

  • data – Mutable unmanaged pointer to be assigned to the array.

  • count – The number of elements of type \(Data\) in the \(data\).

template<typename RefData>
void reset(const array<RefData> &ref, const Data *data, std::int64_t count)

Initializes data with data pointer, count with input \(count\) value, initializes the pointer to ownership structure with the one from ref. Array shall return \(data\) pointer as its immutable block.

Template Parameters

RefData – The type of elements in the referenced array.

Parameters
  • ref – The array which is used to share ownership structure with current one.

  • data – Immutable unmanaged pointer to be assigned to the array.

  • count – The number of elements of type \(Data\) in the \(data\).

const Data &operator[](std::int64_t index) const noexcept

Provides a read-only access to the elements of array. Shall not perform boundary checks.

Properties

Data *mutable_data

The pointer to the memory block holding mutable data.

Getter & Setter
Data * get_mutable_data() const
Invariants
mutable_data != nullptr if has_mutable_data() && count > 0
const Data *data

The pointer to the memory block holding immutable data.

Getter & Setter
const Data * get_data() const noexcept
Invariants
data != nullptr if count > 0
if has_mutable_data() == true then data == mutable_data
std::int64_t count

The number of elements of type \(Data\) in a memory block.

Getter & Setter
std::int64_t get_count() const noexcept
std::int64_t size

The size of memory block in bytes.

Getter & Setter
std::int64_t get_size() const noexcept
Invariants
size == count * sizeof(Data)