为啥将许多小字节数组写入文件比写入一个大数组要快?
Posted
技术标签:
【中文标题】为啥将许多小字节数组写入文件比写入一个大数组要快?【英文标题】:Why writing many small byte arrays to a file is faster than writing one big array?为什么将许多小字节数组写入文件比写入一个大数组要快? 【发布时间】:2012-07-11 05:44:28 【问题描述】:我做了一个测试,看看在磁盘上从单字节数组写入 1GB 文件所需的时间与从 1024 个数组(每个 1MB)写入另一个 1GB 文件所需的时间是否存在差异。
测试写入多个数组 331.6902 毫秒 测试写大数组 14756.7559 毫秒
对于这个测试,“许多数组”实际上是一个 byte[1024 * 1024]
数组,我使用 for 循环写了 1024 次。
“大数组”只是一个 1GB 字节的数组,里面填充了随机值。
代码如下:
Console.WriteLine("Test Writing many arrays");
byte[] data = new byte[1048576];
for (int i = 0; i < 1048576; i++)
data[i] = (byte)(i % 255);
FileStream file = new FileStream("test.txt", FileMode.Create);
sw1.Restart();
for (int i = 0; i < 1024; i++ )
file.Write(data, 0, 1048576);
file.Close();
sw1.Stop();
s1 = sw1.Elapsed;
Console.WriteLine(s1.TotalMilliseconds);
Console.WriteLine("Test Writing big array");
byte[] data2 = new byte[1073741824];
for (int i = 0; i < 1073741824; i++)
data2[i] = (byte)(i % 255);
FileStream file2 = new FileStream("test2.txt", FileMode.Create);
sw1.Restart();
file2.Write(data2, 0, 1073741824);
file2.Close();
sw1.Stop();
s1 = sw1.Elapsed;
Console.WriteLine(s1.TotalMilliseconds);
我在定时部分中包含了file.Close()
,因为它调用Flush()
方法并将流写入磁盘。
生成的文件大小完全相同。
我认为也许 C# 可以看到我总是使用相同的数组,它可能会优化迭代/写入过程,但结果不是快 2-3 倍,而是快了大约 45 倍。 .. 为什么?
【问题讨论】:
我怀疑这不是关于写入内存的部分,而是关于导致问题的临时存储(即 1gb in ram),导致硬盘驱动器从虚拟内存交换。 另一件事是缓存命中/未命中:1MB 内存的缓存命中率始终保持不变。不过,这比页面交换问题要小。 这也不是一个公平的比较,是吗?你不会关闭小数组 1000 次,只有一次。 您可能需要添加另一个测试,在其中使用 1024 次写入操作和正确的偏移量/长度写入大数组。 @C.Evenhuis 这就是我所做的;将偏移量设置为 0 将开始写入当前流所在的位置,即文件末尾,因为我没有寻找也没有关闭 FileStream。 【参考方案1】:这可能与操作系统如何处理文件写入有关。当使用单个write
调用写入 1GB 时,操作系统将不得不多次暂停写入以允许其他进程使用磁盘 I/O。而且您也没有缓冲写入。您可以通过指定更大的 bufferSize 来优化速度。
public FileStream(
SafeFileHandle handle,
FileAccess access,
int bufferSize
)
【讨论】:
【参考方案2】:原因可能是单个 1MB 数组被保存在主内存中,但 1GB 数组被换出到磁盘。
因此,当写入单个数组 1024 次时,您是从内存写入磁盘。如果目标文件是连续的,则硬盘磁头在此过程中不必移动太远。
写入 1GB 数组一次,您从磁盘读取到内存,然后写入磁盘,很可能导致每次写入至少两次 HDD 磁头移动 - 首先从交换文件中读取块,然后返回目标文件来写它。
【讨论】:
我不认为是这种情况,因为我在资源监视器中没有看到太多读取,并且因为在调试时我可以看到数组被保存在内存中并且一直没有移动。 它不会显示为“已读”,而是显示为“页面错误”,并且您不会在调试器中看到它。 VMM 的工作是使虚拟内存对程序(包括调试器)不可见。 好的,很高兴知道!但是我还有一个问题:系统是否可以交换一个变量,即使它当前正在使用或者它在程序的当前范围内? @AlexRose,虚拟内存管理器不知道变量,只知道内存,它在 4KB 页面中管理。因此,如果一个数组大于 4KB,那么它的一部分可能会被换出。只有实际被读取或写入的部分需要在物理内存中。【参考方案3】:我认为造成巨大差异的主要原因是操作系统设法缓存了您以小块执行的几乎整个 1GB 写入。
您需要更改设置基准的方式:代码应该写入相同的数据,第一次写入 1024 个块,第二次写入一个块。您还需要通过指定FileOptions.WriteThrough
来关闭操作系统中的数据缓存,如下所示:
var sw1 = new Stopwatch();
Console.WriteLine("Test Writing many arrays");
var data = new byte[1073741824];
for (var i = 0; i < 1073741824; i++)
data[i] = (byte)(i % 255);
var file = new FileStream("c:\\temp\\__test1.txt", FileMode.Create, FileSystemRights.WriteData, FileShare.None, 8, FileOptions.WriteThrough);
sw1.Restart();
for (int i = 0; i < 1024; i++)
file.Write(data, i*1024, 1048576);
file.Close();
sw1.Stop();
var s1 = sw1.Elapsed;
Console.WriteLine(s1.TotalMilliseconds);
Console.WriteLine("Test Writing big array");
var file2 = new FileStream("c:\\temp\\__test2.txt", FileMode.Create, FileSystemRights.WriteData, FileShare.None, 8, FileOptions.WriteThrough);
sw1.Restart();
file2.Write(data, 0, 1073741824);
file2.Close();
sw1.Stop();
s1 = sw1.Elapsed;
Console.WriteLine(s1.TotalMilliseconds);
运行此代码时,结果如下所示:
Test Writing many arrays
5234.5885
Test Writing big array
5032.3626
【讨论】:
以上是关于为啥将许多小字节数组写入文件比写入一个大数组要快?的主要内容,如果未能解决你的问题,请参考以下文章