|
| Error | memAlloc (void **a_devicePtr, size_t a_numBytes) noexcept |
| | Allocate device memory (cudaMalloc / hipMalloc).
|
| |
| Error | memFree (void *a_devicePtr) noexcept |
| | Free device memory allocated with memAlloc (cudaFree / hipFree).
|
| |
| Error | memAllocManaged (void **a_ptr, size_t a_numBytes) noexcept |
| | Allocate managed (unified) memory accessible from host and device (cudaMallocManaged / hipMallocManaged).
|
| |
| Error | memAllocHost (void **a_ptr, size_t a_numBytes) noexcept |
| | Allocate page-locked (pinned) host memory (cudaHostAlloc / hipHostMalloc).
|
| |
| Error | memAllocHostMapped (void **a_ptr, size_t a_numBytes) noexcept |
| | Allocate page-locked (pinned) host memory that is additionally mapped into the device address space for zero-copy access (cudaHostAlloc with cudaHostAllocMapped / hipHostMalloc with hipHostMallocMapped). Under unified virtual addressing the returned host pointer is directly valid on the device. Free with memFreeHost.
|
| |
| Error | memFreeHost (void *a_ptr) noexcept |
| | Free page-locked host memory allocated with memAllocHost (cudaFreeHost / hipHostFree).
|
| |
| Error | memcpy (void *a_dst, const void *a_src, size_t a_numBytes, MemcpyKind a_kind) noexcept |
| | Copy memory between host and device (cudaMemcpy / hipMemcpy). Synchronous.
|
| |
| Error | getDeviceCount (int *a_count) noexcept |
| | Get the number of available GPU devices (cudaGetDeviceCount / hipGetDeviceCount).
|
| |
| Error | getLastError () noexcept |
| | Get and clear the last runtime error (cudaGetLastError / hipGetLastError).
|
| |
| Error | deviceSynchronize () noexcept |
| | Block until the device has finished all preceding work (cudaDeviceSynchronize / hipDeviceSynchronize).
|
| |
| const char * | getErrorString (Error a_error) noexcept |
| | Get the human-readable description of an error code (cudaGetErrorString / hipGetErrorString).
|
| |
| bool | isShutdownError (Error a_error) noexcept |
| | True for the errors a runtime call returns once the runtime has shut down at program exit (cudaErrorCudartUnloading or cudaErrorContextIsDestroyed under CUDA, hipErrorDeinitialized or hipErrorContextIsDestroyed under HIP).
|
| |
| const char * | getErrorHint (Error a_error) noexcept |
| | What to do about an error that means the binary has no code this GPU can run: the binary was built for another architecture, or carries only PTX that the driver, older than the toolkit, cannot compile (cudaErrorUnsupportedPtxVersion or cudaErrorNoKernelImageForDevice under CUDA, hipErrorNoBinaryForGpu under HIP).
|
| |
| bool | deviceAvailable () noexcept |
| | Whether a GPU is visible to the runtime.
|
| |
| MemoryResource & | memoryResource () noexcept |
| | The memory a query's data must live in for parallelFor() and transform() to read it.
|
| |
| template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class F > |
| void | parallelFor (std::size_t a_n, const F &a_f) |
| | Run a functor on every index: a_f(i) for i = 0, ..., a_n - 1, in no particular order.
|
| |
| template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out > |
| void | transform (const Query &a_query, PODSpan< In > a_in, PODSpan< Out > a_out) |
| | Run a query on every input in place: a_out[i] = a_query(a_in[i]), with no allocation and no copies.
|
| |
| template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out > |
| void | transform (const Query &a_query, const Pool &a_pool, const PODVector< In > &a_in, const PODVector< Out > &a_out) |
| | Run a query on every input of a pool-resident array, writing the results into another array in the same pool: out[i] = a_query(in[i]), with no allocation and no copies.
|
| |
| template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out > |
| void | transform (const Query &a_query, const std::vector< In > &a_in, std::vector< Out > &a_out) |
| | Run a query on every input, from host memory to host memory: a_out[i] = a_query(a_in[i]).
|
| |
Backend-neutral GPU runtime API surface (CUDA or HIP).
Thin aliases and inline forwarders over the backend runtime (cuda* under CUDA, hip* under HIP) covering exactly the surface EBGeometry uses: memory allocation/copy/free, device enumeration, error retrieval, and synchronization. The two runtimes mirror each other's signatures 1:1 for this surface, so every wrapper is a one-line forwarder.
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class F >
| void EBGeometry::GPU::parallelFor |
( |
std::size_t |
a_n, |
|
|
const F & |
a_f |
|
) |
| |
|
inline |
Run a functor on every index: a_f(i) for i = 0, ..., a_n - 1, in no particular order.
In a CUDA or HIP build, a kernel runs the functor once per index (one thread each, in blocks of BlockSize threads) on the current device, and the call returns once it has finished. Every runtime call is checked with EBGEOMETRY_GPU_CHECK, which aborts with the failing call on an error, and a visible device is required (see deviceAvailable()). The functor reaches the kernel by value, so anything it reads or writes must be memory the device can access.
In a host build the functor is copied once, as a kernel launch would copy it, and run in a loop. The functor must be trivially copyable in both builds, which is checked at compile time. A grid holds at most 2^31 - 1 blocks, so a_n may not exceed (2^31 - 1) * BlockSize; a larger count aborts with a message, in both builds.
- Template Parameters
-
| BlockSize | Threads per block: a multiple of 64 (an AMD wavefront, and two NVIDIA warps), at most 1024. The kernel carries it as launch bounds, so the compiler can give each thread the registers that block size allows. |
| MinResidentThreads | In a CUDA build, the threads to keep resident on a multiprocessor, at least, which fixes the register budget (see DefaultMinResidentThreads); at most 2048, 0 for no minimum. |
| F | Trivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a std::size_t. |
- Parameters
-
| [in] | a_n | Number of indices. |
| [in] | a_f | The functor. |
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
| void EBGeometry::GPU::transform |
( |
const Query & |
a_query, |
|
|
const Pool & |
a_pool, |
|
|
const PODVector< In > & |
a_in, |
|
|
const PODVector< Out > & |
a_out |
|
) |
| |
|
inline |
Run a query on every input of a pool-resident array, writing the results into another array in the same pool: out[i] = a_query(in[i]), with no allocation and no copies.
The span overload, with the memory checked first. A PODVector is an offset into a pool, so this overload takes the pool both arrays live in and binds them against its base. It aborts with a message, in every build, unless the pool is frozen (as rebasedView() requires of a device-accessible pool) and its resource is device-accessible: a mirror into memoryResource(), or a pool built in managed memory. A pool in hostMemoryResource() is therefore rejected in a host build too, where memoryResource() is the emulated device resource. A PODVector holds an offset, not its pool, so membership can be checked only as far as both arrays ending inside the pool's used bytes, which also aborts otherwise; that an array really belongs to this pool (or a mirror of its pool) is the caller's responsibility. The inputs are bound read-only. Freezing forbids reservations, not writes, so the results can be written into a frozen pool, in place over the inputs or into a disjoint array as for the span overload. Arrays in two different pools, or not in a pool at all, go through the span overload.
- Template Parameters
-
| BlockSize | Threads per block; see parallelFor(). |
| MinResidentThreads | Threads to keep resident, in a CUDA build; see parallelFor(). |
| Query | Trivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a const In&. |
| In | Input type. |
| Out | Result type: the query's result type. |
- Parameters
-
| [in] | a_query | The query. |
| [in] | a_pool | The frozen, device-accessible pool holding both arrays. |
| [in] | a_in | The inputs. |
| [in] | a_out | The results' array, as many elements as a_in; its elements are written. |
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
| void EBGeometry::GPU::transform |
( |
const Query & |
a_query, |
|
|
const std::vector< In > & |
a_in, |
|
|
std::vector< Out > & |
a_out |
|
) |
| |
|
inline |
Run a query on every input, from host memory to host memory: a_out[i] = a_query(a_in[i]).
A convenience over the span overload. In a CUDA or HIP build it allocates device memory for the inputs and the results, copies the inputs in, runs the query in a kernel, copies the results into a_out and frees, so timing it includes two allocations and two copies; time the span or pool overload to measure the query itself. In a host build the query runs in a loop directly on the vectors. a_out is not resized: it must already hold as many elements as a_in, which aborts with a message otherwise, and can be reused across calls. At most 2^32 - 1 inputs, which also aborts with a message otherwise. Neither the input nor the result may be bool (std::vector<bool> has no contiguous storage to copy), which is checked at compile time in both builds.
- Template Parameters
-
| BlockSize | Threads per block; see parallelFor(). |
| MinResidentThreads | Threads to keep resident, in a CUDA build; see parallelFor(). |
| Query | Trivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a const In&. |
| In | Trivially copyable input type. |
| Out | Result type: the query's result type. |
- Parameters
-
| [in] | a_query | The query. |
| [in] | a_in | The inputs. |
| [out] | a_out | The results, in input order; as many elements as a_in. |
template<unsigned int BlockSize = DefaultBlockSize, unsigned int MinResidentThreads = DefaultMinResidentThreads, class Query , class In , class Out >
| void EBGeometry::GPU::transform |
( |
const Query & |
a_query, |
|
|
PODSpan< In > |
a_in, |
|
|
PODSpan< Out > |
a_out |
|
) |
| |
|
inline |
Run a query on every input in place: a_out[i] = a_query(a_in[i]), with no allocation and no copies.
The inputs and the results stay wherever the caller put them, so this is the call to time when a query's cost is what is being measured, and the one to use for repeated queries over the same points. A span is a pointer and a count: bind a PODVector against the base of a pool mirrored into memoryResource() (inputs) or of a pool reserved there (results), or wrap managed memory or a buffer the application already owns on the device. In a host build the spans are host memory. The results may overwrite the inputs in place (the same array, when In and Out are the same type), but the two must otherwise not overlap, which aborts with a message. The query runs through parallelFor(), with its requirements.
- Template Parameters
-
| BlockSize | Threads per block; see parallelFor(). |
| MinResidentThreads | Threads to keep resident, in a CUDA build; see parallelFor(). |
| Query | Trivially copyable functor with an EBGEOMETRY_HOST_DEVICE call operator taking a const In&. |
| In | Input type, const or not. |
| Out | Result type: the query's result type, not const. |
- Parameters
-
| [in] | a_query | The query. |
| [in] | a_in | The inputs, in memory the device can read. |
| [out] | a_out | The results, in memory the device can write; as many as there are inputs. |
| constexpr unsigned int EBGeometry::GPU::DefaultMinResidentThreads = 384 |
|
inlineconstexpr |
The threads a launch asks the CUDA compiler to keep resident on each multiprocessor, at least, unless it is given another: 384, three blocks of 128, which holds a thread to at most 168 registers.
The kernel carries it as launch bounds (a minimum number of resident blocks, rounded up). Without a minimum, the compiler picks one register budget for the whole kernel, every function it calls included, and the pick jumps with small changes to code the kernel may not even run (a tape's ray kernel once went from 255 registers to 40, spilling twice as much); given one, it uses the budget the minimum allows. Not tuned for every query: the budget is a ceiling the compiler tends to fill, so a kernel that runs best with few registers may be slower under it (rays traced through a fused composition went from 56 registers to 161 and ran up to a third slower, and the renderer's frame of a mesh from 128 to 161, 10% slower). The minimum still pays where registers matter: on one desktop GPU, ray tracing through a tape took 2.5 times as long with no minimum as at 384, and 1.45 times as long at 768, while a mesh's signed distance took the same time at every setting; DeviceTuning's mesh-minus-ball tape distance in float was a fifth to a quarter faster at 768 than at 384, whatever the block size. 0 asks for no minimum. HIP reads a second launch bound differently, and a HIP build ignores it.