stdgpu Documentation

repository·master·Indexed 23 days ago

https://github.com/stotko/stdgpu

A lightweight C++17 library providing STL-like data structures (vector, unordered_map, unordered_set, deque, queue, stack, bitset, and atomic) optimized for GPU execution. It supports CUDA, OpenMP, and HIP (experimental) backends, offering both a high-level agnostic API and a low-level native API for custom kernels. The library includes utilities for device memory management, iterator helpers for Thrust algorithm integration, and safety-checked host-to-device data transfers.

Tokens
7K
Snippets
14
Records
28
Agent score
78%

What's inside stdgpu

  1. Overview of stdgpu features and design philosophy

    master

    stdgpu is a lightweight C++17 library providing generic GPU data structures designed for fast and reliable data management. Unlike libraries that focus primarily on algorithms (like Thrust or ArrayFire), stdgpu focuses on providing STL-like containers that enable the development of flexible GPU algorithms.

    Key Features

    • Multi-backend support: Works with CUDA, OpenMP, and HIP (experimental).
    • Dual-level API:
      • High-level, agnostic functions: e.g., insert(begin, end) for writing shared C++ code across different backends.
      • Low-level, native functions: e.g., find(key) for writing custom CUDA kernels.
    • Interoperability: Compatible with thrust GPU algorithms.
    • Minimal dependencies: Lightweight C++17 implementation.
  2. Overview of stdgpu features and data structures

    master

    stdgpu is a lightweight C++17 library providing generic, STL-like data structures optimized for the GPU. It supports CUDA, OpenMP, and HIP (experimental) backends and is designed for fast and reliable data management rather than just algorithm implementation.

    Supported GPU Data Structures

    • atomic & atomic_ref: Atomic primitive types and references.
    • bitset: Space-efficient bit array.
    • deque: Dynamically sized double-ended queue.
    • queue & stack: Container adapters.
    • unordered_map & unordered_set: Hashed collections of unique keys and key-value pairs.
    • vector: Dynamically sized contiguous array.

    Helper Functionality

    The library also provides headers for common utilities including algorithm, bit, contract, cstddef, execution, functional, iterator, limits, memory, mutex, numeric, ranges, type_traits, and utility.

  3. Link your project against the stdgpu::stdgpu target

    master

    Regardless of how stdgpu is integrated (pre-installed, submodule, or FetchContent), you must link your project target to the stdgpu::stdgpu target. This step is crucial as it propagates all necessary properties, including include directories, compile flags, and library files, to your project.

    target_link_libraries(your_project PUBLIC stdgpu::stdgpu)
  4. Understand the two classes of stdgpu examples

    master

    The examples/ directory provides two distinct types of usage patterns depending on where your code is executing:

    1. Host code with device support: These examples are designed to be compiled and run by both the host and device compiler. They demonstrate functionality that complements GPU data structures and containers, typically used in standard application logic that interacts with GPU resources.

    2. Device only code: These examples are intended for use within native code (such as custom CUDA kernels). Because they are meant to run exclusively on the device, they must be compiled by the device compiler. These examples are organized into backend-specific subdirectories (e.g., CUDA-specific) to account for the requirements of the chosen backend.

  5. Define Host and Device Container Objects

    master

    To bridge the gap between CPU and GPU programming, stdgpu provides a way to wrap semantically coherent data (like arrays) into container classes. Instead of managing raw pointers and sizes manually, you can define classes that encapsulate both the data and its metadata (like size).

    When designing these classes for stdgpu, you should use a factory-based pattern rather than standard constructors to handle the differences between host and device memory allocation. This avoids common issues like double-free errors and ensures compatibility with GPU memory management.

    Implementation Pattern

    1. Avoid standard constructors for allocation: Use static factory methods like createDeviceObject and createHostObject.
    2. Manual Lifecycle Management: Use static methods like destroyDeviceObject and destroyHostObject to clean up resources.
    3. Data Encapsulation: The object should internally manage the array and its size, so users don't need to pass the size as a separate parameter during operations.
    class MyHostDeviceObjectClass
    {
        public:
            MyHostDeviceObjectClass()
            {
                this->_array = nullptr;
                this->_size = 0;
            }
    
            [[nodiscard]] static MyHostDeviceObjectClass createDeviceObject(const int size)
            {
                MyHostDeviceObjectClass result;
    
                result._array = createDeviceArray<float>(size);
                result._size = size;
    
                return result;
            }
    
            static void destroyDeviceObject(MyHostDeviceObjectClass& device_object)
            {
                destroyDeviceArray<float>(device_object._array);
                device_object._size = 0;
            }
    
            [[nodiscard]] static MyHostDeviceObjectClass createHostObject(const int size)
            {
                MyHostDeviceObjectClass result;
    
                result._array = createHostArray<float>(size);
                result._size = size;
    
                return result;
            }
    
            static void destroyHostObject(MyHostDeviceObjectClass& host_object)
            {
                destroyHostArray<float>(host_object._array);
                host_object._size = 0;
            }
    
            void function(const int parameter) const
            {
                // Do something useful with array
            }
    
        private:
            float* _array;
            int _size;
    };
  6. Integrate stdgpu using a pre-installed version

    master

    If stdgpu is already installed on your system, you can use CMake's find_package to locate it. Once found, link your project target against the stdgpu::stdgpu target to automatically propagate include directories, compile flags, and library files.

    find_package(stdgpu REQUIRED)
    
    add_library(your_project ...)
    # ...
    
    # Link your project against stdgpu
    target_link_libraries(your_project PUBLIC stdgpu::stdgpu)
  7. Build stdgpu from source

    master

    stdgpu uses CMake for cross-platform building. You can build it using direct CMake commands or the provided helper scripts in the tools/ directory. The following example demonstrates building in Release mode and installing to a local bin directory.

    1. Configure

    Create a build directory and evaluate the configuration.

    Direct Command:

    mkdir build
    cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=bin

    Using Script:

    bash tools/backend/configure_cuda.sh Release

    2. Compile

    Compile the library and its components. Use --parallel to speed up the process.

    Direct Command:

    cmake --build build --config Release --parallel 8

    Using Script:

    bash tools/build.sh Release

    Verify the build by running unit tests. This requires STDGPU_BUILD_TESTS to be ON (default).

    Direct Command:

    cmake -E chdir build ctest -V -C Release

    Using Script:

    bash tools/run_tests.sh Release

    4. Install (Optional)

    Install the compiled version to your system.

    Direct Command:

    cmake --install build --config Release

    Using Script:

    bash tools/install.sh Release
    # Example workflow using direct commands
    mkdir build
    cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=bin
    cmake --build build --config Release --parallel 8
    cmake --install build --config Release
  8. Integrate stdgpu using CMake FetchContent

    master

    For a modern CMake approach that automatically downloads the source, use FetchContent. This method allows you to specify the repository and tag directly in your build script. Similar to the submodule method, you should set STDGPU_BUILD_* flags to OFF to avoid building non-essential components.

    include(FetchContent)
    
    FetchContent_Declare(
        stdgpu
        GIT_REPOSITORY https://github.com/stotko/stdgpu.git
        GIT_TAG        master
    )
    
    # Exclude unneeded parts from the build
    set(STDGPU_BUILD_EXAMPLES OFF CACHE INTERNAL "")
    set(STDGPU_BUILD_BENCHMARKS OFF CACHE INTERNAL "")
    set(STDGPU_BUILD_TESTS OFF CACHE INTERNAL "")
    
    FetchContent_MakeAvailable(stdgpu)
    
    add_library(your_project ...)
    # ...
    
    # Link your project against stdgpu
    target_link_libraries(your_project PUBLIC stdgpu::stdgpu)
  9. Use stdgpu iterator API to simplify Thrust algorithm calls

    master

    When working with raw device arrays (allocated via createDeviceArray), using standard Thrust syntax requires verbose pointer casting and explicit size management. The stdgpu/iterator.h API provides stdgpu::device_begin, stdgpu::device_end, and their const counterparts to automatically handle pointer casting and size querying. This allows you to pass raw device pointers to Thrust algorithms with a syntax similar to C++11/14 STL containers.

    #include <thrust/sort.h>
    #include <stdgpu/memory.h>
    #include <stdgpu/iterator.h>
    
    float* device_array = createDeviceArray<float>(1000);
    
    // Fill it with something useful
    
    thrust::sort(stdgpu::device_begin(device_array), stdgpu::device_end(device_array));
    
    destroyDeviceArray<float>(device_array);
  10. Integrate stdgpu as a git submodule

    master

    To build stdgpu from source alongside your project using a git submodule, use add_subdirectory. You can reduce build times by disabling unneeded components like examples, benchmarks, and tests using the STDGPU_BUILD_* cache variables before calling add_subdirectory.

    # Exclude unneeded parts from the build
    set(STDGPU_BUILD_EXAMPLES OFF CACHE INTERNAL "")
    set(STDGPU_BUILD_BENCHMARKS OFF CACHE INTERNAL "")
    set(STDGPU_BUILD_TESTS OFF CACHE INTERNAL "")
    
    add_subdirectory(stdgpu)
    
    add_library(your_project ...)
    # ...
    
    # Link your project against stdgpu
    target_link_libraries(your_project PUBLIC stdgpu::stdgpu)