哈哈,我又来了。依然,先说是不是,再问为什么。

别人我不知道,反正我的经验,Windows就是越用越慢。这是为什么呢?

本文是下文的姊妹篇:
为什么Windows/iOS操作很流畅而Linux/Android却很卡顿呢? https://blog.csdn.net/dog250/article/details/96362789
所以,上述文章的声明,本文依然适用,不再赘述。


这个话题网上一搜一箩筐,不外乎以下:

  • Windows会在C盘创建很多临时文件,临时文件越来越多。

这个貌似没有直击问题。我们要问几个为什么:

  1. 这些临时文件是用来干什么的?
  2. 为什么临时文件会越来越多?
  3. 如果临时文件越来越多,为什么会使系统变慢?
  4. Linux难道就不创建临时文件吗?

在详细回答上面几个问题之前,首先要说一个非技术方面的事实,或许会回答本文题目的问题有所指引。

使用Linux系统的几户都是非常懂计算机原理的用户,至少也是会编程的,而Windows用户就比这个群体更加普遍了,几户包括所有人。Linux用户是不会等到系统出问题才会有所察觉的,换句话说,Linux用户是专家用户,他们的 使用习惯 就不会让系统变慢,Linux用户使用系统的过程,本身就是一个维护系统的过程,而Windows用户根本就不操心这个 技术问题 ,所以Windows系统可能会在用户无暇照料维护久了之后,慢慢地变慢了。

所以,问这个 “为什么Windows系统越用越慢而Linux不会?” 的问题,即便是得到了肯定的回答,也不代表Linux更加优秀,由于用户群体不同,这个问题对于Windows是不公平的。这就好比问 为什么美女比男生干净? 这个问题一样,答案会让你陷入困境, 因为美女不会等到脏了才去洗,而男生可能根本就不在乎脏不脏,意识到脏了,其实也是慢慢变脏的。 这是一个自解释的悖论,难道爱干净不就是美女必然不充分条件吗?公平的评判方法应该是,男生女生同样的生活习惯和清洁习惯,然后再问谁更干净。

好了,开始正文。


个性要看出身,换句话说要看基因,我是信这个的。

我们要知道Windows和Linux出身之大不同。

Windows NT作为一个个人计算机PC操作系统,可以说最初的思想源自于家酿俱乐部那帮极客的折腾经验,受到了Apple的刺激,然后集MS-DoS,OS/2等于一身,后来者居上了。个人操作系统就好像是在菜市场里拼凑出来的,一开始并没有什么作为一个完整的操作系统的设计感,它更像是一个CP/M那样的微机监视程序,提供一些基本的功能,类似:

  • 屏幕上显示一个字符。
  • 往磁盘等介质上写一个字符。
  • 往显示设备写一个字符。
  • 接受键盘输入。

这非常不同于大教堂般的实验室里出来的带有设计感的UNIX,而Linux则继承了UNIX的设计感。

可以看到,PC操作系统一开始就是为了 人机交互 这个非常单纯的目标而逐步构建起来的。即便是到了如今的Windows 10,也依然是一个 优秀的人机交互系统

虽说Windows早就是一个多任务系统了,但是从人机交互的视角来看,它更像是一个单进程的系统,只有前台焦点窗口的处理进程才是需要处理的,其他的被盖在后面的窗口的处理进程并不需要持续运行占据着资源而不用,它们不需要提供什么服务,在被用户切换到前台被用户操作前,它们几乎没有什么重要的事情可做。这一点应该可以理解。

侧重人机交互的系统,其本身就是用户从外部的操作引发的事件驱动运行的,只有前台焦点窗口的处理进程是活动的,其它的都是不活动的,因此将资源倾斜给前台焦点窗口的处理进程,就可以给用户非常好的操作体验,既包括CPU资源,也包括内存资源。其它不活动进程的资源既然可以让给前台进程,那么它们的数据怎么办?换出到磁盘呗。

我们可以从DoS操作系统看清楚这个事实的本质。

DoS系统虽然没有图形界面,但是它却可以完美诠释人机交互系统的本质, 前台进程占有大部分的资源甚至所有资源,其它进程资源序列化到磁盘等待。 自这个原始DoS系统开始,一直到Windows 10,在这一点上,一直是不忘初心。

举个例子。

