CPU utilization seems to be on a higher side

This is a piece of Go code

func SignalDemo(){
	sigs := make(chan os.Signal, 1)
	finished := make(chan bool)
	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTSTP)
	go func(){
		for{
			s := <-sigs
			switch s{
			case syscall.SIGINT:
				fmt.Println("Received Ctrl+C")
				finished<-true
			case syscall.SIGTSTP:
				fmt.Println("Received Ctrl+Z")
				finished<-true
			}
		}
	}()
	fmt.Println("Waiting for Ctrl+C")
	<-finished
	fmt.Println("We are done here")
}

Below is a piece of rust code

pub fn signal_demo() -> Result<(), Error> {
  let (tr, recv):(mpsc::Sender<&str>, mpsc::Receiver<&str>) = mpsc::channel();
  let mut signals = Signals::new(&[SIGTERM, SIGINT, SIGTSTP])?;

  thread::spawn(move|| {
    'signal_loop: loop {
      for signal in signals.pending() {
          match signal {
              SIGINT => {
                  tr.send("Received Ctrl+C").unwrap();
                  // break 'signal_loop;
              }
              SIGTERM => {
                  tr.send("Received signal SIGTERM").unwrap();
                  // break 'signal_loop;
              }
              SIGTSTP => {
                tr.send("Received Ctrl+Z").unwrap();
                // break 'signal_loop;
            }
              _ => unreachable!(),
          }
      }
    }
  });

  println!("Waiting for Ctrl+C");
  for message in recv{
    println!("{}", message);
    break;
  }
  Ok(())
}

When I am running the Go code, CPU utilization is around 5%. Whereas when I am running the Rust code (build to release version), the utilization is around 15%. What am I doing wrong in Rust? Or, is this expected?

Thanks & Regards,
Soupayan

A couple of things:

  • Are you running an optimized build? (cargo run --release)
  • You are spawning real OS threads in the Rust code. Go's goroutines are lightweight cooperative coroutines with no direct backing OS threads (they are scheduled by the runtime onto threads as efficiently as possible). Try not spawning threads directly in your Rust code by e.g. using async instead. (Mind you, that is not a trivial transformation.)
  • How accurate is your measurement? Did you just run the two programs once? Or how did you get the figures 5% vs 15% and how confident are you that they are correct?

Well I have not used any sophisticated system to benchmark (due to my lack of knowledge). I simply eye balled the CPU utilization from task manager in windows. However, I did this atleast 10 times and each time the results were as shared. Yes, I ran in optimized mode as well. But I guess you point about OS threads answers the question.

You should use signals.forever() instead of signals.pending() if your thread is only waiting for signals. Then you can get rid of the outer loop which is causing the busy waiting:

  thread::spawn(move|| {
      for signal in signals.forever() {
          match signal {
              SIGINT => {
                  tr.send("Received Ctrl+C").unwrap();
              }
              SIGTERM => {
                  tr.send("Received signal SIGTERM").unwrap();
              }
              SIGTSTP => {
                tr.send("Received Ctrl+Z").unwrap();
            }
              _ => unreachable!(),
          }
      }
  });
3 Likes

Wow! It worked. Thank you.