Skip to content

Commit

Permalink
docs: update blog post
Browse files Browse the repository at this point in the history
  • Loading branch information
sxyazi committed Jan 16, 2024
1 parent 60d3117 commit 34d2347
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 24 deletions.
63 changes: 42 additions & 21 deletions blog/2023-10-29-why-is-yazi-fast.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,38 @@ Internally, Yazi uses Tokio as its async runtime: hold on! Tokio's async may not

Uh, okay. From an application-layer perspective, it indeed is async; however, from a system-level view, there are possibly better solutions.

But! This is not the current performance bottleneck for Yazi. Considering Yazi is a TUI app, unlike CLI programs like `ls` and `exa` that need to output all files immediately, Yazi has more optimization opportunities at the application-layer:
But! This is not the current performance bottleneck for Yazi. Considering Yazi is a TUI app, unlike CLI programs like `ls` and `eza` that need to output all files immediately, Yazi has more optimization opportunities at the application-layer:

- For large directories (e.g., 100,000 files), Yazi uses [chunked loading](https://github.com/sxyazi/yazi/pull/117), which is unmatched by `ls` and `exa` since they must load everything at once.
- Yazi also preloads directory file lists in the background, an optimization that `ls` and `exa` do not possess.
- For large directories (e.g., 100,000 files), Yazi uses [chunked loading](https://github.com/sxyazi/yazi/pull/117), which is unmatched by `ls` and `eza` since they must load everything at once.
- Yazi also preloads directory file lists in the background, an optimization that `ls` and `eza` do not possess.

I must express my gratitude to Tokio for providing an excellent and convenient way to realize these application-layer optimizations.

I believe that the benefits brought by these application-level optimizations are more noticeable compared to switching to solutions like `io_uring`. But I remain open to this and welcome constructive PR.
I believe that the benefits brought by these application-level optimizations are more noticeable compared to switching to solutions like `io_uring`. But I'm open to this and welcome any constructive PR.

Here is a relevant discussion on Reddit: [reddit.com/r/rust/comments/16fxr58/comment/k066gmh](https://www.reddit.com/r/rust/comments/16fxr58/comment/k066gmh/)
Here is a relevant discussion on Reddit: https://www.reddit.com/r/rust/comments/16fxr58/comment/k066gmh/

## Pre-Caching
## Pre-Loading

Yazi provides pre-caching mechanisms for the following data:
Preloaders are part of Yazi's concurrent plugin system, and the entire pre-loading process is asynchronous and spans multiple threads. This means that preloaders can handle not only expensive IO tasks but also CPU-bound tasks! Here are some built-in preloaders in Yazi:

- Mimetype: The baseline. Yazi uses the file's mimetype as a reference for tasks such as opening, previewing, and style rendering, and internally utilizes `file(1)` to obtain the file's mimetype.
- Image: To accelerate image previews, Yazi employs a 2-pass process for image files. The first pass is preprocessing, which downscales the image based on user-set max_width/max_height and generates a compressed lossy image as a cache file, significantly reducing file size. The second pass occurs when the user actually switches to the file and downscales it again to fit the terminal size.
- Video: To speed up video previews, Yazi pre-converts them into images and goes through the first pass of image processing. When the user needs to display the video, it undergoes the same second pass.
- Mimetype: The baseline. Yazi uses the file's mime-type as a reference for tasks such as opening, previewing, and style rendering, and internally utilizes `file(1)` to obtain the file's mime-type. For better performance, Yazi computes them for files of an entire page, rather than for each file individually, and the entire process is chunked to minimize response latency.
- Image: To accelerate image previews, Yazi uses a 2-pass process for image files. The first pass is preprocessing, which downscales the image based on user-set max_width/max_height and generates a compressed lossy image as a cache file, significantly reducing file size. The second pass occurs when the user actually switches to the file and downscales it again to fit the terminal size.
- Video: To speed up video previews, Yazi pre-converts them into images and goes through the first pass of image processing. When the user needs to display the video, it goes the same second pass.
- PDF: Similar to video.
- Directory size: Yazi lazily calculates the directory size only when the user sets sorting by file size, as it is a time-consuming operation.
- Directory size: Yazi lazily calculates the directory size only when the user sets sorting by file size, as it's a time-consuming operation.

Note: Except for size, all of these are scoped, meaning that when you are on the first page, only the first few files will be pre-cached.
Note: Except for size, all of these are paged, meaning that when you are on the first page, only the first few files will be pre-loaded.

For example, if your directory has 1000 files, your terminal height is 10, and you are on the second page, only files 11 to 20 will be processed. This greatly saves resources.

## Discardable Tasks

Every preview task is discardable. When you navigate quickly between files and the previous file's triggered preview task is still not finished, it will be discarded directly, initiating a new task.
Every preview task is discardable. When you navigate between files quickly and the previous file's triggered preview task is still not finished, it will be discarded directly, initiating a new task. This promotes resource utilization:

This promotes resource utilization. For I/O tasks like loading directory lists, Tokio's `abort` is used; for CPU tasks like code highlighting, an `Atomic` is used to record a `ticket`, and it checks if the value changes on each line code highlight. If it changes, it indicates that the current context has changed, and the entire highlighting task is discarded.
- For I/O tasks like loading directory lists, Tokio's `abort` is used;
- For CPU tasks like code highlighting, an `Atomic` is used to store a `ticket`, and it checks if the value changes on each line code highlight. If it changes, indicates that the current context has changed, and the entire highlighting task is discarded.
- For I/O and CPU tasks like previewer/preloader plugins, with Lua, Yazi can check whether these tasks are canceled when a specific number of CPU instructions. If canceled, it interrupts the execution of the Lua script immediately, avoiding wasting more I/O and CPU resources.

## Code Highlighting

Expand All @@ -53,25 +55,44 @@ Other file managers that rely on external programs like `bat` need to wait for `

In cases like JSON that require external program `jq`, Yazi kills `jq` directly after reading the first 10 lines to avoid unnecessary resource consumption.

Since code highlighting is a CPU-intensive task, it is distributed among multiple blocking threads, managed through Tokio's spawn_blocking, and is also discardable.
Since code highlighting is a CPU-bound task, it is distributed among multiple blocking threads, managed through Tokio's spawn_blocking, and is also discardable.

## Image Preview

Yazi not only has built-in code highlighting but also includes image encoding and downscaling - there is likely nothing faster than having it directly built-in. It is also distributed among multiple threads and is discardable.
Yazi not only has built-in code highlighting but also includes image decoding and downscaling - there is likely nothing faster than having it directly built-in. It is also distributed among multiple threads and is discardable.

Besides being fast, Yazi's built-in Terminal graphics protocol, Inline images protocol, and Sixel graphics format allow Yazi to finely control when to display or hide images.
Besides being fast, Yazi's built-in Kitty graphics protocol, Inline images protocol, and Sixel graphics format allow Yazi to finely control when to display or hide images.

This ensures that in Yazi, there won't be issues, like images stacking on top of each other, or image escape code breaking the entire screen, when quickly navigating through images, as `stdout` is locked while outputting these escape codes. This locking happens after all image data is prepared, so it has no impact on performance.
This ensures that in Yazi, there won't be issues, like images stacking on top of each other, or image escape code breaking the entire screen, when navigating through images quickly, as `stdout` is locked while outputting these escape codes. This locking happens after all image data is prepared, so it has no impact on performance.

Yazi even supports partially erasing content in preview images, which is useful for pop-up components (Input, Select). The image won't overlap the input, and when the pop-up disappears, Yazi redraws the image to complete the erased portion automatically.

## Async Task Scheduling

In Yazi, tasks are automatically prioritized based on their severity. Yazi categorizes tasks into two types:
In Yazi, tasks are prioritized based on their severity automatically. Yazi categorizes tasks into two types:

- Macro tasks: Large and heavy tasks, such as copying large files, typically taking a long time to complete.
- Micro tasks: Small and urgent tasks, such as fetching file mimetype, pre-caching images, calculating directory size, and so on.
- Micro tasks: Small and urgent tasks, such as fetching file mime-type, pre-loading images, calculating directory size, and so on.

This is similar to having big and small cores in a CPU; when the big cores are idle, they help with the micro tasks. Yazi defaults to starting 5 micro workers and 10 macro workers, and these numbers can be configured by the user!

For complex tasks like file copying, a combination of micro and macro approaches is employed. Micro is used to recursively gather a list of all files to be copied, allowing users to see the number of tasks and their sizes in advance. Macro, on the other hand, handles the actual copying process.
In addition, Yazi introduces a priority scheduling mechanism. Each task has 3 priority levels: low, normal, and high. High-priority tasks can preempt low-priority tasks, applying to both micro and macro tasks. This increases task concurrency, slowing down HOL blocking caused by queuing execution of sudden requests.

For complex tasks like file copying, a combination of micro and macro approaches is employed. Micro is used to gather a list of all files to be copied recursively, allowing users to see the number of tasks and their sizes in advance. Macro, on the other hand, handles the actual copying process.

The advantage of task scheduling extends beyond providing ample concurrency for I/O and CPU resources; it also indirectly mitigates the depletion of system resources (such as file handles and CPU) due to sudden task surges.

## Other optimizations

The above optimizations are the most noticeable to users, but behind the scenes, Yazi has also done many other optimizations. Include but are not limited to:

- The re-implemented highly optimized natural sorting algorithm is [~6 times faster than the `natord`](https://github.com/sxyazi/yazi/pull/237) that `eza` uses in case-insensitive sorting.
- Yazi caches the directory state that has already been read, avoiding any unnecessary IO operations.
- When a file in a directory changes, it only updates the changed files rather than re-reading the entire directory list.
- Merges multiple renders triggered by multiple commands into a single render, avoiding unnecessary CPU consumption.
- Frequent updates to components, such as progress bars, are rendered independently, which is no cost compared to a complete render.
- The entire plugin system is designed with an asynchronous-first philosophy to avoid blocking the main thread with time-consuming tasks.

## TODO

I'll find time to continue writing.
6 changes: 3 additions & 3 deletions docs/configuration/keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ If you haven't created and used your own configuration file yet, please see [Con
- enter: Enter the child directory.
- back: Go back to the previous directory.
- forward: Go forward to the next directory.
- peek
- seek

- `n`: Peek up or down at file contents in the preview. Use negative values to peek up and positive values to peek down.
- `n`: Seek up or down at file contents in the preview. Use negative values to peek up and positive values to peek down.

- cd: Change the current directory.

Expand Down Expand Up @@ -170,7 +170,7 @@ If you haven't created and used your own configuration file yet, please see [Con
- `"natural"`: Sort naturally, e.g. `1.md` < `2.md` < `10.md`
- `"size"`: Sort by file size.
- `--reverse`: Display files in reverse order.
- `--dir_first`: Display directories first.
- `--dir-first`: Display directories first.

### Tabs

Expand Down

0 comments on commit 34d2347

Please sign in to comment.