如果我在Windows上同时打开了Word和Firefox,在我用Word写文档的时候,Firefox就可以把资源全部出让,自己的内存换出去到磁盘,以达到Word操作的最优化,至少它的大部分内存可以换出去。这有点像协作式多进程。虽然,在如今CPU如此多核且强劲,内存也是如此充盈的时代,没有必要这般,但是如果遇到资源紧缺,系统是有这个能力这般实施策略的。

现在看Linux。

作为服务器的明星操作系统Linux和Windows对待进程的方式非常不同。

以Apache,Nginx Web服务已经Memcached缓存服务为例,一般而言这些Web服务会有大量个Worker进程来处理客户端的请求,而客户端的请求是 随机到达 的。为了最高效率地公平对待每一个客户端的请求,最大化系统的数据吞吐,必须在这多个Worker进程之间做公平轮转调度,这些Worker之间,这些Worker和可能同时存在的数据库缓存服务之间,不存在前台和后台的区别,更不存在焦点的概念。

对于Web服务或者数据库缓存服务等一系列的服务而言, 处理逻辑和数据是独立的,分开的 ,也就是说,多个不同的并发Worker进程并不妨碍它们共享同一份数据,“处理和数据分开”便映射成了CPU和内存如何分配的问题。

  • CPU公平轮转调度Worker进程。
  • 同一份数据被常驻内存。

CPU公平轮转调度多个进程是为了将串行的CPU时间并行化,转换为空间问题,将同一份数据同时推给给多的客户端,而数据常驻内存则是为了让数据的读取更加有效率。

从以上这一点来看,这就解释了为什么 Linux总是将更多的剩余物理内存用来缓存更多的文件。

Windows的方法与此完全不同。

Windows系统中 处理逻辑和数据不是独立的。 Word不会去动Firefox的数据,QQ也不会去动PPT的数据,几乎每一个进程都有自己的一套处理逻辑和数据。

DoS系统伊始就携带了大量的覆盖,换出等技术,一直到了现在,这些技术依然在起作用。为了便于理解,下面让我们极端点说,虽然你我都知道,系统并不是真的如此运作,但是显然,极端点描述,便于理解本质。

对于Windows而言,系统所有的CPU和内存资源都会全部赋予前台焦点窗口的处理进程,后台不活动窗口的处理进程将全部被换出到磁盘。这意味着磁盘上必须为每一个进程分配内存的换出空间,其处理逻辑和内存资源各自一份:

  • 处理逻辑即代码段,换出到其exe可执行文件本身即可。
  • 内存则需要单独创建一个文件用来换出。

代码段换出到exe可执行文件非常好理解,这就是典型的文件映射机制。而内存为什么要换出到一个文件呢?其实这还是文件映射的机制。Windows之所以没有采用交换分区而采用了交换文件,这也是因为一开始就是这样子的。

在PC上安装的物理非常小的20世纪80年代, Windows是把文件当后备内存用的!

我在Linux上演示一下如何以及为什么把文件当后备内存来用。

[root@10 map]# cat /proc/meminfo |grep MemTotal
MemTotal:        1016860 kB

我安装了1G的物理内存,已经不小了,但是我需要2G的内存来参与运算,怎么办?我写下以下的代码:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SIZE	(4096*1024*500)

int main(int argc, char *argv[])
{
	char *addr;

	addr = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
	if (addr == MAP_FAILED) {
		perror("mmap");
	}

	memcpy(addr, "abcdefg", 7);

	printf("%s\n", addr);

	{ // 访问2G的所有字节
		int i = 0;
		for (i = 0; i < SIZE; i++) {
			addr[i] = 1;
			printf("%d ", i);
		}
	}

	munmap(addr, 4096*1024*20);

	return 0;
}

关闭所有的交换空间:

[root@10 map]# swapoff -a
[root@10 map]# free -m
              total        used        free      shared  buff/cache   available
Mem:            993         102         708          50         181         712
Swap:             0           0           0

执行之:

[root@10 map]# ./a.out
mmap: Cannot allocate memory
段错误

分配内存失败。很显然,我们没有那么大的内存。怎么办呢?可以把文件当内存用:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define SIZE	(4096*1024*500)

