2024年10月21日 14:20

This commit is contained in:
许大仙 2024-10-21 06:20:38 +00:00
parent 1c98f64d61
commit e5a7d6d046

View File

@ -20,7 +20,7 @@
> - `存储器`(内存)保存数据和程序,是计算机运作的基础。
> - `输入设备`和`输出设备`负责与外界的交互,确保用户能够输入信息并接收计算机的处理结果。
>
> 直到今天,即使硬件的发展日新月异,但是现代计算机的硬件理论基础还是《冯·诺依曼体系结构》。
> 直到今天,虽然硬件的发展日新月异,但是现代计算机的硬件理论基础还是《冯·诺依曼体系结构》。
## 1.2 冯·诺依曼体系结构的瓶颈
@ -86,16 +86,16 @@
> - ① 存储器越往上速度越快,但是价格越来越贵, 越往下速度越慢,但是价格越来越便宜。
> - ② 正是由于计算机各个部件的速度不同,容量不同,价格不同,导致了计算机系统/编程中的各种问题以及相应的解决方案。
* 正是由于 CPU、内存以及 IO 设备之间的速度差异,从而导致了计算机的性能瓶颈,即所谓的`“冯·诺依曼体系结构的瓶颈”`。
* 正是由于 CPU、内存以及 I/O 设备之间的速度差异,从而导致了计算机的性能瓶颈,即所谓的`“冯·诺依曼体系结构的瓶颈”`。
![](./assets/5.svg)
* 因为 CPU 的处理速度远远快于内存和 IO 设备导致在等待数据处理和传输的时候CPU 大部分处于空闲状态。就是这种显著的速度差异就导致了计算机的性能瓶颈,限制了整个计算机系统的效率。
* 因为 CPU 的处理速度远远快于内存和 I/O 设备导致在等待数据处理和传输的时候CPU 大部分处于空闲状态。就是这种显著的速度差异就导致了计算机的性能瓶颈,限制了整个计算机系统的效率。
> [!NOTE]
>
> * 对于硬件的这种显著的速度差异,我们程序员是无法解决的。
> * 但是,为了平衡三者之间的速度鸿沟,我们可以通过引入`缓冲区`技术,来降低系统的 IO 次数,降低系统的开销。
> * 但是,为了平衡三者之间的速度鸿沟,我们可以通过引入`缓冲区`技术,来降低系统的 I/O 次数,降低系统的开销。
* 其实,在硬件上也是有`缓冲区`的CPU 内部集成了缓存,将经常使用到的数据从内存中加载到缓存中。
@ -136,6 +136,10 @@
* ③ `操作系统处理输入`:操作系统接收到`中断信号`后,立即获取键盘数据并处理。由于没有缓冲区,操作系统必须将数据立即传递给程序。
* ④ `程序直接读取数据`:程序必须在键盘每次输入后立即读取数据,并且处理这个输入,不会有任何数据被暂存或积累。
* 其对应的图示,如下所示:
![](./assets/8.png)
> [!NOTE]
>
> 如果没有缓冲区,键盘输入的数据将无法有效地被程序管理和处理,系统的工作效率会显著下降,具体影响体现在以下几个方面:
@ -150,46 +154,35 @@
> - **输入延迟**:程序必须实时处理每个键盘输入,用户输入数据的速度一旦超过程序的处理能力,可能导致输入延迟或丢失输入。
> - **无法处理复杂输入**:如果用户需要输入多个字符或进行复杂的输入操作(比如连续输入多个命令),程序可能难以一次性正确处理,因为它只能逐一处理每一个输入,而无法一次性获取多个输入进行批量处理。
* 其对应的图示,如下所示:
![](./assets/8.png)
> [!IMPORTANT]
>
> 其实C 语言中的 `printf` 函数和 `scanf` 函数,其内部就使用了缓冲区。
>
> * ① 当我们使用 `printf` 函数输出数据的时候,数据并不会立即就写出到输出设备(如:屏幕等)。而是先将其放置到 `stdout 缓冲区`中,然后在满足条件的时候,再从缓冲区中刷新到输出设备。
> * ② 当我们使用 `scanf` 函数输入数据的时候,数据并不会立即就从输入设备中读取(如:键盘等)。而是先将其放置到 `stdin 缓冲区`中,然后在满足条件的时候,再从缓冲区中加载数据。
### 1.3.3 缓冲区的好处
* 使用缓冲区的好处就是`减少了 I/O 操作的频率,降低了系统资源的消耗,提高了系统的性能,提升了用户的使用体验`。
* 使用缓冲区的好处:`减少了 I/O 操作的频率,降低了系统资源的消耗,提高了系统的性能,提升了用户的使用体验`。
### 1.3.4 缓冲区是如何提高 IO 操作的频率?
### 1.3.4 缓冲区是如何提高 I/O 操作的频率?
* 对于 C 语言中的 `printf` 函数和 scanf 函数,其功能如下:
* `printf` 函数:将程序中的数据输出到外部设备(如:显示器)中。
* `scanf` 函数:从外部设备(如:键盘)中读取数据到程序中。
* 这些都是非常典型的 IO 操作,并且 IO 过程效率也是很低的。除了硬件性能本身的差异外IO 操作的复杂性也是非常重要的因素,每次 IO 操作都会带来一些固定的开销,如:
* ① 每次 IO 操作都需要设备初始化和响应等待。
* ② 操作系统管理 IO 请求,涉及中断处理和上下文切换,这些都消耗了大量时间。
* ③ 应用从用户态切换到内核态的系统调用也会带来额外的时间开销。IO 操作普遍涉及系统调用)
* 这些都是非常典型的 I/O 操作,并且 I/O 过程的效率也是很低的。除了硬件性能本身的差异外I/O 操作的复杂性也是非常重要的因素,每次 I/O 操作都会带来一些固定的开销,如:
* ① 每次 I/O 操作都需要设备初始化和响应等待。
* ② 操作系统管理 I/O 请求,涉及中断处理和上下文切换,这些都消耗了大量时间。
* ③ 应用从用户态切换到内核态的系统调用也会带来额外的时间开销。I/O 操作普遍涉及系统调用)
* ④ ...
* 如果每输入一个字符或每输出一个字符都需要进行一次完整的 IO 操作,那么这些固定的开销会迅速积累,进而导致系统的性格显著下降。
* 如果每输入一个字符或每输出一个字符都需要进行一次完整的 I/O 操作,那么这些固定的开销会迅速积累,进而导致系统的性格显著下降。
* 硬件层面的效率低下,我们没有办法通过软件层面的优化去解决。但对于这些大量的固定开销,我们可以通过`缓冲区`来进行效率优化。
> [!IMPORTANT]
>
> * ① 缓冲区的主要目的是暂时存储数据,然后在适当的时机一次性进行大量的 IO 操作。
> * ② 这样,多个小的 I/O 请求可以被组合成一个大的请求,有效地分摊了固定开销,并显著提高了总体性能。
> * ① 缓冲区的主要目的是暂时存储数据,然后在适当的时机一次性进行大量的 I/O 操作。
> * ② 这样,多个小的 I/O 请求可以被组合成一个大 I/O 的请求,有效地分摊了固定开销,并显著提高了总体性能。
* 对于 `scanf` 函数而言,当用户通过键盘输入字符的时候,这些输入的字符首先被保存在 `stdin` 的缓冲区中,,`当满足某个触发条件后`,才传递给程序处理。这样就减少了总的 IO 次数,提高了效率。
* 对于 `scanf` 函数而言,当用户通过键盘输入字符的时候,这些输入的字符首先被保存在 `stdin` 的缓冲区中,,`当满足某个触发条件后`,才传递给程序处理。这样就减少了总的 I/O 次数,提高了效率。
* 对于 `printf` 函数而言,输出的内容首先会保存到 `stdout` 的缓冲区中,`当满足某个触发条件后`,这些内容会一次性写入并显示到屏幕,降低了与显示设备的交互频率。
> [!NOTE]
>
> * ① 如果你还不能理解,就可以将 IO 操作想象是搬家。对于搬家而言,需要搬运东西的总量是固定的,搬一趟的时间也是差不多的。我们当然希望:一次性搬东西尽量多,搬运的次数尽量少,这样总耗时就少。
> * ② 不使用缓冲区,就类似每次搬家只能手提一个东西,频繁的往返。而使用缓冲区,就好比我们使用一个小推车,可以一次性的搬运多个东西,极大的提高了效率。
> * ① 如果你还不能理解,就可以将 I/O 操作,看做是搬家。对于搬家而言,需要搬运东西的总量是固定的,搬一趟的时间也是差不多的。我们当然希望:一次性搬东西尽量多,搬运的次数尽量少,这样总耗时就少。
> * ② 不使用缓冲区,就类似每次搬家只能手提一个东西,需要频繁的往返。而使用缓冲区,就好比我们使用一个小推车,可以一次性的搬运多个东西,极大的提高了效率。
### 1.3.5 缓冲区的分类
@ -202,7 +195,7 @@
* 之前,我们经常会在代码中,会加入以下的代码,其实就是为了让行缓冲变为无缓冲,如下所示:
```c {6}
#include <stdio.h>
#include <stdI/O.h>
int main() {
@ -228,7 +221,7 @@ int main() {
> [!NOTE]
>
> * ① setbuf 是 C 语言标准库中的一个函数,用于设置文件流的缓冲区。它允许程序员控制 IO 操作的缓冲行为,从而影响文件流(如 `stdin`、`stdout` 或文件指针 `FILE *` 类型)的效率和顺序。
> * ① setbuf 是 C 语言标准库中的一个函数,用于设置文件流的缓冲区。它允许程序员控制 I/O 操作的缓冲行为,从而影响文件流(如 `stdin`、`stdout` 或文件指针 `FILE *` 类型)的效率和顺序。
> * ② 其定义,如下所示:
>
> ```c
@ -238,7 +231,7 @@ int main() {
> */
> void setbuf(FILE *stream, char *buf);
> ```
> * ③ 不同的编译器和开发环境可能会对输出缓冲进行特殊设置,尤其是在调试模式下,以便提供更好的调试体验,例如:微软的 MSVC 在 debug 模式下即使没有换行符printf 函数的输出通常也会立即显示在控制台上。这种行为是为了帮助程序员更有效地调试程序,即时看到他们的输出,而不需要固定等待缓冲区刷新条件。但是遗憾的是GCC 在 debug 模式中,并没有这么做!!!
> * ③ 不同的编译器和开发环境可能会对输出缓冲进行特殊设置,尤其是在调试模式下,以便提供更好的调试体验,例如:微软的 MSVC 在 debug 模式下即使没有换行符printf 函数的输出通常也会立即显示在控制台上。这种行为是为了帮助程序员更有效地调试程序即时看到他们的输出而不需要等待缓冲区刷新条件。但是遗憾的是GCC 在 debug 模式中,并没有这么做!!!
> [!IMPORTANT]
>