Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cpp/benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ ConfigureNVBench(
SEARCH_NVBENCH search/contains_scalar.cpp search/contains_table.cpp search/search.cpp
)

# ##################################################################################################
# * scalar benchmark ------------------------------------------------------------------------------
ConfigureNVBench(SCALAR_NVBENCH scalar/scalar.cpp)

# ##################################################################################################
# * sort benchmark --------------------------------------------------------------------------------
ConfigureNVBench(
Expand Down
55 changes: 55 additions & 0 deletions cpp/benchmarks/scalar/scalar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
* SPDX-License-Identifier: Apache-2.0
*/

#include <benchmarks/common/memory_stats.hpp>

#include <cudf/scalar/scalar.hpp>
#include <cudf/utilities/memory_resource.hpp>

#include <nvbench/nvbench.cuh>

#include <cstdint>
#include <string>

namespace {

void numeric_scalar_construction(nvbench::state& state)
{
auto const is_valid = static_cast<bool>(state.get_int64("valid"));

auto const mem_stats_logger = cudf::memory_stats_logger();
state.exec(nvbench::exec_tag::sync, [&](nvbench::launch& launch) {
auto const stream = rmm::cuda_stream_view{launch.get_stream()};
[[maybe_unused]] auto scalar = cudf::numeric_scalar<int64_t>{
42, is_valid, stream, cudf::get_current_device_resource_ref()};
});
state.add_buffer_size(
mem_stats_logger.peak_memory_usage(), "peak_memory_usage", "peak_memory_usage");
}

void string_scalar_construction(nvbench::state& state)
{
auto const num_bytes = state.get_int64("num_bytes");
auto const value = std::string(static_cast<std::size_t>(num_bytes), 'a');

auto const mem_stats_logger = cudf::memory_stats_logger();
state.exec(nvbench::exec_tag::sync, [&](nvbench::launch& launch) {
auto const stream = rmm::cuda_stream_view{launch.get_stream()};
[[maybe_unused]] auto scalar = cudf::string_scalar{
value, true, stream, cudf::get_current_device_resource_ref()};
});
state.add_buffer_size(
mem_stats_logger.peak_memory_usage(), "peak_memory_usage", "peak_memory_usage");
}

} // namespace

NVBENCH_BENCH(numeric_scalar_construction)
.set_name("numeric_scalar_construction")
.add_int64_axis("valid", {0, 1});

NVBENCH_BENCH(string_scalar_construction)
.set_name("string_scalar_construction")
.add_int64_power_of_two_axis("num_bytes", {0, 4, 8, 12, 16, 20});
36 changes: 20 additions & 16 deletions cpp/doxygen/developer_guide/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,22 +403,23 @@ Use `cudf::host_span<T>` only when one of the following applies:
A `cudf::scalar` is an object that can represent a singular, nullable value of any of the types
currently supported by cudf. Each type of value is represented by a separate type of scalar class
which are all derived from `cudf::scalar`. e.g. A `numeric_scalar` holds a single numerical value,
a `string_scalar` holds a single string. The data for the stored value resides in device memory.
a `string_scalar` holds a single string.

A `list_scalar` holds the underlying data of a single list. This means the underlying data can be
any type that cudf supports. For example, a `list_scalar` representing a list of integers stores a
`cudf::column` of type `INT32`. A `list_scalar` representing a list of lists of integers stores a
`cudf::column` of type `LIST`, which in turn stores a column of type `INT32`.
A scalar owns its value and validity in an Arrow-compatible, one-row `cudf::column`. This common
storage representation includes the offsets and child hierarchy required by strings and nested
types. `scalar::as_column_view()` returns an allocation-free, non-owning `scalar_column_view`
directly over that storage.

|Value type|Scalar class|Notes|
|-|-|-|
|fixed-width|`fixed_width_scalar<T>`| `T` can be any fixed-width type|
|numeric|`numeric_scalar<T>` | `T` can be `int8_t`, `int16_t`, `int32_t`, `int64_t`, `float` or `double`|
|fixed-point|`fixed_point_scalar<T>` | `T` can be `numeric::decimal32` or `numeric::decimal64`|
|fixed-point|`fixed_point_scalar<T>` | `T` can be `numeric::decimal32`, `numeric::decimal64`, or `numeric::decimal128`|
|timestamp|`timestamp_scalar<T>` | `T` can be `timestamp_D`, `timestamp_s`, etc.|
|duration|`duration_scalar<T>` | `T` can be `duration_D`, `duration_s`, etc.|
|string|`string_scalar`| This class object is immutable|
|list|`list_scalar`| Underlying data can be any type supported by cudf |
|struct|`struct_scalar`| Children can be any types supported by cudf |

### Construction
`scalar`s can be created using either their respective constructors or using factory functions like
Expand All @@ -441,15 +442,17 @@ auto s1 = static_cast<ScalarType *>(s.get());
```

### Passing to device
Each scalar type, except `list_scalar`, has a corresponding non-owning device view class which
allows access to the value and its validity from the device. This can be obtained using the function
`get_scalar_device_view(ScalarType s)`. Note that a device view is not provided for a base scalar
object, only for the derived typed scalar class objects.
Use `scalar::as_column_view()` and `column_device_view::create()` to access a scalar from device
code. The resulting `column_device_view` has one row, so its value and validity are accessed at
index `0`. Specialized column device views may be constructed from it for nested types.

The underlying data for `list_scalar` can be accessed via `view()` method. For non-nested data,
the device view can be obtained via function `column_device_view::create(column_view)`. For nested
data, a specialized device view for list columns can be constructed via
`lists_column_device_view(column_device_view)`.
Use `scalar::as_mutable_column_view()` and `mutable_column_device_view::create()` when device code
must modify a scalar value. Scalar validity is host-authoritative: establish it separately with
`scalar::set_valid_async()` and do not mutate the null mask through the mutable column view.

Typed scalar device views and `get_scalar_device_view()` remain compatibility APIs. New code should
prefer column views so it works through the type-erased `scalar` interface and uses the same storage
and access patterns as columns.

# libcudf Policies and Design Principles

Expand Down Expand Up @@ -1035,8 +1038,9 @@ libcudf as well as Thrust algorithms. Most are defined in `include/detail/iterat

The pair iterator is used to access elements of nullable columns as a pair containing an element's
value and validity. `cudf::detail::make_pair_iterator` can be used to create a pair iterator from a
`column_device_view` or a `cudf::scalar`. `make_pair_iterator` is not available for
`mutable_column_device_view`.
`column_device_view`. Scalar callers use `scalar::as_column_view()` to create a
`column_device_view` and must retain the returned device-view owner until iterator use completes.
`make_pair_iterator` is not available for `mutable_column_device_view`.

### Null-replacement iterator

Expand Down
13 changes: 7 additions & 6 deletions cpp/include/cudf/ast/expressions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,9 @@ class generic_scalar_device_view : public cudf::detail::scalar_device_view_base
*
* @param type The data type of the value
* @param data The pointer to the data in device memory
* @param is_valid The pointer to the bool in device memory that indicates the
* validity of the stored value
* @param is_valid Pointer to the validity bitmask in device memory
*/
generic_scalar_device_view(data_type type, void const* data, bool* is_valid)
generic_scalar_device_view(data_type type, void const* data, bitmask_type const* is_valid)
: cudf::detail::scalar_device_view_base(type, is_valid), _data(data)
{
}
Expand All @@ -228,11 +227,13 @@ class generic_scalar_device_view : public cudf::detail::scalar_device_view_base
*
* @param type The data type of the value
* @param data The pointer to the data in device memory
* @param is_valid The pointer to the bool in device memory that indicates the
* validity of the stored value
* @param is_valid Pointer to the validity bitmask in device memory
* @param size The size of the string in bytes
*/
generic_scalar_device_view(data_type type, void const* data, bool* is_valid, size_type size)
generic_scalar_device_view(data_type type,
void const* data,
bitmask_type const* is_valid,
size_type size)
: cudf::detail::scalar_device_view_base(type, is_valid), _data(data), _size(size)
{
}
Expand Down
41 changes: 41 additions & 0 deletions cpp/include/cudf/column/scalar_column_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,45 @@ struct scalar_column_view : private column_view {
[[nodiscard]] column_view const& as_column_view() const noexcept { return *this; }
};

/**
* @brief A non-owning, mutable view of one row of device column storage.
*
* This view is intended for updating scalar values. Scalar validity is managed separately by the
* owning scalar and the null mask must not be modified through this view.
*
* @ingroup column_classes
*/
struct mutable_scalar_column_view : private mutable_column_view {
/**
* @brief Construct a `mutable_scalar_column_view` from a `mutable_column_view`.
*
* @throws cudf::logic_error if the column view does not have exactly one element.
*
* @param view The mutable column view to construct from
*/
explicit mutable_scalar_column_view(mutable_column_view view)
: mutable_column_view(std::move(view))
{
CUDF_EXPECTS(
this->size() == 1, "A scalar column view must have exactly one element.", std::logic_error);
}

using mutable_column_view::data;
using mutable_column_view::head;
using mutable_column_view::is_empty;
using mutable_column_view::null_count;
using mutable_column_view::null_mask;
using mutable_column_view::nullable;
using mutable_column_view::offset;
using mutable_column_view::size;
using mutable_column_view::type;

/**
* @brief Returns the underlying `mutable_column_view`.
*
* @return A reference to the underlying mutable view
*/
[[nodiscard]] mutable_column_view const& as_mutable_column_view() const noexcept { return *this; }
};

} // namespace CUDF_EXPORT cudf
10 changes: 5 additions & 5 deletions cpp/include/cudf/detail/calendrical_month_sequence.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
#pragma once