int main(int argc, char *argv[])
{
	char *addr;
	int fd;
	ssize_t s;

	fd = open("file_as_mem", O_RDWR|O_CREAT);
	if (fd == -1) {
		perror("open");
	}

	ftruncate(fd, SIZE); //设置文件的大小,但是并不分配任何磁盘块!

	addr = mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	if (addr == MAP_FAILED) {
		perror("mmap");
	}

	memcpy(addr, "abcdefg", 7);

	printf("%s\n", addr);
	{
		int i = 0;
		for (i = 0; i < SIZE; i++) {
			addr[i] = 1;
			printf("%d ", i);
		}
	}
	munmap(addr, 4096*1024*20);
	close(fd);

	return 0;
}

和上面的代码几乎一样,只是把匿名内存换成了一个磁盘上很大的文件。再执行就成功了。

这说明当你的物理内存小于你的需求的时候,为了让程序仍然可以执行,你可以使用文件来作为后备。

Windows系统作为人机交互系统,为了保证最佳的操作体验, 一拉一推两方面保证了前台焦点窗口的处理进程的资源配额最大化

  • 将其它非前台焦点窗口处理进程的内存换出到磁盘。
    【或许它不这么做,但它有能力且偏向于这么做】
  • 为自己创建交换文件,以保证自己内存需求不能满足时,程序仍可继续。
    【可以默认使用C盘根目录的交换文件pagefile.sys,也可以类似上面程序,自己直接以文件映射方式分配内存】
    采用文件映射的方式使用内存,可以保证内存的非易失 ,这进一步增强了用户操作体验,一旦系统或者程序崩溃,内存依然在映射的文件里,下次程序重新执行时即可恢复。

如此策略的结果是什么?很明显,磁盘I/O会增加,这也是为什么Windows系统在使用过程中总是听到磁盘哒哒响的原因之一,特别是安装物理内存比较小的时候。

当你安装的程序非常多时,同时打开的进程也非常多,以上的过程就会表现为更加繁重的磁盘寻道,而磁盘寻道是一个典型的重体力活儿,系统自然会显得比较慢。

这是其一。其二呢?也是更加重要的,其二是临时文件在搞鬼。

Windows为了提高效率,它总是会 试图保存一些记忆,以便下一次操作时更加快速。 这就是缓存咯。这种缓存往往以临时文件的方式存在:

  • 用户的操作历史记录。
  • 用户的操作结果记录。
  • 用户编辑未完成的文件。

这些数据缓存往往以最优的数据结构存储在文件系统的临时文件中,当下一次再需要的时候,系统可以高效地将其调出。Windows作为人机交互系统在这方面考虑的非常周到。人的操作习惯是具有反复性的,对待操作结果的获取,也是具有局部性的。

以Firefox为例,用户的访问历史会被缓存,用户的DNS查询结果会被缓存,用户的表单输入情况会被缓存,这些都是以文件的形式存在。此外,一些脱机网页也会被缓存,图片等多媒体信息就更不用说了,这也是为了为用户甚至整个互联网节省带宽资源。

如果这些临时文件变的越来越多,那么定位访问其中某个文件的时间就会增加,表现出来就是系统变慢了。

可是为什么这些临时文件会越来越多?这可不是Windows的锅。

按照Windows的编程规范,如何创建和使用临时文件以提高效率都被写在编程手册里,但是这并不意味着每一个软件厂商都是严格遵守里面的约定,最基本的,很多软件厂商提供的软件在创建了临时文件后,并没有任何删除策略,这种软件越来越多时,就会让C盘显得越来越臃肿,最终系统就会越来越慢。

所以说,最终的删除工作,全凭用户自己来完成,还好,网上的攻略一搜一箩筐。


好了,现在让我们试图回答本文最初的那几个问题:

  1. 这些临时文件是用来干什么的?
    用来做内存的文件映射或者临时文件的,比如各种缓存之类。
  2. 为什么临时文件会越来越多?
    很多软件使用映射文件以及临时文件的方法不正确不严谨。
  3. 如果临时文件越来越多,为什么会使系统变慢?
    杂碎小文件越来越多,磁盘寻道效率降低,且磁盘磨损也增加,寿命减少。
  4. Linux难道就不创建临时文件吗?
    谁说不创建临时文件的??

