// This file is part of Eigen, a lightweight C++ template library // for linear algebra. // // This Source Code Form is subject to the terms of the Mozilla // Public License v. 2.0. If a copy of the MPL was not distributed // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. #define EIGEN_USE_THREADS #include #include #include "main.h" #include struct Counter { Counter() = default; void inc() { // Check that mutation happens only in a thread that created this counter. VERIFY_IS_EQUAL(std::this_thread::get_id(), created_by); counter_value++; } int value() { return counter_value; } std::thread::id created_by; int counter_value = 0; }; struct InitCounter { void operator()(Counter& counter) { counter.created_by = std::this_thread::get_id(); } }; void test_simple_thread_local() { int num_threads = internal::random(4, 32); Eigen::ThreadPool thread_pool(num_threads); Eigen::ThreadLocal counter(num_threads, InitCounter()); int num_tasks = 3 * num_threads; Eigen::Barrier barrier(num_tasks); for (int i = 0; i < num_tasks; ++i) { thread_pool.Schedule([&counter, &barrier]() { Counter& local = counter.local(); local.inc(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); barrier.Notify(); }); } barrier.Wait(); counter.ForEach( [](std::thread::id, Counter& cnt) { VERIFY_IS_EQUAL(cnt.value(), 3); }); } void test_zero_sized_thread_local() { Eigen::ThreadLocal counter(0, InitCounter()); Counter& local = counter.local(); local.inc(); int total = 0; counter.ForEach([&total](std::thread::id, Counter& cnt) { total += cnt.value(); VERIFY_IS_EQUAL(cnt.value(), 1); }); VERIFY_IS_EQUAL(total, 1); } // All thread local values fits into the lock-free storage. void test_large_number_of_tasks_no_spill() { int num_threads = internal::random(4, 32); Eigen::ThreadPool thread_pool(num_threads); Eigen::ThreadLocal counter(num_threads, InitCounter()); int num_tasks = 10000; Eigen::Barrier barrier(num_tasks); for (int i = 0; i < num_tasks; ++i) { thread_pool.Schedule([&counter, &barrier]() { Counter& local = counter.local(); local.inc(); barrier.Notify(); }); } barrier.Wait(); int total = 0; std::unordered_set unique_threads; counter.ForEach([&](std::thread::id id, Counter& cnt) { total += cnt.value(); unique_threads.insert(id); }); VERIFY_IS_EQUAL(total, num_tasks); // Not all threads in a pool might be woken up to execute submitted tasks. // Also thread_pool.Schedule() might use current thread if queue is full. VERIFY_IS_EQUAL( unique_threads.size() <= (static_cast(num_threads + 1)), true); } // Lock free thread local storage is too small to fit all the unique threads, // and it spills to a map guarded by a mutex. void test_large_number_of_tasks_with_spill() { int num_threads = internal::random(4, 32); Eigen::ThreadPool thread_pool(num_threads); Eigen::ThreadLocal counter(1, InitCounter()); int num_tasks = 10000; Eigen::Barrier barrier(num_tasks); for (int i = 0; i < num_tasks; ++i) { thread_pool.Schedule([&counter, &barrier]() { Counter& local = counter.local(); local.inc(); barrier.Notify(); }); } barrier.Wait(); int total = 0; std::unordered_set unique_threads; counter.ForEach([&](std::thread::id id, Counter& cnt) { total += cnt.value(); unique_threads.insert(id); }); VERIFY_IS_EQUAL(total, num_tasks); // Not all threads in a pool might be woken up to execute submitted tasks. // Also thread_pool.Schedule() might use current thread if queue is full. VERIFY_IS_EQUAL( unique_threads.size() <= (static_cast(num_threads + 1)), true); } EIGEN_DECLARE_TEST(cxx11_tensor_thread_local) { CALL_SUBTEST(test_simple_thread_local()); CALL_SUBTEST(test_zero_sized_thread_local()); CALL_SUBTEST(test_large_number_of_tasks_no_spill()); CALL_SUBTEST(test_large_number_of_tasks_with_spill()); }