#include <cudf/column/column.hpp>
#include <cudf/column/column_device_view.cuh>
#include <cudf/column/column_factories.hpp>
#include <cudf/column/column_view.hpp>
#include <cudf/detail/datetime_ops.cuh>
#include <cudf/scalar/scalar.hpp>
#include <cudf/scalar/scalar_device_view.cuh>
#include <cudf/utilities/memory_resource.hpp>
#include <cudf/utilities/traits.hpp>

Expand All @@ -33,8 +33,8 @@ struct calendrical_month_sequence_functor {
// Return empty column if n = 0
if (n == 0) return cudf::make_empty_column(input.type());

auto const device_input =
get_scalar_device_view(static_cast<cudf::scalar_type_t<T>&>(const_cast<scalar&>(input)));
auto const input_view = input.as_column_view();
auto const device_input = column_device_view::create(input_view.as_column_view(), stream);
auto output_column_type = cudf::data_type{cudf::type_to_id<T>()};
auto output = cudf::make_fixed_width_column(
output_column_type, n, cudf::mask_state::UNALLOCATED, stream, mr);
Expand All @@ -43,9 +43,9 @@ struct calendrical_month_sequence_functor {
cuda::counting_iterator<size_type>{0},
cuda::counting_iterator<size_type>{n},
output->mutable_view().begin<T>(),
[initial = device_input, months] __device__(size_type i) {
[initial = *device_input, months] __device__(size_type i) {
return datetime::detail::add_calendrical_months_with_scale_back(
initial.value(), cuda::std::chrono::months{i * months});
initial.element<T>(0), cuda::std::chrono::months{i * months});
});

return output;
Expand Down
Loading
Loading