Linux也创建临时文件,Linux也会不断写一些文件。我们最常见的两类:

  1. bash的history文件,这是个隐藏文件,无疑,它会越来越大。
  2. 当我们vim一个文件f时,会创建.f.swp临时文件,帮我买保存我们未保存的中间信息。

Linux之所以没有因为临时文件而让系统变慢,本质上的原因是Linux对待内存和文件的关系和Windows大不同。

  • 某种意义上,Linux并不把文件当成内存的后备。
  • Linux对待临时文件时,它就是临时文件,它就是易失的,所以它往往在一个内存文件系统中。
  • Linux若想使用缓存,那么它就一定只能离CPU越来越近,而不能相反,如果缓存曾经在内存中,那么它只能进CPU的cache而不能进磁盘文件。

Linux在将内存尽可能公平地分配个所有的服务进程之后,对待剩余内存就是用来缓存文件数据,对待换出这件事,只是统一采用LRU,将最近最不经常touch的匿名内存换出到交换空间,至于文件,也只是在万不得已时才会将脏数据刷到磁盘并丢弃内存页面。

Linux之所以如此大方的手笔,其实在于它诞生的年代。在Linux诞生的90年代初,PC物理内存就已经是几十MB的了,作为一个从新设计出来的系统,它没有历史的包袱。甚至,Linux把UNIX使用内存的策略都重写了(虽然字面上还有很多带有swap字眼,但已经没有swap的含义了)。


该总结下了:

  • Windows把文件当内存的后备来用。
    Windows的内存资源优先供给前台焦点窗口的处理进程,即你正在操作的那个窗口的处理进程。
    当前进程内存吃紧,焦点窗口切换均会带来可观的磁盘I/O。甚至缓存这种内存风格的数据也会写入磁盘临时文件。
  • Linux把内存当文件的缓存来用。
    Linux的内存多用来缓存多个服务进程的共享数据。
    作为文件系统缓存的内存,只有在涉及数据一致性的时候,才会进行磁盘I/O,而Linux的任何缓存几乎都是明确不落盘的。

Windows本身的设计目标就是一切为了人机交互,一旦发现有进程转入不活跃状态,就会倾向于把分配给它的内存交换到文件中,尽可能空出物理内存给当前活跃的焦点进程,并不是用完物理内存才开始动用交换文件。因为如果等到物理内存吃紧再交换,就已经太迟了,在配置较低的机器上势必造成由于磁盘I/O带来的处理停滞卡顿。

这就涉及到了Windows+Intel=Wintel了,Windows好像默认就是希望你升CPU加内存的。不让恶意揣测一下这背后的商业逻辑,也许初衷并非恶意,但是歪打正着了呗:

  • 越来越大的程序需要越来越多的内存,由于可以拿文件当内存用,这些程序依然能运行,但是磁盘I/O会造成感受得到的卡顿,那就升CPU加内存呗,如此反复。

以上的交换逻辑,对于Linux而言,必须是明确的:

  • 要么明确使用固定大小限制的交换分区。
  • 要么在编程时显式使用磁盘文件mmap到内存。

否则,就像上面的代码所示,直接mmap一个当前物理内存无能为力的空间,便直接失败了。换句话说,Linux是明确区分内存和磁盘文件的层级的。

当然了,操作系统设计时的基因固然重要,然而你若说这种基因带来了这一切,未免太过,除了基因,还有后天的因素。本文开头和中间提到了两个后天因素:

  • 使用Linux的用户和使用Windows的用户不是一类用户,所以,Linux用户 知道如何时时维护系统以保持高效 而大多数的Windows用户没有这个能力。
  • Windows的编程规范并非每个软件厂商都遵守的,未符合规范的程序造成了系统的临时文件堆积,这并不是Windows系统本身的问题。

此外,由于Linux系统作为服务器广泛部署的系统,其运行的程序非常单一,而Windows作为个人广泛使用的操作系统,其安装的软件五花八门,没有一套可行的认证机制,所以更加加剧了软件鱼龙混杂的局面,不符合规范的程序固然带来了非常多的问题。

所以本文题目中的对比没有技术上的意义,更合适的,你也许应该问:

  • Windows XP为什么越用越慢而Windows 8却不会?
  • Suse为什么会越用越慢而Debian却不会?

同一个性质的东西比较起来,才好玩。


明早还乡。

浙江温州皮鞋湿,下雨进水不会胖。

08-16 23:39