MCPcopy Create free account
hub / github.com/arrayfire/arrayfire / genLocal

Method genLocal

src/backend/opencl/threadsMgt.hpp:174–295  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

172// BLOCK OPERATIONS, as are: matmul, etc.
173template<typename T>
174inline cl::NDRange threadsMgt<T>::genLocal(const cl::Kernel& ker) const {
175 // Performance is mainly dependend on:
176 // - reducing memory latency, by preferring a sequential read of
177 // cachelines (principally dim0)
178 // - more parallel threads --> higher occupation of available
179 // threads
180 // - more I/O operations per thread --> dims[3] indicates the #
181 // of I/Os handled by the kernel inside each thread, and outside
182 // the scope of the block scheduler
183 // High performance is achievable with occupation rates as low as
184 // 30%. Here we aim at 50%, to also cover older hardware with slower
185 // cores.
186 // https://stackoverflow.com/questions/7737772/improving-kernel-performance-by-increasing-occupancy
187 // http://www.nvidia.com/content/gtc-2010/pdfs/2238_gtc2010.pdf
188 // https://www.cvg.ethz.ch/teaching/2011spring/gpgpu/GPU-Optimization.pdf
189 // https://en.wikipedia.org/wiki/Graphics_Core_Next#SIMD_Vector_Unit
190
191 // The performance for vectors is independent from array sizes.
192 if ((d1 == 1) & (d2 == 1)) return cl::NDRange{128ULL};
193
194 // TOTAL OCCUPATION = occup(dim0) * occup(dim1) * occup(dim2).
195 // For linearized arrays, each linear block is allocated to a dim,
196 // resulting in large numbers for dim0 & dim1.
197 // - For dim2, we only return exact dividers of the array dim[3], so
198 // occup(dim2)=100%
199 // - For dim0 & dim1, we aim somewhere between 30% and 50%
200 // * Having 2 blocks filled + 1 thread in block 3 --> occup >
201 // 2/3=66%
202 // * Having 3 blocks filled + 1 thread in block 4 --> occup >
203 // 3/4=75%
204 // * Having 4 blocks filled + 1 thread in block 5 --> occup >
205 // 4/5=80%
206 constexpr unsigned OCCUPANCY_FACTOR{2U}; // at least 2 blocks filled
207
208 // NVIDIA:
209 // WG multiple = 32
210 // possible blocks = [32, 64, 96, 128, 160, 192, 224, 256, .. 1024]
211 // best performance = [32, 64, 96, 128]
212 // optimal perf = 128; any combination
213 // NIVIDA always processes full wavefronts. Allocating partial WG
214 // (<32) reduces throughput. Performance reaches a plateau from
215 // 128 with a slightly slowing for very large sizes.
216 // AMD:
217 // WG multiple = 64
218 // possible block = [16, 32, 48, 64, 128, 192, 256]
219 // best performance = [(32, low #threads) 64, 128, 256]
220 // optimal perf = (128,2,1); max 128 for 1 dimension
221 // AMD can process partial wavefronts (multiple of 16), although
222 // all threads of a full WG are allocated, only the active ones
223 // are executed, so the same number of WGs will fit a CU. When we
224 // have insufficent threads to occupy all the CU's, partial
225 // wavefronts (<64) are usefull to distribute all threads over the
226 // available CU's iso all concentrating on the 1st CU.
227 // For algorithm below:
228 // parallelThreads = [32, 64, (96 for NIVIDA), 128, (256 for AMD)]
229 constexpr unsigned minThreads{32};
230 const unsigned relevantElements{d0 * d1 * d2};
231 const unsigned WG{static_cast<unsigned>(

Callers 3

evalNodesFunction · 0.80
memcopyFunction · 0.80
copyFunction · 0.80

Calls 1

minFunction · 0.70

Tested by

no test coverage detected