core_affinity/
lib.rs

1//! This crate manages CPU affinities.
2//!
3//! ## Example
4//!
5//! This example shows how to create a thread for each available processor and pin each thread to its corresponding processor.
6//!
7//! ```
8//! extern crate core_affinity;
9//!
10//! use std::thread;
11//!
12//! // Retrieve the IDs of all active CPU cores.
13//! let core_ids = core_affinity::get_core_ids().unwrap();
14//!
15//! // Create a thread for each active CPU core.
16//! let handles = core_ids.into_iter().map(|id| {
17//!     thread::spawn(move || {
18//!         // Pin this thread to a single CPU core.
19//!         let res = core_affinity::set_for_current(id);
20//!         if (res) {
21//!             // Do more work after this.
22//!         }
23//!     })
24//! }).collect::<Vec<_>>();
25//!
26//! for handle in handles.into_iter() {
27//!     handle.join().unwrap();
28//! }
29//! ```
30
31#[cfg(any(
32    target_os = "android",
33    target_os = "linux",
34    target_os = "macos",
35    target_os = "freebsd",
36    target_os = "netbsd"
37))]
38extern crate libc;
39
40#[cfg_attr(all(not(test), not(target_os = "macos")), allow(unused_extern_crates))]
41extern crate num_cpus;
42
43/// This function tries to retrieve information
44/// on all the "cores" on which the current thread 
45/// is allowed to run.
46pub fn get_core_ids() -> Option<Vec<CoreId>> {
47    get_core_ids_helper()
48}
49
50/// This function tries to pin the current
51/// thread to the specified core.
52///
53/// # Arguments
54///
55/// * core_id - ID of the core to pin
56pub fn set_for_current(core_id: CoreId) -> bool {
57    set_for_current_helper(core_id)
58}
59
60/// This represents a CPU core.
61#[repr(transparent)]
62#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
63pub struct CoreId {
64    pub id: usize,
65}
66
67// Linux Section
68
69#[cfg(any(target_os = "android", target_os = "linux"))]
70#[inline]
71fn get_core_ids_helper() -> Option<Vec<CoreId>> {
72    linux::get_core_ids()
73}
74
75#[cfg(any(target_os = "android", target_os = "linux"))]
76#[inline]
77fn set_for_current_helper(core_id: CoreId) -> bool {
78    linux::set_for_current(core_id)
79}
80
81#[cfg(any(target_os = "android", target_os = "linux"))]
82mod linux {
83    use std::mem;
84
85    use libc::{CPU_ISSET, CPU_SET, CPU_SETSIZE, cpu_set_t, sched_getaffinity, sched_setaffinity};
86
87    use super::CoreId;
88
89    pub fn get_core_ids() -> Option<Vec<CoreId>> {
90        if let Some(full_set) = get_affinity_mask() {
91            let mut core_ids: Vec<CoreId> = Vec::new();
92
93            for i in 0..CPU_SETSIZE as usize {
94                if unsafe { CPU_ISSET(i, &full_set) } {
95                    core_ids.push(CoreId{ id: i });
96                }
97            }
98
99            Some(core_ids)
100        }
101        else {
102            None
103        }
104    }
105
106    pub fn set_for_current(core_id: CoreId) -> bool {
107        // Turn `core_id` into a `libc::cpu_set_t` with only
108        // one core active.
109        let mut set = new_cpu_set();
110
111        unsafe { CPU_SET(core_id.id, &mut set) };
112
113        // Set the current thread's core affinity.
114        let res = unsafe {
115            sched_setaffinity(0, // Defaults to current thread
116                              mem::size_of::<cpu_set_t>(),
117                              &set)
118        };
119        res == 0
120    }
121
122    fn get_affinity_mask() -> Option<cpu_set_t> {
123        let mut set = new_cpu_set();
124
125        // Try to get current core affinity mask.
126        let result = unsafe {
127            sched_getaffinity(0, // Defaults to current thread
128                              mem::size_of::<cpu_set_t>(),
129                              &mut set)
130        };
131
132        if result == 0 {
133            Some(set)
134        }
135        else {
136            None
137        }
138    }
139
140    fn new_cpu_set() -> cpu_set_t {
141        unsafe { mem::zeroed::<cpu_set_t>() }
142    }
143
144    #[cfg(test)]
145    mod tests {
146        use num_cpus;
147
148        use super::*;
149
150        #[test]
151        fn test_linux_get_affinity_mask() {
152            match get_affinity_mask() {
153                Some(_) => {},
154                None => { assert!(false); },
155            }
156        }
157
158        #[test]
159        fn test_linux_get_core_ids() {
160            match get_core_ids() {
161                Some(set) => {
162                    assert_eq!(set.len(), num_cpus::get());
163                },
164                None => { assert!(false); },
165            }
166        }
167
168        #[test]
169        fn test_linux_set_for_current() {
170            let ids = get_core_ids().unwrap();
171
172            assert!(ids.len() > 0);
173
174            let res = set_for_current(ids[0]);
175            assert_eq!(res, true);
176
177            // Ensure that the system pinned the current thread
178            // to the specified core.
179            let mut core_mask = new_cpu_set();
180            unsafe { CPU_SET(ids[0].id, &mut core_mask) };
181
182            let new_mask = get_affinity_mask().unwrap();
183
184            let mut is_equal = true;
185
186            for i in 0..CPU_SETSIZE as usize {
187                let is_set1 = unsafe {
188                    CPU_ISSET(i, &core_mask)
189                };
190                let is_set2 = unsafe {
191                    CPU_ISSET(i, &new_mask)
192                };
193
194                if is_set1 != is_set2 {
195                    is_equal = false;
196                }
197            }
198
199            assert!(is_equal);
200        }
201     }
202}
203
204// Windows Section
205
206#[cfg(target_os = "windows")]
207#[inline]
208fn get_core_ids_helper() -> Option<Vec<CoreId>> {
209    windows::get_core_ids()
210}
211
212#[cfg(target_os = "windows")]
213#[inline]
214fn set_for_current_helper(core_id: CoreId) -> bool {
215    windows::set_for_current(core_id)
216}
217
218#[cfg(target_os = "windows")]
219extern crate winapi;
220
221#[cfg(target_os = "windows")]
222mod windows {
223    use winapi::shared::basetsd::{DWORD_PTR, PDWORD_PTR};
224    use winapi::um::processthreadsapi::{GetCurrentProcess, GetCurrentThread};
225    use winapi::um::winbase::{GetProcessAffinityMask, SetThreadAffinityMask};
226
227    use super::CoreId;
228
229    pub fn get_core_ids() -> Option<Vec<CoreId>> {
230        if let Some(mask) = get_affinity_mask() {
231            // Find all active cores in the bitmask.
232            let mut core_ids: Vec<CoreId> = Vec::new();
233
234            for i in 0..64 as u64 {
235                let test_mask = 1 << i;
236
237                if (mask & test_mask) == test_mask {
238                    core_ids.push(CoreId { id: i as usize });
239                }
240            }
241
242            Some(core_ids)
243        }
244        else {
245            None
246        }
247    }
248
249    pub fn set_for_current(core_id: CoreId) -> bool {
250        // Convert `CoreId` back into mask.
251        let mask: u64 = 1 << core_id.id;
252
253        // Set core affinity for current thread.
254        let res = unsafe {
255            SetThreadAffinityMask(
256                GetCurrentThread(),
257                mask as DWORD_PTR
258            )
259        };
260        res != 0
261    }
262
263    fn get_affinity_mask() -> Option<u64> {
264        let mut system_mask: usize = 0;
265        let mut process_mask: usize = 0;
266
267        let res = unsafe {
268            GetProcessAffinityMask(
269                GetCurrentProcess(),
270                &mut process_mask as PDWORD_PTR,
271                &mut system_mask as PDWORD_PTR
272            )
273        };
274
275        // Successfully retrieved affinity mask
276        if res != 0 {
277            Some(process_mask as u64)
278        }
279        // Failed to retrieve affinity mask
280        else {
281            None
282        }
283    }
284
285    #[cfg(test)]
286    mod tests {
287        use num_cpus;
288
289        use super::*;
290
291        #[test]
292        fn test_windows_get_core_ids() {
293            match get_core_ids() {
294                Some(set) => {
295                    assert_eq!(set.len(), num_cpus::get());
296                },
297                None => { assert!(false); },
298            }
299        }
300
301        #[test]
302        fn test_windows_set_for_current() {
303            let ids = get_core_ids().unwrap();
304
305            assert!(ids.len() > 0);
306
307            assert_ne!(set_for_current(ids[0]), 0);
308        }
309    }
310}
311
312// MacOS Section
313
314#[cfg(target_os = "macos")]
315#[inline]
316fn get_core_ids_helper() -> Option<Vec<CoreId>> {
317    macos::get_core_ids()
318}
319
320#[cfg(target_os = "macos")]
321#[inline]
322fn set_for_current_helper(core_id: CoreId) -> bool {
323    macos::set_for_current(core_id)
324}
325
326#[cfg(target_os = "macos")]
327mod macos {
328    use std::mem;
329
330    use libc::{c_int, c_uint, c_void, pthread_self};
331
332    use num_cpus;
333
334    use super::CoreId;
335
336    type kern_return_t = c_int;
337    type integer_t = c_int;
338    type natural_t = c_uint;
339    type thread_t = c_uint;
340    type thread_policy_flavor_t = natural_t;
341    type mach_msg_type_number_t = natural_t;
342
343    #[repr(C)]
344    struct thread_affinity_policy_data_t {
345        affinity_tag: integer_t,
346    }
347
348    type thread_policy_t = *mut thread_affinity_policy_data_t;
349
350    const THREAD_AFFINITY_POLICY: thread_policy_flavor_t = 4;
351
352    extern {
353        fn thread_policy_set(
354            thread: thread_t,
355            flavor: thread_policy_flavor_t,
356            policy_info: thread_policy_t,
357            count: mach_msg_type_number_t,
358        ) -> kern_return_t;
359    }
360
361    pub fn get_core_ids() -> Option<Vec<CoreId>> {
362        Some((0..(num_cpus::get())).into_iter()
363             .map(|n| CoreId { id: n as usize })
364             .collect::<Vec<_>>())
365    }
366
367    pub fn set_for_current(core_id: CoreId) -> bool {
368        let THREAD_AFFINITY_POLICY_COUNT: mach_msg_type_number_t =
369            mem::size_of::<thread_affinity_policy_data_t>() as mach_msg_type_number_t /
370            mem::size_of::<integer_t>() as mach_msg_type_number_t;
371
372        let mut info = thread_affinity_policy_data_t {
373            affinity_tag: core_id.id as integer_t,
374        };
375
376        let res = unsafe {
377            thread_policy_set(
378                pthread_self() as thread_t,
379                THREAD_AFFINITY_POLICY,
380                &mut info as thread_policy_t,
381                THREAD_AFFINITY_POLICY_COUNT
382            )
383        };
384        res == 0
385    }
386
387    #[cfg(test)]
388    mod tests {
389        use num_cpus;
390
391        use super::*;
392
393        #[test]
394        fn test_macos_get_core_ids() {
395            match get_core_ids() {
396                Some(set) => {
397                    assert_eq!(set.len(), num_cpus::get());
398                },
399                None => { assert!(false); },
400            }
401        }
402
403        #[test]
404        fn test_macos_set_for_current() {
405            let ids = get_core_ids().unwrap();
406            assert!(ids.len() > 0);
407            assert!(set_for_current(ids[0]))
408        }
409    }
410}
411
412
413// FreeBSD Section
414
415#[cfg(target_os = "freebsd")]
416#[inline]
417fn get_core_ids_helper() -> Option<Vec<CoreId>> {
418    freebsd::get_core_ids()
419}
420
421#[cfg(target_os = "freebsd")]
422#[inline]
423fn set_for_current_helper(core_id: CoreId) -> bool {
424    freebsd::set_for_current(core_id)
425}
426
427#[cfg(target_os = "freebsd")]
428mod freebsd {
429    use std::mem;
430
431    use libc::{
432        cpuset_getaffinity, cpuset_setaffinity, cpuset_t, CPU_ISSET,
433        CPU_LEVEL_WHICH, CPU_SET, CPU_SETSIZE, CPU_WHICH_TID,
434    };
435
436    use super::CoreId;
437
438    pub fn get_core_ids() -> Option<Vec<CoreId>> {
439        if let Some(full_set) = get_affinity_mask() {
440            let mut core_ids: Vec<CoreId> = Vec::new();
441
442            for i in 0..CPU_SETSIZE as usize {
443                if unsafe { CPU_ISSET(i, &full_set) } {
444                    core_ids.push(CoreId { id: i });
445                }
446            }
447
448            Some(core_ids)
449        } else {
450            None
451        }
452    }
453
454    pub fn set_for_current(core_id: CoreId) -> bool {
455        // Turn `core_id` into a `libc::cpuset_t` with only
456        // one core active.
457        let mut set = new_cpu_set();
458
459        unsafe { CPU_SET(core_id.id, &mut set) };
460
461        // Set the current thread's core affinity.
462        let res = unsafe {
463            // FreeBSD's sched_setaffinity currently operates on process id,
464            // therefore using cpuset_setaffinity instead.
465            cpuset_setaffinity(
466                CPU_LEVEL_WHICH,
467                CPU_WHICH_TID,
468                -1, // -1 == current thread
469                mem::size_of::<cpuset_t>(),
470                &set,
471            )
472        };
473        res == 0
474    }
475
476    fn get_affinity_mask() -> Option<cpuset_t> {
477        let mut set = new_cpu_set();
478
479        // Try to get current core affinity mask.
480        let result = unsafe {
481            // FreeBSD's sched_getaffinity currently operates on process id,
482            // therefore using cpuset_getaffinity instead.
483            cpuset_getaffinity(
484                CPU_LEVEL_WHICH,
485                CPU_WHICH_TID,
486                -1, // -1 == current thread
487                mem::size_of::<cpuset_t>(),
488                &mut set,
489            )
490        };
491
492        if result == 0 {
493            Some(set)
494        } else {
495            None
496        }
497    }
498
499    fn new_cpu_set() -> cpuset_t {
500        unsafe { mem::zeroed::<cpuset_t>() }
501    }
502
503    #[cfg(test)]
504    mod tests {
505        use num_cpus;
506
507        use super::*;
508
509        #[test]
510        fn test_freebsd_get_affinity_mask() {
511            match get_affinity_mask() {
512                Some(_) => {}
513                None => {
514                    assert!(false);
515                }
516            }
517        }
518
519        #[test]
520        fn test_freebsd_get_core_ids() {
521            match get_core_ids() {
522                Some(set) => {
523                    assert_eq!(set.len(), num_cpus::get());
524                }
525                None => {
526                    assert!(false);
527                }
528            }
529        }
530
531        #[test]
532        fn test_freebsd_set_for_current() {
533            let ids = get_core_ids().unwrap();
534
535            assert!(ids.len() > 0);
536
537            let res = set_for_current(ids[0]);
538            assert_eq!(res, true);
539
540            // Ensure that the system pinned the current thread
541            // to the specified core.
542            let mut core_mask = new_cpu_set();
543            unsafe { CPU_SET(ids[0].id, &mut core_mask) };
544
545            let new_mask = get_affinity_mask().unwrap();
546
547            let mut is_equal = true;
548
549            for i in 0..CPU_SETSIZE as usize {
550                let is_set1 = unsafe { CPU_ISSET(i, &core_mask) };
551                let is_set2 = unsafe { CPU_ISSET(i, &new_mask) };
552
553                if is_set1 != is_set2 {
554                    is_equal = false;
555                }
556            }
557
558            assert!(is_equal);
559        }
560    }
561}
562
563// NetBSD Section
564
565#[cfg(target_os = "netbsd")]
566#[inline]
567fn get_core_ids_helper() -> Option<Vec<CoreId>> {
568    netbsd::get_core_ids()
569}
570
571#[cfg(target_os = "netbsd")]
572#[inline]
573fn set_for_current_helper(core_id: CoreId) -> bool {
574    netbsd::set_for_current(core_id)
575}
576
577#[cfg(target_os = "netbsd")]
578mod netbsd {
579    use libc::{
580        pthread_getaffinity_np, pthread_setaffinity_np, pthread_self, cpuset_t,
581        _cpuset_create, _cpuset_size, _cpuset_set, _cpuset_isset, _cpuset_destroy
582    };
583    use num_cpus;
584
585    use super::CoreId;
586
587    pub fn get_core_ids() -> Option<Vec<CoreId>> {
588        if let Some(full_set) = get_affinity_mask() {
589            let mut core_ids: Vec<CoreId> = Vec::new();
590
591            let num_cpus = num_cpus::get();
592            for i in 0..num_cpus {
593                if unsafe { _cpuset_isset(i as u64, full_set) } >= 0 {
594                    core_ids.push(CoreId { id: i });
595                }
596            }
597            unsafe { _cpuset_destroy(full_set) };
598            Some(core_ids)
599        } else {
600            None
601        }
602    }
603
604    pub fn set_for_current(core_id: CoreId) -> bool {
605        let set = unsafe { _cpuset_create() };
606        unsafe { _cpuset_set(core_id.id as u64, set) } ;
607
608        let result = unsafe {
609            pthread_setaffinity_np(pthread_self(), _cpuset_size(set), set)
610        };
611        unsafe { _cpuset_destroy(set) };
612
613        match result {
614            0 => true,
615            _ => false,
616        }
617    }
618
619    fn get_affinity_mask() -> Option<*mut cpuset_t> {
620        let set = unsafe { _cpuset_create() };
621
622        match unsafe {
623            pthread_getaffinity_np(pthread_self(), _cpuset_size(set), set)
624        } {
625            0 => Some(set),
626            _ => None,
627        }
628    }
629
630    #[cfg(test)]
631    mod tests {
632        use num_cpus;
633
634        use super::*;
635
636        #[test]
637        fn test_netbsd_get_affinity_mask() {
638            match get_affinity_mask() {
639                Some(set) => unsafe { _cpuset_destroy(set); },
640                None => {
641                    assert!(false);
642                }
643            }
644        }
645
646        #[test]
647        fn test_netbsd_get_core_ids() {
648            match get_core_ids() {
649                Some(set) => {
650                    assert_eq!(set.len(), num_cpus::get());
651                }
652                None => {
653                    assert!(false);
654                }
655            }
656        }
657
658        #[test]
659        fn test_netbsd_set_for_current() {
660            let ids = get_core_ids().unwrap();
661
662            assert!(ids.len() > 0);
663
664            let ci = ids[ids.len() - 1]; // use the last reported core
665            let res = set_for_current(ci);
666            assert_eq!(res, true);
667
668            // Ensure that the system pinned the current thread
669            // to the specified core.
670            let new_mask = get_affinity_mask().unwrap();
671            assert!(unsafe { _cpuset_isset(ci.id as u64, new_mask) > 0 });
672            let num_cpus = num_cpus::get();
673            for i in 0..num_cpus {
674                if i != ci.id {
675                    assert_eq!(0, unsafe { _cpuset_isset(i as u64, new_mask) });
676                }
677            }
678            unsafe { _cpuset_destroy(new_mask) };
679        }
680    }
681}
682
683// Stub Section
684
685#[cfg(not(any(
686    target_os = "linux",
687    target_os = "android",
688    target_os = "windows",
689    target_os = "macos",
690    target_os = "freebsd",
691    target_os = "netbsd"
692)))]
693#[inline]
694fn get_core_ids_helper() -> Option<Vec<CoreId>> {
695    None
696}
697
698#[cfg(not(any(
699    target_os = "linux",
700    target_os = "android",
701    target_os = "windows",
702    target_os = "macos",
703    target_os = "freebsd",
704    target_os = "netbsd"
705)))]
706#[inline]
707fn set_for_current_helper(_core_id: CoreId) -> bool {
708    false
709}
710
711#[cfg(test)]
712mod tests {
713    use num_cpus;
714
715    use super::*;
716
717    // #[test]
718    // fn test_num_cpus() {
719    //     println!("Num CPUs: {}", num_cpus::get());
720    //     println!("Num Physical CPUs: {}", num_cpus::get_physical());
721    // }
722
723    #[test]
724    fn test_get_core_ids() {
725        match get_core_ids() {
726            Some(set) => {
727                assert_eq!(set.len(), num_cpus::get());
728            },
729            None => { assert!(false); },
730        }
731    }
732
733    #[test]
734    fn test_set_for_current() {
735        let ids = get_core_ids().unwrap();
736        assert!(ids.len() > 0);
737        assert!(set_for_current(ids[0]))
738    }
739}