Memory ownership and gko::array#
gko::array<T> is the basic owning container for contiguous data in Ginkgo. Every higher-level container (vectors, matrices, solver workspaces) is built on it. This page explains how array owns memory, what its copy and move semantics are, and how it interacts with executors. View semantics (wrapping memory you do not own) are covered separately in Views and zero-copy wrapping.
What gko::array stores#
array<T> is templated by the storage type. The type must be a POD type (plain old data): a built-in type, or a struct of built-in types. Examples include double, float, int, std::complex<double>. The type may not be const-qualified; use a const array<T> to express read-only access to the whole container, rather than array<const T>.
Attention
Some functionality (notably fill) is not available for all storage types. The restriction is enforced at link time. If you use an unsupported type, the error message will look like:
undefined reference to gko::array<T>::fill(T)
The supported types are documented in the Resizing and filling section below.
Construction#
An array is constructed from an executor and the number of stored elements:
std::shared_ptr<const gko::Executor> exec = gko::OmpExecutor::create();
gko::size_type size = 10;
gko::array<double> a(exec, size);
Attention
After construction with a size only, the stored memory is not initialized. Reading from a before writing to it is undefined behaviour.
To create an array that is already filled with data, pass an initializer list instead of a size. The initializer list lives in CPU memory, but the created array will place the elements into the memory space of the executor:
gko::array<double> b(exec, {1.0, 2.0, 3.0});
An alternative is to provide begin and end iterators from any host container. The data is copied into the array:
std::vector<double> std_c{1.0, 2.0, 3.0};
gko::array<double> c(exec, std_c.begin(), std_c.end());
All four common construction forms together:
// Size only — uninitialised
gko::array<double> a(exec, size);
// Initializer list
gko::array<double> b(exec, {1.0, 2.0, 3.0});
// Iterator pair from host container
std::vector<double> std_c{1.0, 2.0, 3.0};
gko::array<double> c(exec, std_c.begin(), std_c.end());
// Default construction
gko::array<double> d; // no executor, zero size
gko::array<double> e(exec); // executor, zero size
The default-constructed array d is not associated with any executor. The empty array e is associated with exec but stores nothing. The difference matters for copy and move operations.
Accessing the data#
The primary access point is the pair get_data() / get_const_data():
double* ptr = a.get_data(); // non-const only
const double* const_ptr = a.get_const_data(); // always available
get_data() is unavailable on const arrays. Both return nullptr if the array is empty.
Note
The pattern of having get_<something> and get_const_<something> (mutable and const accessors) is standard throughout Ginkgo. For const objects, only the const accessor is callable, making accidental mutation near-impossible.
Attention
The returned pointers address the executor’s memory space. If the array lives on a CudaExecutor, the pointer is a device pointer that is not dereferenceable on the host. Dereference the pointer only on the device that owns it.
Element count and executor:
gko::size_type n = a.get_size(); // number of stored elements
auto e = a.get_executor(); // shared_ptr to the executor; nullptr if default-constructed
Copy and move semantics#
A key rule: copy and move assignment never change the executor of the destination array. This makes cross-device memory movement simple and explicit.
Operation |
Behaviour |
|---|---|
|
Copy. |
|
Cross-executor copy: copies |
|
Move. |
The executor-conservation rule means that assigning across executors performs a cross-device copy automatically:
gko::array<double> a_host(host_exec, {1.0, 2.0, 3.0});
gko::array<double> a_gpu(gpu_exec, 3); // gpu array, uninitialised
a_gpu = a_host; // copies host → device; a_gpu remains on gpu_exec
Cross-executor transfers#
The canonical way to move data from host to device (or back) is via the cross-executor constructor:
auto a_host = gko::array<double>(host_exec, {1.0, 2.0, 3.0});
auto a_gpu = gko::array<double>(gpu_exec, a_host); // copy host → device
The same constructor copies back to the host: gko::array<double> a_back{host_exec, a_gpu};.
To change the executor associated with an existing array, use set_executor. If the new executor differs from the current one, the data is automatically migrated:
gko::array<double> a(omp_exec, {1.0, 2.0, 3.0});
a.set_executor(hip_exec); // values [1.0, 2.0, 3.0] are now in GPU memory
Const-correctness#
Prefer const array<T> over array<const T>. A const array<T> disables get_data(), fill(), resize_and_reset(), and set_executor(), enforcing the read-only contract at compile time. array<const T> is not supported.
Resizing and filling#
Resize an array and discard its contents with resize_and_reset:
a.resize_and_reset(new_size); // content is undefined after this call
Attention
resize_and_reset throws if the array has no executor (default-constructed without one) or if the array is a non-owning view. See Views and zero-copy wrapping for the view restriction.
Reset the size to zero:
a.clear(); // get_data() returns nullptr afterward
Set every element to the same value with fill:
gko::array<double> a(exec, 10);
a.fill(1.5); // all 10 elements are now 1.5
fill is only available for the following storage types:
Real and complex floating-point:
float,double,std::complex<float>,std::complex<double>Index and size types:
int32,int64,size_type
Using fill with any other type results in a link-time error. This restriction exists because fill dispatches to a GPU kernel that must be instantiated at compile time for each supported type.
What array does not do#
array provides raw contiguous storage, not arithmetic. It does not support element-wise addition, scaling, or dot products. For arithmetic on numeric vectors, use gko::matrix::Dense<T>, a LinOp built on top of array that exposes full linear algebra operations.
array does not support copy-on-write semantics, reference counting of the stored data, or RAII for non-POD types. For owning LinOp objects, use std::shared_ptr<gko::LinOp>.
Wrapping memory you do not own#
Owning memory is the common case, but array also supports a non-owning view
mode that wraps memory you already control (a buffer from deal.II, MFEM, Kokkos,
or a raw user pointer) without copying it. The lifetime contract, integration
patterns (deal.II distributed vector, raw user buffers, Kokkos View,
zero-copy CSR construction), and “when to copy instead of view” guidance live on
their own page:
See also
The Executor model: what an
arrayis bound to.API reference:
gko::array