Repository: sharkdp/fd
File: src/cli.rs (Rust)

Code before:
            })
            .transpose()
    }
}

/// Get the default number of threads to use, if not explicitly specified.
fn default_num_threads() -> NonZeroUsize {
    // If we can't get the amount of parallelism for some reason, then
    // default to a single thread, because that is safe.
    let fallback = NonZeroUsize::MIN;
    // fd generally scales well up to 8 threads. Past that, it's better to limit
    // ourselves to the number of physical cores, ignoring hyperthreads.
    let threshold = NonZeroUsize::new(8).unwrap();
    // To limit startup overhead on massively parallel machines, don't use more
    // than 64 threads.
    let limit = NonZeroUsize::new(64).unwrap();

    // Get the total number of CPUs available, including hyperthreads
    let threads = std::thread::available_parallelism().unwrap_or(fallback);
    if threads <= threshold {
        return threads;
    }

    // Compute min(threads, max(cores, threshold), limit). Avoid calling
    // num_cpus::get_physical() if we don't have to, since it costs a bit of
    // startup time to parse /proc/cpuinfo.
    let cores = NonZeroUsize::new(num_cpus::get_physical()).unwrap();
    threads.min(cores.clamp(threshold, limit))
}

#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
pub enum FileType {
    #[value(alias = "f")]
    File,
    #[value(alias = "d")]
    Directory,
    #[value(alias = "l")]
    Symlink,
    #[value(alias = "b")]
    BlockDevice,
    #[value(alias = "c")]
    CharDevice,
    /// A file which is executable by the current effective user
    #[value(alias = "x")]
    Executable,
    #[value(alias = "e")]
    Empty,
    #[value(alias = "s")]
    Socket,
    #[value(alias = "p")]
    Pipe,

Review comment (performance):
For my 6 core / 12 threads laptop, this would select

    min(12, max(6, 8), 64) = min(12, 8, 64) = 8

threads by default, instead of 12 previously.


This leads to a small performance regression for warm cache benchmarks:

`hyperfine --warmup 50 -L j 8,12 "fd -j{j} -u --search-path …"`

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `fd -j8 -u --search-path …` | 347.8 ± 7.3 | 341.8 | 367.2 | 1.06 ± 0.03 |
| `fd -j12 -u --search-path …` | 327.9 ± 5.1 | 322.9 | 341.5 | 1.00 |

And to a quite large performance regression for cold cache benchmarks:

`hyperfine --prepare "sync; echo 3 | sudo tee /proc/sys/vm/drop_caches" -L j 8,12 "fd -j{j} -u --search-path …"`

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `fd -j8 -u --search-path /home/ped1st/workspace` | 2.791 ± 0.017 | 2.780 | 2.837 | 1.36 ± 0.01 |
| `fd -j12 -u --search-path /home/ped1st/workspace` | 2.059 ± 0.018 | 2.038 | 2.082 | 1.00 |


This is a general observation that I made consistently in the past as well: cold cache traversals have a higher optimal thread count than warm cache benchmarks. Since there is no way to know, we should probably try to optimize for both scenarios? Or maybe favor one of the two, but still keep the other in mind.

Here is a parameter scan showing both warm cache (red) and cold cache (blue) results:

![image](https://github.com/sharkdp/fd/assets/4209276/d5c425e6-04b0-4f2f-a651-5c51ba6224a5)


Code after:
            })
            .transpose()
    }
}

/// Get the default number of threads to use, if not explicitly specified.
fn default_num_threads() -> NonZeroUsize {
    // If we can't get the amount of parallelism for some reason, then
    // default to a single thread, because that is safe.
    let fallback = NonZeroUsize::MIN;
    // To limit startup overhead on massively parallel machines, don't use more
    // than 64 threads.
    let limit = NonZeroUsize::new(64).unwrap();

    std::thread::available_parallelism()
        .unwrap_or(fallback)
        .min(limit)
}

#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
pub enum FileType {
    #[value(alias = "f")]
    File,
    #[value(alias = "d")]
    Directory,
    #[value(alias = "l")]
    Symlink,
    #[value(alias = "b")]
    BlockDevice,
    #[value(alias = "c")]
    CharDevice,
    /// A file which is executable by the current effective user
    #[value(alias = "x")]
    Executable,
    #[value(alias = "e")]
    Empty,
    #[value(alias = "s")]
    Socket,
    #[value(alias = "p")]
    Pipe,
}

#[derive(Copy, Clone, PartialEq, Eq, Debug, ValueEnum)]
pub enum ColorWhen {
    /// show colors if the output goes to an interactive console (default)
    Auto,
    /// always use colorized output
    Always,
    /// do not use colorized output
    Never,
}