.NET 4.5文件读取性能同步与异步

我们试图测量使用同步方法和异步读取一系列文件之间的性能。 期望在两者之间大约有相同的时间但是使用异步的结果大约慢了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 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  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

创建一个线程并不便宜。 每个线程为用户模式保留1 MB的地址空间,为内核模式保留另外12kb的地址空间。 在此之后,操作系统必须通知系统中的所有dll已经生成了一个新线程。当你销毁一个线程时就会发生这种情况。 还要考虑上下文切换的复杂性

在这里找到了很好的答案