0

Currently, I have the following Rust toy program:

use rayon::prelude::*;
use std::{env, thread, time};

/// Sleeps 1 seconds n times parallely using rayon
fn rayon_sleep(n: usize) {
    let millis = vec![0; n];
    millis
        .par_iter()
        .for_each(|_| thread::sleep(time::Duration::from_millis(1000)));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let n = args[1].parse::<usize>().unwrap();

    let now = time::Instant::now();
    rayon_sleep(n);
    println!("rayon: {:?}", now.elapsed());
}

Basically, my program accepts one input argument n. Then, I sleep for 1 second n times. The program executes the sleep tasks in parallel using rayon.

However, this is not exactly what I want. As far as I know, thread::sleep sleeps according to wall-clock time. However, I would like to keep a virtual CPU busy for 1 second in CPU time.

Is there any way to do this?

EDIT

I would like to make this point clear: I don't mind if the OS preempts the tasks. However, if this happens, then I don't want to consider the time the task spends in the ready/waiting queue.

EDIT

This is a simple, illustrative example of what I need to do. In reality, I have to develop a benchmark for a crate that allows defining and simulating models using the DEVS formalism. The benchmark aims to compare DEVS-compliant libraries with each other, and it explicitly says that the models must spend a fixed, known amount of CPU time. That is why I need to make sure of that. Thus, I cannot use a simple busy loop nor simply sleep.

Román Cárdenas
  • 492
  • 5
  • 15
  • 2
    It's not generally useful to make the CPU burn electricity for nothing, so functions waiting for a certain time don't do that. You can build your own function by calibrating some spinning loop using the `cpu_time` crate (or `libc` directly). Make sure to use `std::hint::black_box()` to prevent your loop from getting optimized away. – Sven Marnach Jan 31 '23 at 13:33
  • My understanding is that you are trying to simulate some CPU-bound workload to experiment with parallelism. If this is what you are doing, my recommendation is to simply use some trivial compute workload without trying to prescribe a priori how long it must take. That will still allow you to compare different ways of parallelizing the code. Just divde by the time it takes to run the workload once. – Sven Marnach Jan 31 '23 at 13:38
  • Yes, the idea is to implement a simple benchmark to evaluate how good using parallelism is for a crate I'm developing. So... yes, it is useless, but at least I have a ¿fair? framework to compare the sequential and parallel versions in a controlled way. – Román Cárdenas Jan 31 '23 at 13:43
  • BTW: the benchmark I want to use will be probably developed in Python, C++... and I want all the implementations to take the *exact* same amount of CPU time in the sleep task (that is why an arbitrary loop is not suitable for me) – Román Cárdenas Jan 31 '23 at 13:44
  • 1
    You should describe your actual problem in the question. And I still think a simple loop without presribed duration would work if you simply drop the requirement that the loop should take the same amount of time in every language, since you still can use a simple division to normalize your numbers. That said, comparing the numnbers from different languages is very unlikely to make sense anyway. – Sven Marnach Jan 31 '23 at 13:48
  • I just added a few lines to clarify my constraints and why I need to do a useless busy sleep – Román Cárdenas Jan 31 '23 at 14:57

1 Answers1

0

I followed Sven Marnach's suggestions and implemented the following function:

use cpu_time::ThreadTime;
use rayon::prelude::*;
use std::{env, thread, time};
/// Sleeps 1 seconds n times parallely using rayon
fn rayon_sleep(n: usize) {
    let millis = vec![0; n];
    millis.par_iter().for_each(|_| {
        let duration = time::Duration::from_millis(1000);
        let mut x: u32 = 0;
        let now = ThreadTime::now(); // get current thread time
        while now.elapsed() < duration { // active sleep
            std::hint::black_box(&mut x); // to avoid compiler optimizations
            x = x.wrapping_add(1);
        }
    });
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let n = args[1].parse::<usize>().unwrap();

    let now = time::Instant::now();
    rayon_sleep(n);
    println!("rayon: {:?}", now.elapsed());
}

If I set n to 8, it takes 2 seconds more or less. I'd expect a better performance (1 second, as I have 8 vCPUs), but I guess that the overhead corresponds to the OS scheduling policy.

Román Cárdenas
  • 492
  • 5
  • 15