.NET 4.5文件读取性能同步与异步
本文关键字:同步 异步 性能 读取 文件 NET | 更新日期: 2023-09-27 18:28:22
我们试图衡量使用同步方法和异步方法读取一系列文件之间的性能。原本预计两者之间的时间大致相同,但使用async的速度慢了5.5倍。
这可能是由于管理线程的开销,但只是想知道您的意见。也许我们只是测量时间不对。
以下是正在测试的方法:
static void ReadAllFile(string filename)
{
var content = File.ReadAllBytes(filename);
}
static async Task ReadAllFileAsync(string filename)
{
using (var file = File.OpenRead(filename))
{
using (var ms = new MemoryStream())
{
byte[] buff = new byte[file.Length];
await file.ReadAsync(buff, 0, (int)file.Length);
}
}
}
这就是运行它们并启动秒表的方法:
static void Test(string name, Func<string, Task> gettask, int count)
{
Stopwatch sw = new Stopwatch();
Task[] tasks = new Task[count];
sw.Start();
for (int i = 0; i < count; i++)
{
string filename = "file" + i + ".bin";
tasks[i] = gettask(filename);
}
Task.WaitAll(tasks);
sw.Stop();
Console.WriteLine(name + " {0} ms", sw.ElapsedMilliseconds);
}
这一切都是从这里运行的:
static void Main(string[] args)
{
int count = 10000;
for (int i = 0; i < count; i++)
{
Write("file" + i + ".bin");
}
Console.WriteLine("Testing read...!");
Test("Read Contents", (filename) => Task.Run(() => ReadAllFile(filename)), count);
Test("Read Contents Async", (filename) => ReadAllFileAsync(filename), count);
Console.ReadKey();
}
和助手编写方法:
static void Write(string filename)
{
Data obj = new Data()
{
Header = "random string size here"
};
int size = 1024 * 20; // 1024 * 256;
obj.Body = new byte[size];
for (var i = 0; i < size; i++)
{
obj.Body[i] = (byte)(i % 256);
}
Stopwatch sw = new Stopwatch();
sw.Start();
MemoryStream ms = new MemoryStream();
Serializer.Serialize(ms, obj);
ms.Position = 0;
using (var file = File.Create(filename))
{
ms.CopyToAsync(file).Wait();
}
sw.Stop();
//Console.WriteLine("Writing file {0}", sw.ElapsedMilliseconds);
}
结果:
-Read Contents 574 ms
-Read Contents Async 3160 ms
如果有人能在我们搜索堆栈和网络时对此有所了解,但却找不到合适的解释,我们将不胜感激。
测试代码有很多错误。最值得注意的是,您的"异步"测试不使用异步I/O;对于文件流,您必须显式地以异步方式打开它们,否则您只是在后台线程上执行同步操作。此外,您的文件大小非常小,可以轻松缓存。
我修改了测试代码,以写出更大的文件,使同步代码与异步代码相当,并使异步代码异步:
static void Main(string[] args)
{
Write("0.bin");
Write("1.bin");
Write("2.bin");
ReadAllFile("2.bin"); // warmup
var sw = new Stopwatch();
sw.Start();
ReadAllFile("0.bin");
ReadAllFile("1.bin");
ReadAllFile("2.bin");
sw.Stop();
Console.WriteLine("Sync: " + sw.Elapsed);
ReadAllFileAsync("2.bin").Wait(); // warmup
sw.Restart();
ReadAllFileAsync("0.bin").Wait();
ReadAllFileAsync("1.bin").Wait();
ReadAllFileAsync("2.bin").Wait();
sw.Stop();
Console.WriteLine("Async: " + sw.Elapsed);
Console.ReadKey();
}
static void ReadAllFile(string filename)
{
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false))
{
byte[] buff = new byte[file.Length];
file.Read(buff, 0, (int)file.Length);
}
}
static async Task ReadAllFileAsync(string filename)
{
using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
{
byte[] buff = new byte[file.Length];
await file.ReadAsync(buff, 0, (int)file.Length);
}
}
static void Write(string filename)
{
int size = 1024 * 1024 * 256;
var data = new byte[size];
var random = new Random();
random.NextBytes(data);
File.WriteAllBytes(filename, data);
}
在我的机器上,这个测试(在Release中内置,在调试器之外运行)产生了以下数字:
Sync: 00:00:00.4461936
Async: 00:00:00.4429566
所有I/O操作都是异步的。线程只是等待(挂起)I/O操作完成。这就是为什么当读到jeffrey richter时,他总是告诉做i/o异步,这样你的线程就不会因为等待而浪费。来自Jeffery Ricter
创建线程也不便宜。每个线程获得为用户模式保留的1mb地址空间和为内核模式保留的12kb地址空间。在此之后,操作系统必须通知系统中的所有dll新线程已经生成。破坏线程时也会发生同样的情况。还要考虑上下文切换的复杂性
在这里找到了一个很棒的SO答案