Merged in rmlarsen/eigen2 (pull request PR-543)

Add parallel memcpy to TensorThreadPoolDevice in Eigen, but limit the number of threads to 4, beyond which we just seem to be wasting CPU cycles as the threads contend for memory bandwidth.

Approved-by: Eugene Zhulenev <ezhulenev@google.com>
This commit is contained in:
Rasmus Munk Larsen 2018-11-13 17:10:30 +00:00
commit 72928a2c8a

View File

@ -77,7 +77,35 @@ struct ThreadPoolDevice {
}
EIGEN_STRONG_INLINE void memcpy(void* dst, const void* src, size_t n) const {
#ifdef __ANDROID__
::memcpy(dst, src, n);
#else
// TODO(rmlarsen): Align blocks on cache lines.
// We have observed that going beyond 4 threads usually just wastes
// CPU cycles due to the threads competing for memory bandwidth, so we
// statically schedule at most 4 block copies here.
const size_t kMinBlockSize = 32768;
typedef TensorCostModel<ThreadPoolDevice> CostModel;
const size_t num_threads = CostModel::numThreads(n, TensorOpCost(1.0, 1.0, 0), 4);
if (n <= kMinBlockSize || num_threads == 1) {
::memcpy(dst, src, n);
} else {
const char* src_ptr = static_cast<const char*>(src);
char* dst_ptr = static_cast<char*>(dst);
const size_t blocksize = (n + (num_threads - 1)) / num_threads;
Barrier barrier(num_threads - 1);
// Launch the last 3 blocks on worker threads.
for (size_t i = 1; i < num_threads; ++i) {
enqueue_with_barrier(&barrier, [n, i, src_ptr, dst_ptr, blocksize] {
::memcpy(dst_ptr + i * blocksize, src_ptr + i * blocksize,
numext::mini(blocksize, n - (i * blocksize)));
});
}
// Launch the first block on the main thread.
::memcpy(dst_ptr, src_ptr, blocksize);
barrier.Wait();
}
#endif
}
EIGEN_STRONG_INLINE void memcpyHostToDevice(void* dst, const void* src, size_t n) const {
memcpy(dst, src, n);