先说结论,Windows文件系统的性能就是要比Linux慢,这是一个事实。我做过Windows的文件系统开发(写文件系统),也研究过Linux文件系统,还做过UNIX-like
的文件系统的设计和开发,Windows的文件系统在各个方面性能都不如Linux。
题主这个问题分两部分回答:
一、应用软件搜索慢
Windows在文件系统里搜索的时候,会搜索一部分文件内容,会展开搜索一部分压缩包(比如CAB文件)的内容,并不是只搜索文件名,所以搜索负担更重(要打开文件并读取内容)。而开始菜单里(也就是题主说的左下角)搜索是基于预先创建的索引,所以更快一些。
这本质上是软件设计的问题,Windows如果用其它搜索工具,比如everything等,搜索速度是比自带的搜索要快一些的。但Windows native API仍然比不过Linux,所以才有第二部分。
二、文件系统访问慢
为了证明这一点,我专门做了一个实现,分别在Windows和Linux平台写两个最简单的递归枚举目录的代码,Windows使用FindNextFile(),Linux使用readdir(),递归枚举一个目录树
,然后计算时间。其中Windows代码是直接运行的,Linux是Ubuntu 20.04运行在虚拟机里。关闭所有杀毒软件和可能影响性能的软件。
被测的目标目录是VirtualBox6.1.32的源码包含4万多个文件和目录,总大小1G多,Windows和Linux虚拟机都在同一个磁盘上,存储介质速度是一致的(准确的说Linux会更慢,因为是虚拟机)。
开机首轮测试 | 第二次 | 第三次 | |
---|---|---|---|
Windows | 6868 ms | 197 ms | 195 ms |
Linux虚拟机 | 248 ms | 52 ms | 64 ms |
可以看到不管是无cache(开机首轮测试)还是有cache的情况下,Windows的文件系统性能都远不如Linux,所以Windows文件系统慢是一个事实,而不仅仅只是用户感觉。
慢的原因有多方面的,可以通过分析Linux代码和Windows的源码(基于WinXP泄漏的源码,还有WRK)来得到结论:
1. Windows的枚举目录项的操作效率太低。
因为读目录每次都要经过一次系统调用,所以不管是Windows还是Linux都在设计上尽量减少系统调用的次数。
Windows提供的FindNextFileW这个API,每次通过函数NtQueryDirectoryFile最后经系统调用到文件系统驱动,获得目录内的子文件信息,为了减少系统调用次数,NtQueryDirectoryFile一次可以获得多个子文件信息,放到一个缓存里,这个缓存的大小是4K:
#define FIND_BUFFER_SIZE 4096
Linux提供的readdir()里也有类似的动作,Linux的默认缓存是BUFSIZ * 4,定义如下(位于glibc):
#define BUFSIZ 8192
也就是说Linux的默认缓存是Windows的8倍大小,从直观上就可以看出Linux一次系统调用能获取的子文件项目更多。
同样是获取4万多个文件信息,Windows需要的系统调用要比Linux多。系统调用对于操作系统来说是一个巨大的开销,Windows比Linux要慢,大部分原因都是因为系统调用太多导致的。
2. Windows目录信息比Linux要多。
Windows文件系统里有个短名的概念,并且Windows默认是UTF-16
的编码,所以同样一个英文字符串文件名,Linux要比Windows少至少一半的数据:
Windows:
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
WCHAR cFileName[MAX_PATH];
WCHAR cAlternateFileName[14];
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
Linux:
struct dirent
{
#ifdef __USE_FILE_OFFSET64
__ino64_t d_ino;
#else
__ino_t d_ino;
int __pad;
#endif
__off_t d_off;
unsigned short int d_reclen;
unsigned char d_type;
char d_name[256]; /* We must not include limits.h! */
};
另外,因为Windows一个文件有两个名字:长名和短名,所以Windows的name cache比Linux更复杂,需要更多的内存,同一个文件需要两个名字节点,也间接导致了文件系统查找名字的效率更低。
Windows文件系统内部用的是Unicode,如果用FindNextFileA的话,性能比Unicode版本还慢,因为FindNextFileA调用的是FindNextFileW并且还多了一个字符集转换。
3. Windows缓存机制不够激进
Linux系统启动以后,一般会先划分走一半的物理内存作为缓存,但Windows一直以来都没有这么激进的缓存策略,所以Linux文件系统缓存效率会比Windows要高,性能也高很多。
4. 其它原因
Windows文件系统效率低还有很多细节的原因,比如Windows内核的通知机制更长更复杂(尤其表现在删除文件性能上)。
Windows安全检查比较多并且复杂(NTFS权限
比POSIX权限要多)。
……
所以,上面的几个共同的原因导致了Windows文件系统性能比Linux低,操作文件的性能自然也就比Linux要慢(并且是慢的多了)。
最后,Windows文件系统访问慢,是代码的问题,不是NTFS的问题,有兴趣的同学可以试试在Linux下使用NTFS,你会发现Linux的NTFS比Windows的还快一些。
Windows递归枚举目录
的代码:
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <string.h>
#include <stdio.h>
int TotalFiles;
int TotalDirectories;
void SearchDirectory(LPCTSTR lpPath)
{
TCHAR FindDir[MAX_PATH] = { 0 };
TCHAR SubDir[MAX_PATH] = { 0 };
WIN32_FIND_DATA FindFileData;
BOOL bRet;
wcscpy(FindDir, lpPath);
wcscat(FindDir, L"\\*.*");
HANDLE hFind = FindFirstFile(FindDir, &FindFileData);
if (INVALID_HANDLE_VALUE == hFind)
{
return;
}
bRet = TRUE;
while (bRet == TRUE)
{
if (wcscmp(FindFileData.cFileName, L".") != 0
&& wcscmp(FindFileData.cFileName, L"..") != 0)
{
if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
TotalDirectories++;
wcscpy(SubDir, lpPath);
wcscat(SubDir, L"\\");
wcscat(SubDir, FindFileData.cFileName);
SearchDirectory(SubDir);
}
else
{
TotalFiles++;
}
}
bRet = FindNextFile(hFind, &FindFileData);
}
FindClose(hFind);
}
int main(int argc, char *argv[])
{
LARGE_INTEGER Freq, Start, End;
TCHAR Path[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, argv[1], strlen(argv[1]) + 1, Path, sizeof(Path));
QueryPerformanceFrequency(&Freq);
QueryPerformanceCounter(&Start);
SearchDirectory(Path);
QueryPerformanceCounter(&End);
printf("Total Files [%d] Total Directories [%d]\n", TotalFiles, TotalDirectories);
printf("Counter [%lld] Freq [%lld], Total [%lld] ms\n",
End.QuadPart - Start.QuadPart,
Freq.QuadPart,
(End.QuadPart - Start.QuadPart) * 1000 / Freq.QuadPart);
return 0;
}
Linux枚举子目录的代码:
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
int TotalFiles;
int TotalDirectories;
int SearchDirectory(char * path)
{
DIR * dirfd;
struct dirent * entry;
char subpath[256];
dirfd = opendir(path);
while((entry = readdir(dirfd)) != NULL)
{
strcpy(subpath, path);
strcat(subpath, "/");
strcat(subpath, entry->d_name);
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
{
if (entry->d_type == DT_DIR)
{
TotalDirectories++;
SearchDirectory(subpath);
}
else
{
TotalFiles++;
}
}
}
closedir(dirfd);
return 0;
}
int main(int argc, char * argv[])
{
time_t st, ed;
st = clock();
SearchDirectory(argv[1]);
ed = clock();
printf("Total Files [%d] Total Directories [%d]\n", TotalFiles, TotalDirectories);
printf("Counter [%ld] Freq [%ld], Total [%ld] ms\n",
ed - st,
CLOCKS_PER_SEC,
(ed - st) * 1000 / CLOCKS_PER_SEC);
return 0;
}