legate::LogicalStore#
-
class LogicalStore#
A multi-dimensional data container.
LogicalStore
is a multi-dimensional data container for fixed-size elements. Stores are internally partitioned and distributed across the system. By default, Legate clients need not create nor maintain the partitions explicitly, and the Legate runtime is responsible for managing them. Legate clients can control how stores should be partitioned for a given task by attaching partitioning constraints to the task (see the constraint module for partitioning constraint APIs).Each
LogicalStore
object is a logical handle to the data and is not immediately associated with a physical allocation. To access the data, a client must “map” the store to a physical store (PhysicalStore
). A client can map a store by passing it to a task, in which case the task body can see the allocation, or callingLogicalStore::get_physical_store()
, which gives the client a handle to the physical allocation (seePhysicalStore
for details about physical stores).Normally, a
LogicalStore
gets a fixedShape
upon creation. However, there is a special type of logical stores called “unbound” stores whose shapes are unknown at creation time. (seeRuntime
for the logical store creation API.) The shape of an unbound store is determined by a task that first updates the store; upon the submission of the task, theLogicalStore
becomes a normal store. Passing an unbound store as a read-only argument or requesting aPhysicalStore
of an unbound store are invalid.One consequence due to the nature of unbound stores is that querying the shape of a previously unbound store can block the client’s control flow for an obvious reason; to know the shape of the
LogicalStore
whoseShape
was unknown at creation time, the client must wait until the updater task to finish. However, passing a previously unbound store to a downstream operation can be non-blocking, as long as the operation requires no changes in the partitioning and mapping for theLogicalStore
.Public Functions
-
std::uint32_t dim() const#
Returns the number of dimensions of the store.
- Returns:
The number of dimensions
-
bool has_scalar_storage() const#
Indicates whether the store’s storage is optimized for scalars.
- Returns:
true The store is backed by a scalar storage
- Returns:
false The store is a backed by a normal region storage
-
bool overlaps(const LogicalStore &other) const#
Indicates whether this store overlaps with a given store.
- Returns:
true The stores overlap
- Returns:
false The stores are disjoint
-
const tuple<std::uint64_t> &extents() const#
Returns the extents of the store.
The call can block if the store is unbound
- Returns:
The store’s extents
-
std::size_t volume() const#
Returns the number of elements in the store.
The call can block if the store is unbound
- Returns:
The number of elements in the store
-
bool unbound() const#
Indicates whether the store is unbound.
- Returns:
true
if the store is unbound,false
otherwise
-
bool transformed() const#
Indicates whether the store is transformed.
- Returns:
true
if the store is transformed,false
otherwise
-
LogicalStore reinterpret_as(const Type &type) const#
Reinterpret the underlying data of a
LogicalStore
byte-for-byte as another type.The size and alignment of the new type must match that of the existing type.
The reinterpreted store will share the same underlying storage as the original, and therefore any writes to one will also be reflected in the other. No type conversions of any kind are performed across the stores, the bytes are interpreted as-is. In effect, if one were to model a
LogicalStore
as a pointer to an array, then this routine is equivalent toreinterpret_cast
-ing the pointer.Example:
// Create a store of some shape filled with int32 data. constexpr std::int32_t minus_one = -1; const auto store = runtime->create_store(shape, legate::int32()); runtime->issue_fill(store, legate::Scalar{minus_one}); // Reinterpret the underlying data as unsigned 32-bit integers. auto reinterp_store = store.reinterpret_as(legate::uint32()); // Our new store should have the same type as it was reinterpreted to. ASSERT_EQ(reinterp_store.type(), legate::uint32()); // Our old store still has the same type though. ASSERT_EQ(store.type(), legate::int32()); // Both stores should refer to the same underlying storage. ASSERT_TRUE(store.equal_storage(reinterp_store)); const auto phys_store = reinterp_store.get_physical_store(); const auto acc = phys_store.read_accessor<std::uint32_t, 1>(); std::uint32_t interp_value; // Need to memcpy here in order to do a "true" bitcast. A reinterpret_cast() may or may not // result in the compilers generating the conversion, since type-punning with // reinterpret_cast is UB. std::memcpy(&interp_value, &minus_one, sizeof(minus_one)); for (auto it = legate::PointInRectIterator<1>{phys_store.shape<1>()}; it.valid(); ++it) { ASSERT_EQ(acc[*it], interp_value); }
- Parameters:
type – The new type to interpret the data as.
- Returns:
The reinterpreted store.
- Throws:
std::invalid_argument – If the size (in bytes) of the new type does not match that of the old type.
std::invalid_argument – If the alignment of the new type does not match that of the old type.
- LogicalStore promote(
- std::int32_t extra_dim,
- std::size_t dim_size
Adds an extra dimension to the store.
Value of
extra_dim
decides where a new dimension should be added, and each dimension \(i\), where \(i\) >=extra_dim
, is mapped to dimension \(i+1\) in a returned store. A returned store provides a view to the input store where the values are broadcasted along the new dimension.For example, for a 1D store
A
contains[1, 2, 3]
,A.promote(0, 2)
yields a store equivalent to:[[1, 2, 3], [1, 2, 3]]
whereas
A.promote(1, 2)
yields:[[1, 1], [2, 2], [3, 3]]
The call can block if the store is unbound
- Parameters:
extra_dim – Position for a new dimension
dim_size – Extent of the new dimension
- Throws:
std::invalid_argument – When
extra_dim
is not a valid dimension name- Returns:
A new store with an extra dimension
-
LogicalStore project(std::int32_t dim, std::int64_t index) const#
Projects out a dimension of the store.
Each dimension \(i\), where \(i\) >
dim
, is mapped to dimension \(i-1\) in a returned store. A returned store provides a view to the input store where the values are on hyperplane \(x_\mathtt{dim} = \mathtt{index}\).For example, if a 2D store
A
contains[[1, 2], [3, 4]]
,A.project(0, 1)
yields a store equivalent to[3, 4]
, whereasA.project(1, 0)
yields[1, 3]
.The call can block if the store is unbound
- Parameters:
dim – Dimension to project out
index – Index on the chosen dimension
- Throws:
std::invalid_argument – If
dim
is not a valid dimension name orindex
is out of bounds- Returns:
A new store with one fewer dimension
-
LogicalStore slice(std::int32_t dim, Slice sl) const#
Slices a contiguous sub-section of the store.
For example, consider a 2D store
A
:[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
A slicing
A.slice(0, legate::Slice{1})
yields[[4, 5, 6], [7, 8, 9]]
The result store will look like this on a different slicing call
A.slice(1, legate::Slice{legate::Slice::OPEN, 2})
:[[1, 2], [4, 5], [7, 8]]
Finally, chained slicing calls
A.slice(0, legate::Slice{1}) .slice(1, legate::Slice{legate::Slice::OPEN, 2})
results in:
[[4, 5], [7, 8]]
The call can block if the store is unbound
- Parameters:
dim – Dimension to slice
sl –
Slice
descriptor
- Throws:
std::invalid_argument – If
dim
is not a valid dimension name- Returns:
A new store that corresponds to the sliced section
-
LogicalStore transpose(std::vector<std::int32_t> &&axes) const#
Reorders dimensions of the store.
Dimension \(i\)i of the resulting store is mapped to dimension
axes[i]
of the input store.For example, for a 3D store
A
[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
transpose calls
A.transpose({1, 2, 0})
andA.transpose({2, 1, 0})
yield the following stores, respectively:[[[1, 5], [2, 6]], [[3, 7], [4, 8]]]
[[[1, 5], [3, 7]], [[2, 6], [4, 8]]]
The call can block if the store is unbound
- Parameters:
axes – Mapping from dimensions of the resulting store to those of the input
- Throws:
std::invalid_argument – If any of the following happens: 1) The length of
axes
doesn’t match the store’s dimension; 2)axes
has duplicates; 3) Any axis inaxes
is an invalid axis name.- Returns:
A new store with the dimensions transposed
- LogicalStore delinearize(
- std::int32_t dim,
- std::vector<std::uint64_t> sizes
Delinearizes a dimension into multiple dimensions.
Each dimension \(i\) of the store, where \(i >\)
dim
, will be mapped to dimension \(i+N\) of the resulting store, where \(N\) is the length ofsizes
. A delinearization that does not preserve the size of the store is invalid.For example, consider a 2D store
A
[[1, 2, 3, 4], [5, 6, 7, 8]]
A delinearizing call
A.delinearize(1, {2, 2}))
yields:[[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
Unlike other transformations, delinearization is not an affine transformation. Due to this nature, delinearized stores can raise
legate::NonInvertibleTransformation
in places where they cannot be used.The call can block if the store is unbound
- Parameters:
dim – Dimension to delinearize
sizes – Extents for the resulting dimensions
- Throws:
std::invalid_argument – If
dim
is invalid for the store orsizes
does not preserve the extent of the chosen dimension- Returns:
A new store with the chosen dimension delinearized
- LogicalStorePartition partition_by_tiling(
- std::vector<std::uint64_t> tile_shape
Creates a tiled partition of the store.
The call can block if the store is unbound
- Parameters:
tile_shape – Shape of tiles
- Returns:
A store partition
- PhysicalStore get_physical_store(
- std::optional<mapping::StoreTarget> target = std::nullopt
Creates a
PhysicalStore
for thisLogicalStore
This call blocks the client’s control flow and fetches the data for the whole store to the current node.
When the target is
StoreTarget::FBMEM
, the data will be consolidated in the framebuffer of the first GPU available in the scope.If no
target
is given, the runtime usesStoreTarget::SOCKETMEM
if it exists andStoreTarget::SYSMEM
otherwise.If there already exists a physical store for a different memory target, that physical store will be unmapped from memory and become invalid to access.
- Parameters:
target – The type of memory in which the physical store would be created.
- Throws:
std::invalid_argument – If no memory of the chosen type is available
- Returns:
A
PhysicalStore
of theLogicalStore
-
void detach()#
Detach a store from its attached memory.
This call will wait for all operations that use the store (or any sub-store) to complete.
After this call returns, it is safe to deallocate the attached external allocation. If the allocation was mutable, the contents would be up-to-date upon the return. The contents of the store are invalid after that point.
-
void offload_to(mapping::StoreTarget target_mem)#
Offload store to specified target memory.
See also
- Parameters:
target_mem – The target memory.
-
bool equal_storage(const LogicalStore &other) const#
Determine whether two stores refer to the same memory.
This routine can be used to determine whether two seemingly unrelated stores refer to the same logical memory region, including through possible transformations in either
this
orother
.The user should note that some transformations do modify the underlying storage. For example, the store produced by slicing will not share the same storage as its parent, and this routine will return false for it:
const auto store = runtime->create_store(legate::Shape{4, 3}, legate::int64()); const auto transformed = store.slice(1, legate::Slice{-2, -1}); // Slices partition a store into a parent and sub-store which both cover distinct regions, // and hence don't share storage. ASSERT_FALSE(store.equal_storage(transformed));
Transposed stores, on the other hand, still share the same storage, and hence this routine will return true for them:
const auto store = runtime->create_store(legate::Shape{4, 3}, legate::int64()); const auto transformed = store.transpose({1, 0}); // Transposing a store doesn't modify the storage ASSERT_TRUE(store.equal_storage(transformed));
- Parameters:
other – The
LogicalStore
to compare with.- Returns:
true
if two stores cover the same underlying memory region,false
otherwise.
-
class Impl#
-
std::uint32_t dim() const#