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

Method genThreads

src/backend/cuda/threadsMgt.hpp:86–194  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

84// Generate optimal thread values
85template<typename T>
86const dim3 threadsMgt<T>::genThreads() const {
87 // Performance is mainly dependend on:
88 // - reducing memory latency, by preferring a sequential read of
89 // cachelines (principally dim0)
90 // - more parallel threads --> higher occupation of available
91 // threads
92 // - more I/O operations per thread --> dims[3] indicates the #
93 // of I/Os handled by the kernel inside each thread, and outside
94 // the scope of the block scheduler
95 // High performance is achievable with occupation rates as low as
96 // 30%. Here we aim at 50%, to also cover older hardware with slower
97 // cores.
98 // https://stackoverflow.com/questions/7737772/improving-kernel-performance-by-increasing-occupancy
99 // http://www.nvidia.com/content/gtc-2010/pdfs/2238_gtc2010.pdf
100 // https://www.cvg.ethz.ch/teaching/2011spring/gpgpu/GPU-Optimization.pdf
101 // https://en.wikipedia.org/wiki/Graphics_Core_Next#SIMD_Vector_Unit
102
103 // The performance for vectors is independent from array sizes.
104 if ((d1 == 1) & (d2 == 1)) return dim3(128U);
105
106 // TOTAL OCCUPATION = occup(dim0) * occup(dim1) * occup(dim2).
107 // For linearized arrays, each linear block is allocated to a dim,
108 // resulting in large numbers for dim0 & dim1.
109 // - For dim2, we only return exact dividers of the array dim[3], so
110 // occup(dim2)=100%
111 // - For dim0 & dim1, we aim somewhere between 30% and 50%
112 // * Having 2 blocks filled + 1 thread in block 3 --> occup >
113 // 2/3=66%
114 // * Having 3 blocks filled + 1 thread in block 4 --> occup >
115 // 3/4=75%
116 // * Having 4 blocks filled + 1 thread in block 5 --> occup >
117 // 4/5=80%
118 constexpr unsigned OCCUPANCY_FACTOR{2U}; // at least 2 blocks filled
119
120 // NVIDIA:
121 // warp = 32
122 // possible blocks = [32, 64, 96, 128, 160, 192, 224, 256, ..
123 // 1024] best performance = [32, 64, 96, 128] optimal perf =
124 // 128; any combination
125 // NIVIDA always processes full wavefronts. Allocating partial
126 // warps
127 // (<32) reduces throughput. Performance reaches a plateau from
128 // 128 with a slightly slowing for very large sizes.
129 // For algorithm below:
130 // parallelThreads = [32, 64, 96, 128]
131 constexpr unsigned minThreads{32};
132 const unsigned relevantElements{d0 * d1 * d2};
133 constexpr unsigned warp{32};
134
135 // For small array's, we reduce the maximum threads in 1 block to
136 // improve parallelisme. In worst case the scheduler can have 1
137 // block per CU, even when only partly loaded. Range for block is:
138 // [minThreads ... 4 * warp multiple]
139 // * NVIDIA: [4*32=128 threads]
140 // At 4 * warp multiple, full wavefronts (queue of 4 partial
141 // wavefronts) are all occupied.
142
143 // We need at least maxParallelThreads to occupy all the CU's.

Callers 3

evalNodesFunction · 0.80
memcopyFunction · 0.80
copyFunction · 0.80

Calls 1

minFunction · 0.70

Tested by

no test coverage detected