Open3D (C++ API)  0.20.0
Loading...
Searching...
No Matches
ParallelFor.h
Go to the documentation of this file.
1// ----------------------------------------------------------------------------
2// - Open3D: www.open3d.org -
3// ----------------------------------------------------------------------------
4// Copyright (c) 2018-2026 www.open3d.org
5// SPDX-License-Identifier: MIT
6// ----------------------------------------------------------------------------
7
8#pragma once
9
10#include <cstdint>
11#include <type_traits>
12
13#include "open3d/core/Device.h"
18
19#ifdef __CUDACC__
20#include <cuda.h>
21#include <cuda_runtime.h>
22
24#else
25#include <tbb/parallel_for.h>
26#endif
27
28#if defined(SYCL_LANGUAGE_VERSION)
30#endif
31
32namespace open3d {
33namespace core {
34
35#ifdef __CUDACC__
36
37static constexpr int64_t OPEN3D_PARFOR_BLOCK = 128;
38static constexpr int64_t OPEN3D_PARFOR_THREAD = 4;
39
41template <int64_t block_size, int64_t thread_size, typename func_t>
42__global__ void ElementWiseKernel_(int64_t n, func_t f) {
43 int64_t items_per_block = block_size * thread_size;
44 int64_t idx = blockIdx.x * items_per_block + threadIdx.x;
45#pragma unroll
46 for (int64_t i = 0; i < thread_size; ++i) {
47 if (idx < n) {
48 f(idx);
49 idx += block_size;
50 }
51 }
52}
53
55template <typename func_t>
56void ParallelForCUDA_(const Device& device, int64_t n, const func_t& func) {
57 if (device.GetType() != Device::DeviceType::CUDA) {
58 utility::LogError("ParallelFor for CUDA cannot run on device {}.",
59 device.ToString());
60 }
61 if (n == 0) {
62 return;
63 }
64
65 CUDAScopedDevice scoped_device(device);
66 int64_t items_per_block = OPEN3D_PARFOR_BLOCK * OPEN3D_PARFOR_THREAD;
67 int64_t grid_size = (n + items_per_block - 1) / items_per_block;
68
69 ElementWiseKernel_<OPEN3D_PARFOR_BLOCK, OPEN3D_PARFOR_THREAD>
70 <<<grid_size, OPEN3D_PARFOR_BLOCK, 0, core::cuda::GetStream()>>>(
71 n, func);
72 OPEN3D_GET_LAST_CUDA_ERROR("ParallelFor failed.");
73}
74
75#else
76
82template <typename func_t>
83void ParallelForCPU_(const Device& device,
84 int64_t n,
85 int64_t grain_size,
86 const func_t& func) {
87 if (!device.IsCPU()) {
88 utility::LogError("ParallelFor for CPU cannot run on device {}.",
89 device.ToString());
90 }
91 if (n == 0) {
92 return;
93 }
94
95 tbb::parallel_for(tbb::blocked_range<int64_t>(0, n, grain_size),
96 [&func](const tbb::blocked_range<int64_t>& range) {
97 for (int64_t i = range.begin(); i < range.end();
98 ++i) {
99 func(i);
100 }
101 });
102}
103
109template <typename func_t>
110void ParallelForCPU_(const Device& device, int64_t n, const func_t& func) {
111 ParallelForCPU_(device, n,
112 static_cast<int64_t>(utility::DefaultGrainSizeTBB2D()),
113 func);
114}
115
116#endif
117
118#if defined(SYCL_LANGUAGE_VERSION)
119
125template <typename func_t>
126sycl::event ParallelForSYCLImpl_(sycl::queue& queue,
127 int64_t n,
128 const func_t& func,
129 const std::vector<sycl::event>& deps) {
130 if (n == 0) {
131 return sycl::event();
132 }
133 size_t wg = core::sy::PreferredWorkGroupSize(queue.get_device());
134 const size_t global_size = ((static_cast<size_t>(n) + wg - 1) / wg) * wg;
135 sycl::nd_range<1> nd_range{sycl::range<1>(global_size), sycl::range<1>(wg)};
136 return queue.parallel_for(nd_range, deps, [=](sycl::nd_item<1> item) {
137 int64_t i = item.get_global_id(0);
138 if (i < n) {
139 func(i);
140 }
141 });
142}
143
145template <typename func_t>
146void ParallelForSYCL_(const Device& device, int64_t n, const func_t& func) {
147 if (!device.IsSYCL()) {
148 utility::LogError("ParallelFor for SYCL cannot run on device {}.",
149 device.ToString());
150 }
151 auto queue = core::sy::GetQueue(device);
152 ParallelForSYCLImpl_(queue, n, func, {}).wait_and_throw();
153}
154
160template <typename func_t>
161sycl::event ParallelForSYCL_(const Device& device,
162 int64_t n,
163 const func_t& func,
164 const std::vector<sycl::event>& deps) {
165 if (!device.IsSYCL()) {
166 utility::LogError("ParallelFor for SYCL cannot run on device {}.",
167 device.ToString());
168 }
169 auto queue = core::sy::GetQueue(device);
170 return ParallelForSYCLImpl_(queue, n, func, deps);
171}
172
173#endif
174
189template <typename func_t>
190void ParallelFor(const Device& device, int64_t n, const func_t& func) {
191#ifdef __CUDACC__
192 ParallelForCUDA_(device, n, func);
193#elif defined(SYCL_LANGUAGE_VERSION)
194 if (device.IsSYCL()) {
195 ParallelForSYCL_(device, n, func);
196 } else {
197 ParallelForCPU_(device, n, func);
198 }
199#else
200 ParallelForCPU_(device, n, func);
201#endif
202}
203
204#if defined(SYCL_LANGUAGE_VERSION)
219template <typename func_t>
220sycl::event ParallelFor(const Device& device,
221 int64_t n,
222 const func_t& func,
223 std::initializer_list<sycl::event> deps) {
224 return ParallelForSYCL_(device, n, func, std::vector<sycl::event>(deps));
225}
226
236template <typename func_t>
237void ParallelFor(sycl::queue& queue, int64_t n, const func_t& func) {
238 ParallelForSYCLImpl_(queue, n, func, {}).wait_and_throw();
239}
240
242template <typename func_t>
243sycl::event ParallelFor(sycl::queue& queue,
244 int64_t n,
245 const func_t& func,
246 const std::vector<sycl::event>& deps) {
247 return ParallelForSYCLImpl_(queue, n, func, deps);
248}
249
250template <typename func_t>
251sycl::event ParallelFor(sycl::queue& queue,
252 int64_t n,
253 const func_t& func,
254 std::initializer_list<sycl::event> deps) {
255 return ParallelFor(queue, n, func, std::vector<sycl::event>(deps));
256}
257#endif
258
305template <typename vec_func_t, typename func_t>
306void ParallelFor(const Device& device,
307 int64_t n,
308 const func_t& func,
309 const vec_func_t& vec_func) {
310#ifdef BUILD_ISPC_MODULE
311
312#ifdef __CUDACC__
313 ParallelForCUDA_(device, n, func);
314#elif defined(SYCL_LANGUAGE_VERSION)
315 if (device.IsSYCL()) {
316 ParallelForSYCL_(device, n, func);
317 } else {
318 // The work is already blocked into num_threads chunks here, so the
319 // grain size must be 1: a larger grain would exceed the range and
320 // make TBB run every chunk on a single thread.
321 int num_threads = utility::EstimateMaxThreads();
322 ParallelForCPU_(device, num_threads, /*grain_size=*/1, [&](int64_t i) {
323 int64_t start = n * i / num_threads;
324 int64_t end = std::min<int64_t>(n * (i + 1) / num_threads, n);
325 vec_func(start, end);
326 });
327 }
328#else
329 // See the comment above: the range is already one item per thread.
330 int num_threads = utility::EstimateMaxThreads();
331 ParallelForCPU_(device, num_threads, /*grain_size=*/1, [&](int64_t i) {
332 int64_t start = n * i / num_threads;
333 int64_t end = std::min<int64_t>(n * (i + 1) / num_threads, n);
334 vec_func(start, end);
335 });
336#endif
337
338#else
339
340#ifdef __CUDACC__
341 ParallelForCUDA_(device, n, func);
342#elif defined(SYCL_LANGUAGE_VERSION)
343 if (device.IsSYCL()) {
344 ParallelForSYCL_(device, n, func);
345 } else {
346 ParallelForCPU_(device, n, func);
347 }
348#else
349 ParallelForCPU_(device, n, func);
350#endif
351
352#endif
353}
354
355#ifdef BUILD_ISPC_MODULE
356
357// Internal helper macro.
358#define OPEN3D_CALL_ISPC_KERNEL_(ISPCKernel, start, end, ...) \
359 using namespace ispc; \
360 ISPCKernel(start, end, __VA_ARGS__);
361
362#else
363
364// Internal helper macro.
365#define OPEN3D_CALL_ISPC_KERNEL_(ISPCKernel, start, end, ...) \
366 utility::LogError( \
367 "ISPC module disabled. Unable to call vectorized kernel {}", \
368 OPEN3D_STRINGIFY(ISPCKernel));
369
370#endif
371
373#define OPEN3D_OVERLOADED_LAMBDA_(T, ISPCKernel, ...) \
374 [&](T, int64_t start, int64_t end) { \
375 OPEN3D_CALL_ISPC_KERNEL_( \
376 OPEN3D_CONCAT(ISPCKernel, OPEN3D_CONCAT(_, T)), start, end, \
377 __VA_ARGS__); \
378 }
379
389#define OPEN3D_VECTORIZED(ISPCKernel, ...) \
390 [&](int64_t start, int64_t end) { \
391 OPEN3D_CALL_ISPC_KERNEL_(ISPCKernel, start, end, __VA_ARGS__); \
392 }
393
407#define OPEN3D_TEMPLATE_VECTORIZED(T, ISPCKernel, ...) \
408 [&](int64_t start, int64_t end) { \
409 static_assert(std::is_arithmetic<T>::value, \
410 "Data type is not an arithmetic type"); \
411 utility::Overload( \
412 OPEN3D_OVERLOADED_LAMBDA_(bool, ISPCKernel, __VA_ARGS__), \
413 OPEN3D_OVERLOADED_LAMBDA_(uint8_t, ISPCKernel, __VA_ARGS__), \
414 OPEN3D_OVERLOADED_LAMBDA_(int8_t, ISPCKernel, __VA_ARGS__), \
415 OPEN3D_OVERLOADED_LAMBDA_(uint16_t, ISPCKernel, __VA_ARGS__), \
416 OPEN3D_OVERLOADED_LAMBDA_(int16_t, ISPCKernel, __VA_ARGS__), \
417 OPEN3D_OVERLOADED_LAMBDA_(uint32_t, ISPCKernel, __VA_ARGS__), \
418 OPEN3D_OVERLOADED_LAMBDA_(int32_t, ISPCKernel, __VA_ARGS__), \
419 OPEN3D_OVERLOADED_LAMBDA_(uint64_t, ISPCKernel, __VA_ARGS__), \
420 OPEN3D_OVERLOADED_LAMBDA_(int64_t, ISPCKernel, __VA_ARGS__), \
421 OPEN3D_OVERLOADED_LAMBDA_(float, ISPCKernel, __VA_ARGS__), \
422 OPEN3D_OVERLOADED_LAMBDA_(double, ISPCKernel, __VA_ARGS__), \
423 [&](auto&& generic, int64_t start, int64_t end) { \
424 utility::LogError( \
425 "Unsupported data type {} for calling " \
426 "vectorized kernel {}", \
427 typeid(generic).name(), \
428 OPEN3D_STRINGIFY(ISPCKernel)); \
429 })(T{}, start, end); \
430 }
431
432} // namespace core
433} // namespace open3d
Common CUDA utilities.
#define OPEN3D_GET_LAST_CUDA_ERROR(message)
Definition CUDAUtils.h:47
sycl::queue queue
Definition SYCLContext.cpp:88
Common SYCL utilities.
Definition Device.h:18
bool IsSYCL() const
Returns true iff device type is SYCL GPU.
Definition Device.h:54
bool IsCPU() const
Returns true iff device type is CPU.
Definition Device.h:48
std::string ToString() const
Returns string representation of device, e.g. "CPU:0", "CUDA:0".
Definition Device.cpp:94
sycl::queue GetQueue(const Device &device)
Definition SYCLContext.cpp:183
void ParallelForCPU_(const Device &device, int64_t n, int64_t grain_size, const func_t &func)
Definition ParallelFor.h:83
void ParallelFor(const Device &device, int64_t n, const func_t &func)
Definition ParallelFor.h:190
std::size_t & DefaultGrainSizeTBB2D() noexcept
Definition Parallel.cpp:43
int EstimateMaxThreads()
Definition Parallel.cpp:22
Definition PinholeCameraIntrinsic.cpp:16