Something unintelligible about hot loop

I am a beginner and I am currently reviewing the documentation for rust. For macro print! I am confused about some of the descriptions.

The print! macro will lock the standard output on each call. 
If you call print! within a hot loop, 
this behavior may be the bottleneck of the loop. 
To avoid this, lock stdout with io::stdout().lock():

What is a hot loop, why does this phenomenon occur, what is the specific phenomenon of the bottom of the loop it can cause, and what are the consequences of this.

Based on what demurgos said,I have a general understanding of 'a hot loop', but can there be an intuitive example that can help me better understand it from an objective perspective?

(Here was what he said about 'a hot loop'@demurgos)
I recommend using the users forum for usage questions. The internals forum is probably right to discuss how to update the documentation; but here you seem more confused by the concept. The users forum is better suited to answer this kind of questions.


One of my views is that the so-called "a hot loop" is a streamlined program with high usage frequency.

A hot loop can be present in any program. It's the part of the code that where performance changes have the most impact, because it's repeatedly called very often.

In a typical program, not all functions are called as often. Some code is only used during initialization, some during clean-up, some only occasionally, and some performs most of the work. This part doing most of the work is usually called in a loop: this is the hot loop.

If we simplify even more, ask yourself in which part of your code a 100x performance improvement would matter the most. In some parts like initialization, 100x improvement means that the program is a few milliseconds faster; while in other parts it means that your program goes from ten minutes to a few seconds. These parts where performance changes matter form the hot loop.

Here is an example program that can be divided in three parts:

fn main() {
  // Initialization
  let mut input: String = String::new();
  println!("How many squares to print?");
  std::io::stdin().read_line(&mut input).expect("failed to read stdin");
  let n: usize = input.trim().parse().expect("invalid input");

  // Main part
  for i in 0..n {
    let square = i * i;
    println!("{i}² = {square}");
  }

  // Shutdown
  println!("done");
}

The main part of this program can be considered a hot loop: if we want the program to go as fast possible, making the loop body faster will have way more impact than optimizing the initialization or shutdown code. The documentation you quoted in your original post recommends to use io::stdout().lock() instead of the print macro in hot loops:

use std::io::Write;

fn main() {
  // Initialization
  let mut input: String = String::new();
  println!("How many squares to print?");
  std::io::stdin().read_line(&mut input).expect("failed to read stdin");
  let n: usize = input.trim().parse().expect("invalid input");

  {
    // Still initialization
    let mut stdout = std::io::stdout().lock();

    // Hot loop
    for i in 0..n {
      let square = i * i;
      writeln!(stdout, "{i}² = {square}").expect("failed to write");
    }
  }

  // Shutdown
  println!("done");
}

In the new code, locking is moved out of the hot loop: it's now only performed once during initialization instead of every iteration.


Aside: I used println/writeln instead of print/write from the original post. It does not change the main idea, but there may be considerations about flushing if you care about I/O perf.


EDIT: I found a Wikpedia article on hot spots describing a similar concept.

Imagine you are running a game and there are currently thousands of different entities on the screen (the player, NPCs, random objects like cups and books, projectiles, etc.) and the game updates 60 times per second.

Every time the game does an update, it'll need to calculate the new position for an entity based on its current position and velocity. As you can imagine, this calculation is trivial (next_position = current_position + velocity*delta_time), but we'll be executing it a lot, quite possibly tens of thousands of times per second.

Now imagine you add a print statement in which logs the new position of each entry for debugging purposes. If that print statement needs to lock a mutex (which may involve a syscall or sleeping temporarily), then an operation that used to take tens of nanoseconds could start to take hundreds of microseconds. If you do that for all the game entities on every frame, your game is going to go from 60 FPS to 0.1 FPS pretty quickly.

That for entity in entities { update_entity() } loop inside the physics system would be called a "hot loop".

By locking stdout you'll still need to make the allocations and string formatting associated with logging, but at least you skip the (considerably more expensive) cost of locking a mutex.

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.