Skip to content

Memory Spaces and Views, Execution Spaces and Parallel Loops

Memory Spaces and Views

The primary data structure in Kokkos is called a View. A View is similar to an array in the standard C++ library, but unlike an array, a View can be allocated at compile time or run time.

A View is allocated inside of a memory space. The memory space is passed as a template parameter to the View constructor. Usually the memory space is set once at the start of a program and passed as a template argument to any functions that allocate Views. The most common memory spaces are:
1. Kokkos::HostSpace – CPU memory
2. Kokkos::CudaSpace – NVIDIA GPU memory
3. Kokkos::HIPSpace – AMD GPU memory

The following are examples of creating View objects:

using memory_space = Kokkos::HostSpace;

// Integer View of size (10, 3) with first dimension allocated at run time.
Kokkos::View<int*[3], memory_space> myView0(“myViewLabel0”, 10);

// Intger View of size (10, 3) with both dimensions allocated at compile time.
Kokkos::View<int[10][3], memory_space> myView1(“myViewLabel1”);

// Double View of size (3, 4) with both dimensions allocated at run time.
Kokkos::View<double**, memory_space> myView2(“myViewLabel2”, 3, 4);

// View of size (3, 4, 5) with all dimensions allocated at run time.
// This is a View of non-primitive types.
using cdouble = Kokkos::complex<double>;
Kokkos::View<cdouble***, memory_space> myView3(“myViewLabel3”, 3, 4, 5); 

Of note:
1. int*[3]: This tells the compiler to create a View of integers with one runtime-sized dimension (the *) and one compile-time sized dimension of size 3 (the [3]). A View can have up to seven dimensions, but runtime-sized dimensions must always come before compile-time sized dimensions.
2. memory_space: The memory space in which the view is allocated. In this example, the View is allocated in host memory.
3. myView0("myViewLabel0", 10): myView0 is the name of the object. myViewLabel0 is the label associated with the View. Labels are helpful for debugging - usually when an error occurs with a View, the message will print error: View (label="my_label") ... which can direct you to the exact View that caused the error. The numbers that come after the label are the runtime-sized dimensions, from left the right. For myView0, the single runtime-sized dimension is of size 10.
4. Kokkos automatically tracks references to Views and will automatically deallocate them when no longer referenced. In other words, you do not need to free dynamically allocated Kokkos Views.

Execution Spaces and Parallel Loops

Views are operated on inside of execution spaces in parallel loops. Like memory spaces, the execution space is usually set once at the start of a program and passed as a template argument to any functions that operate on Views. The most common execution spaces are:
1. Kokkos::DefaultHostExecutionSpace: The default execution space in host memory - usually paired with Kokkos::HostSpace.
2. Kokkos::Serial: Execute loops in serial.
3. Kokkos::OpenMP: Execute loops using OpenMP.
4. Kokkos::Cuda: Execute loops using a NVIDIA GPU.
5. Kokkos::HIP: Execute loops using an AMD GPU.

The following is an example of a parallel for loop. A parallel for loop is the equivalent of a for loop in C++ except that, if using a backend such as OpenMP or a GPU, the loop will be executed in parallel rather than in serial, speeding up the time to completion.

using execution_space = Kokkos::DefaultHostExecutionSpace;
Kokkos::parallel_for(
    "example_parallel_for",
    Kokkos::RangePolicy<execution_space>( 0, myView0.extent(0) ),
    KOKKOS_LAMBDA( const int i ) {
        myView0(i, 0) = i % 2;
        myView0(i, 1) = i * 3;
        myView0(i, 2) = -1;
    } );
Kokkos::fence();

Of note:
1. Like a View, a parallel for loop also takes a label.
2. Kokkos::RangePolicy<execution_space>( 0, myView0.extent(0) ): This defines the range the loop will operate over and the execution space it will operate in. In this case, it is from 0 to the length of the first dimension of myView. It is equivalent to for(int i = 0; i < myView0.extent(0); i++).
3. KOKKOS_LAMBDA( const int i ): This defines the variable that will be assigned a value between 0 and myView0.extent(0) in the loop.
4. The code inside of the curly braces after the lambda is the body of the loop. In this example, the values in the View are set using 2D indexing.
5. The call to Kokkos::fence() says, do not proceed until the loop has completed. If the loop is executing on the device, host-side code after the loop may execute while the loop is still completing on the device. This call is not always necessary. But if the values in the View are used immediately after the parallel loop (such as for MPI communication), we want to make sure all values are set before continuing.

A parallel_for loop is not the only type of parallel loop in Kokkos. Kokkos has parallel_reduce for reduction operations on a View and parallel_scan for prefix sums. More information about these loops can be found in the Kokkos documentation.

Important Information About Memory Spaces

On many computers, the host and the device do not share memory. If a View is created in device memory, that View cannot be accessed from the host. To transfer the data in Views between host and device memory, Kokkos::deep_copy or Kokkos::create_mirror_view_and_copy must be used. The Kokkos::create_mirror_view_and_copy function is used in the example program on the next page.

Additional Resources

  1. Official Kokkos Documentation
  2. In-depth information about views
  3. The Cabana library is an excellent code base to refer to for examples of View creation and parallel loops. Search the repository for instances of Kokkos::parallel_for.