thread_parallel_runner_test.cc (3772B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include <atomic> 7 8 #include "lib/jxl/base/data_parallel.h" 9 #include "lib/jxl/test_utils.h" 10 #include "lib/jxl/testing.h" 11 12 using jxl::test::ThreadPoolForTests; 13 14 namespace jpegxl { 15 namespace { 16 17 int PopulationCount(uint64_t bits) { 18 int num_set = 0; 19 while (bits != 0) { 20 num_set += bits & 1; 21 bits >>= 1; 22 } 23 return num_set; 24 } 25 26 // Ensures task parameter is in bounds, every parameter is reached, 27 // pool can be reused (multiple consecutive Run calls), pool can be destroyed 28 // (joining with its threads), num_threads=0 works (runs on current thread). 29 TEST(ThreadParallelRunnerTest, TestPool) { 30 for (int num_threads = 0; num_threads <= 18; ++num_threads) { 31 ThreadPoolForTests pool(num_threads); 32 for (int num_tasks = 0; num_tasks < 32; ++num_tasks) { 33 std::vector<int> mementos(num_tasks); 34 for (int begin = 0; begin < 32; ++begin) { 35 std::fill(mementos.begin(), mementos.end(), 0); 36 EXPECT_TRUE(RunOnPool( 37 &pool, begin, begin + num_tasks, jxl::ThreadPool::NoInit, 38 [begin, num_tasks, &mementos](const int task, const int thread) { 39 // Parameter is in the given range 40 EXPECT_GE(task, begin); 41 EXPECT_LT(task, begin + num_tasks); 42 43 // Store mementos to be sure we visited each task. 44 mementos.at(task - begin) = 1000 + task; 45 }, 46 "TestPool")); 47 for (int task = begin; task < begin + num_tasks; ++task) { 48 EXPECT_EQ(1000 + task, mementos.at(task - begin)); 49 } 50 } 51 } 52 } 53 } 54 55 // Verify "thread" parameter when processing few tasks. 56 TEST(ThreadParallelRunnerTest, TestSmallAssignments) { 57 const int kMaxThreads = 8; 58 for (int num_threads = 1; num_threads <= kMaxThreads; ++num_threads) { 59 ThreadPoolForTests pool(num_threads); 60 61 // (Avoid mutex because it may perturb the worker thread scheduling) 62 std::atomic<uint64_t> id_bits{0}; 63 std::atomic<int> num_calls{0}; 64 65 EXPECT_TRUE(RunOnPool( 66 &pool, 0, num_threads, jxl::ThreadPool::NoInit, 67 [&num_calls, num_threads, &id_bits](const int task, const int thread) { 68 num_calls.fetch_add(1, std::memory_order_relaxed); 69 70 EXPECT_LT(thread, num_threads); 71 uint64_t bits = id_bits.load(std::memory_order_relaxed); 72 while ( 73 !id_bits.compare_exchange_weak(bits, bits | (1ULL << thread))) { 74 } 75 }, 76 "TestSmallAssignments")); 77 78 // Correct number of tasks. 79 EXPECT_EQ(num_threads, num_calls.load()); 80 81 const int num_participants = PopulationCount(id_bits.load()); 82 // Can't expect equality because other workers may have woken up too late. 83 EXPECT_LE(num_participants, num_threads); 84 } 85 } 86 87 struct Counter { 88 Counter() { 89 // Suppress "unused-field" warning. 90 (void)padding; 91 } 92 void Assimilate(const Counter& victim) { counter += victim.counter; } 93 int counter = 0; 94 int padding[31]; 95 }; 96 97 TEST(ThreadParallelRunnerTest, TestCounter) { 98 const int kNumThreads = 12; 99 ThreadPoolForTests pool(kNumThreads); 100 alignas(128) Counter counters[kNumThreads]; 101 102 const int kNumTasks = kNumThreads * 19; 103 EXPECT_TRUE(RunOnPool( 104 &pool, 0, kNumTasks, jxl::ThreadPool::NoInit, 105 [&counters](const int task, const int thread) { 106 counters[thread].counter += task; 107 }, 108 "TestCounter")); 109 110 int expected = 0; 111 for (int i = 0; i < kNumTasks; ++i) { 112 expected += i; 113 } 114 115 for (int i = 1; i < kNumThreads; ++i) { 116 counters[0].Assimilate(counters[i]); 117 } 118 EXPECT_EQ(expected, counters[0].counter); 119 } 120 121 } // namespace 122 } // namespace jpegxl