【问题标题】:.NET 4.5 file read performance sync vs async.NET 4.5 文件读取性能同步与异步
【发布时间】:2013-08-20 09:18:26
【问题描述】:

我们正在尝试衡量使用同步方法与异步读取一系列文件之间的性能。预计两者之间的时间大致相同,但结果使用异步速度慢了大约 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

如果有人能在我们搜索堆栈和网络但无法真正找到正确解释时对此有所了解,我们将不胜感激。

【问题讨论】:

  • 您的测试可能存在缺陷,因为您正在生成线程以同时进行读取。更好的测试是先测试一个东西,然后再测试另一个。
  • 另一方面,Stopwatch 上有一个漂亮的静态方法称为StartNew,它基本上是var s = new Stopwatch(); s.Start(); return s;,所以你不必这样做。 :-)
  • 我认为这个测试有缺陷。您是否测量了 ReadAllBytes 和 Read 之间的差异?这可能是第一件事,ReadAllBytes 更有效 - 也许它是一个“原子”操作?
  • 你是否在发布模式下测试这个,没有附加调试器?对调试构建进行计时,或在附加调试器的情况下计时将产生不可靠的结果。请务必在 Release 模式下编译并使用 Ctrl+F5 运行(无需调试即可运行)。
  • 为什么会有这行:using (var ms = new MemoryStream())?您似乎没有在任何地方使用ms

标签: c# file asynchronous .net-4.5


【解决方案1】:

测试代码有很多问题。最值得注意的是,您的“异步”测试不使用异步 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

【讨论】:

  • 感谢您指出这一点。我确实在我的机器上得到了大致相同的结果,这现在更有意义了。这实际上是对文件缓存的测试,我们正在设计读取一堆小文件的最佳方法。
  • 嗨,斯蒂芬,如果文件很大,比如说几百 Mbs。这段代码会被破解吗??字节[] buff = 新字节[file.Length];等待 file.ReadAsync(buff, 0, (int)file.Length);因为它试图分配一大块内存?
  • @ToanNguyen:我不确定你的意思;如果你有它的记忆,那么它就不会被“破坏”。
  • 这个测试有很大的缺陷 - FileStream.Read 和 FileStream.ReadAsync 不一定会读取您请求的字节数,对于像您这样的较大文件,很可能不是。您需要在循环中迭代读取,直到读取的总字节数与文件大小匹配。
  • 好吧,有趣的事情...尝试通过流式阅读器阅读它不是我知道的最佳解决方案,但是... string s = await sr.ReadToEndAsync();异步较慢。我已经进行了 MVC 测试,但仍然不知道为什么会这样。 stackoverflow.com/questions/52456841/…
【解决方案2】:

所有 I/O 操作都是异步的。线程只是等待(它被挂起)I/O 操作完成。这就是为什么当阅读 jeffrey Richter 时,他总是告诉做 i/o 异步,这样你的线程就不会因为等待而浪费。 来自杰弗里·里克特

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

找到了一个很棒的答案here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-10
    • 2014-08-03
    • 2015-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    相关资源
    最近更新 更多