1use diskann::{ANNError, ANNResult};
6use rayon::prelude::ParallelIterator;
7
8pub fn create_thread_pool(num_threads: usize) -> ANNResult<RayonThreadPool> {
11 let pool = rayon::ThreadPoolBuilder::new()
12 .num_threads(num_threads)
13 .build()
14 .map_err(|err| ANNError::log_thread_pool_error(err.to_string()))?;
15 Ok(RayonThreadPool(pool))
16}
17
18#[allow(clippy::unwrap_used)]
22pub fn create_thread_pool_for_test() -> RayonThreadPool {
23 use std::env;
24
25 let num_threads = env::var("DISKANN_TEST_POOL_THREADS")
26 .ok()
27 .and_then(|val| val.parse().ok())
28 .unwrap_or(3);
29
30 create_thread_pool(num_threads).unwrap()
31}
32#[allow(clippy::unwrap_used)]
37pub fn create_thread_pool_for_bench() -> RayonThreadPool {
38 let pool = rayon::ThreadPoolBuilder::new()
39 .build()
40 .map_err(|err| ANNError::log_thread_pool_error(err.to_string()))
41 .unwrap();
42 RayonThreadPool(pool)
43}
44
45pub struct RayonThreadPool(rayon::ThreadPool);
46
47impl RayonThreadPool {
48 pub fn install<OP, R>(&self, op: OP) -> R
49 where
50 OP: FnOnce() -> R + Send,
51 R: Send,
52 {
53 self.0.install(op)
54 }
55
56 pub fn as_ref(&self) -> RayonThreadPoolRef<'_> {
57 RayonThreadPoolRef(&self.0)
58 }
59}
60
61#[derive(Clone, Copy)]
62pub struct RayonThreadPoolRef<'a>(&'a rayon::ThreadPool);
63
64impl<'a> RayonThreadPoolRef<'a> {
65 pub fn new(pool: &'a rayon::ThreadPool) -> Self {
67 Self(pool)
68 }
69
70 pub fn install<OP, R>(self, op: OP) -> R
71 where
72 OP: FnOnce() -> R + Send,
73 R: Send,
74 {
75 self.0.install(op)
76 }
77}
78
79#[allow(clippy::disallowed_methods)]
83pub trait ParallelIteratorInPool: ParallelIterator + Sized {
84 fn for_each_in_pool<OP>(self, pool: RayonThreadPoolRef<'_>, op: OP)
85 where
86 OP: Fn(Self::Item) + Sync + Send,
87 {
88 pool.install(|| self.for_each(op));
89 }
90
91 fn for_each_with_in_pool<OP, T>(self, pool: RayonThreadPoolRef<'_>, init: T, op: OP)
92 where
93 OP: Fn(&mut T, Self::Item) + Sync + Send,
94 T: Send + Clone,
95 {
96 pool.install(|| self.for_each_with(init, op))
97 }
98
99 fn for_each_init_in_pool<OP, INIT, T>(self, pool: RayonThreadPoolRef<'_>, init: INIT, op: OP)
100 where
101 OP: Fn(&mut T, Self::Item) + Sync + Send,
102 INIT: Fn() -> T + Sync + Send,
103 {
104 pool.install(|| self.for_each_init(init, op))
105 }
106
107 fn try_for_each_in_pool<OP, E>(self, pool: RayonThreadPoolRef<'_>, op: OP) -> Result<(), E>
108 where
109 OP: Fn(Self::Item) -> Result<(), E> + Sync + Send,
110 E: Send,
111 {
112 pool.install(|| self.try_for_each(op))
113 }
114
115 fn try_for_each_with_in_pool<OP, T, E>(
116 self,
117 pool: RayonThreadPoolRef<'_>,
118 init: T,
119 op: OP,
120 ) -> Result<(), E>
121 where
122 OP: Fn(&mut T, Self::Item) -> Result<(), E> + Sync + Send,
123 E: Send,
124 T: Send + Clone,
125 {
126 pool.install(|| self.try_for_each_with(init, op))
127 }
128
129 fn try_for_each_init_in_pool<OP, INIT, T, E>(
130 self,
131 pool: RayonThreadPoolRef<'_>,
132 init: INIT,
133 op: OP,
134 ) -> Result<(), E>
135 where
136 OP: Fn(&mut T, Self::Item) -> Result<(), E> + Sync + Send,
137 INIT: Fn() -> T + Sync + Send,
138 E: Send,
139 {
140 pool.install(|| self.try_for_each_init(init, op))
141 }
142
143 fn count_in_pool(self, pool: RayonThreadPoolRef<'_>) -> usize {
144 pool.install(|| self.count())
145 }
146
147 fn collect_in_pool<C>(self, pool: RayonThreadPoolRef<'_>) -> C
148 where
149 C: rayon::iter::FromParallelIterator<Self::Item> + Send,
150 {
151 pool.install(|| self.collect())
152 }
153
154 fn sum_in_pool<S>(self, pool: RayonThreadPoolRef<'_>) -> S
155 where
156 S: Send + std::iter::Sum<Self::Item> + std::iter::Sum<S>,
157 {
158 pool.install(|| self.sum())
159 }
160}
161
162impl<T> ParallelIteratorInPool for T where T: ParallelIterator {}
164
165#[cfg(test)]
166mod tests {
167 use std::sync::{Mutex, mpsc::channel};
168
169 use super::*;
170 use rayon::prelude::IntoParallelIterator;
171
172 fn get_num_cpus() -> usize {
173 std::thread::available_parallelism()
174 .map(|n| n.get())
175 .unwrap()
176 }
177
178 #[test]
179 fn test_create_thread_pool_for_test_default() {
180 unsafe { std::env::remove_var("DISKANN_TEST_POOL_THREADS") };
185 let pool = create_thread_pool_for_test();
186 assert_eq!(pool.0.current_num_threads(), 3);
188 }
189
190 #[test]
191 fn test_create_thread_pool_for_test_from_env() {
192 unsafe { std::env::set_var("DISKANN_TEST_POOL_THREADS", "5") };
197 let pool = create_thread_pool_for_test();
198 assert_eq!(pool.0.current_num_threads(), 5);
200
201 unsafe { std::env::remove_var("DISKANN_TEST_POOL_THREADS") };
206 }
207
208 #[test]
209 fn test_create_thread_pool_for_test_invalid_env() {
210 unsafe { std::env::set_var("DISKANN_TEST_POOL_THREADS", "invalid") };
215 let pool = create_thread_pool_for_test();
216 assert_eq!(pool.0.current_num_threads(), 3);
218
219 unsafe { std::env::remove_var("DISKANN_TEST_POOL_THREADS") };
224 }
225
226 #[test]
227 fn test_create_thread_pool_for_bench() {
228 let pool = create_thread_pool_for_bench();
229 assert_eq!(pool.0.current_num_threads(), get_num_cpus());
230 }
231
232 fn assert_run_in_rayon_thread() {
233 println!(
234 "Thread name: {:?}, Thread id: {:?}, Rayon thread index: {:?}, Rayon num_threads: {:?}",
235 std::thread::current().name(),
236 std::thread::current().id(),
237 rayon::current_thread_index(),
238 rayon::current_num_threads()
239 );
240 assert!(rayon::current_thread_index().is_some());
241 }
242
243 #[test]
244 fn test_bring_your_own_pool() {
245 let external_pool = rayon::ThreadPoolBuilder::new()
246 .num_threads(2)
247 .build()
248 .unwrap();
249 let pool_ref = RayonThreadPoolRef::new(&external_pool);
250
251 let res = Mutex::new(Vec::new());
252 (0..5).into_par_iter().for_each_in_pool(pool_ref, |x| {
253 let mut res = res.lock().unwrap();
254 res.push(x);
255 assert_run_in_rayon_thread();
256 });
257
258 let mut res = res.lock().unwrap();
259 res.sort();
260 assert_eq!(&res[..], &[0, 1, 2, 3, 4]);
261 }
262
263 #[test]
264 fn test_for_each_in_pool() {
265 let pool = create_thread_pool(4).unwrap();
266
267 let res = Mutex::new(Vec::new());
268 (0..5).into_par_iter().for_each_in_pool(pool.as_ref(), |x| {
269 let mut res = res.lock().unwrap();
270 res.push(x);
271 assert_run_in_rayon_thread();
272 });
273
274 let mut res = res.lock().unwrap();
275 res.sort();
276
277 assert_eq!(&res[..], &[0, 1, 2, 3, 4]);
278 }
279 #[test]
280 fn test_for_each_with_in_pool() {
281 let pool = create_thread_pool(4).unwrap();
282 let (sender, receiver) = channel();
283
284 (0..5)
285 .into_par_iter()
286 .for_each_with_in_pool(pool.as_ref(), sender, |s, x| s.send(x).unwrap());
287
288 let mut res: Vec<_> = receiver.iter().collect();
289
290 res.sort();
291
292 assert_eq!(&res[..], &[0, 1, 2, 3, 4]);
293 }
294
295 #[test]
296 fn test_for_each_init_in_pool() {
297 let pool = create_thread_pool(4).unwrap();
298 let iter = (0..100).into_par_iter();
299 iter.for_each_init_in_pool(
300 pool.as_ref(),
301 || 0,
302 |s, i| {
303 assert_run_in_rayon_thread();
304 *s += i;
305 },
306 );
307 }
308
309 #[test]
310 fn test_map_in_pool() {
311 let pool = create_thread_pool(4).unwrap();
312 let iter = (0..100).into_par_iter();
313 let mapped_iter = iter.map(|i| {
314 assert_run_in_rayon_thread();
315 i as f32
316 });
317 let list = mapped_iter.collect_in_pool::<Vec<f32>>(pool.as_ref());
318 assert!(list.len() == 100);
319 }
320
321 #[test]
322 fn test_try_for_each_in_pool() {
323 let pool = create_thread_pool(4).unwrap();
324 let iter = (0..100).into_par_iter();
325 let result = iter.try_for_each_in_pool(pool.as_ref(), |i| {
326 assert_run_in_rayon_thread();
327 if i < 50 { Ok(()) } else { Err("Error") }
328 });
329 assert!(result.is_err());
330 }
331
332 #[test]
333 fn test_try_for_each_init_in_pool() {
334 let pool = create_thread_pool(4).unwrap();
335 let iter = (0..100).into_par_iter();
336 let result = iter.try_for_each_init_in_pool(
337 pool.as_ref(),
338 || 0,
339 |_, i| {
340 assert_run_in_rayon_thread();
341 if i < 50 { Ok(()) } else { Err("Error") }
342 },
343 );
344 assert!(result.is_err());
345 }
346
347 #[test]
348 fn test_try_for_each_with_in_pool() {
349 let pool = create_thread_pool(4).unwrap();
350 let iter = (0..100).into_par_iter();
351 let result = iter.try_for_each_with_in_pool(pool.as_ref(), 0, |acc, i| {
352 assert_run_in_rayon_thread();
353 if i < 50 {
354 *acc += i;
355 Ok(())
356 } else {
357 Err("Error")
358 }
359 });
360 assert!(result.is_err());
361 }
362
363 #[test]
364 fn test_count_in_pool() {
365 let pool = create_thread_pool(4).unwrap();
366 let iter = (0..100).into_par_iter();
367 let count = iter.count_in_pool(pool.as_ref());
368 assert_eq!(count, 100);
369 }
370
371 #[test]
372 fn test_collect_in_pool() {
373 let pool = create_thread_pool(4).unwrap();
374 let iter = (0..100).into_par_iter();
375 let vec = iter.collect_in_pool::<Vec<_>>(pool.as_ref());
376 assert_eq!(vec.len(), 100);
377 }
378
379 #[test]
380 fn test_sum_in_pool() {
381 let pool = create_thread_pool(4).unwrap();
382 let iter = (0..100).into_par_iter();
383 let sum: i32 = iter.sum_in_pool(pool.as_ref());
384 assert_eq!(sum, (0..100).sum::<i32>());
385 }
386}