【问题标题】:.NET Native incredibly slower than Debug build with ReadAsync calls.NET Native 比使用 ReadAsync 调用的 Debug 构建慢得多
【发布时间】:2016-09-01 08:26:36
【问题描述】:

所以我刚刚在我的应用程序中发现了一个非常奇怪的问题,事实证明这是由于某种原因由 .NET Native 编译器引起的。

我有一个方法可以比较两个文件的内容,而且效果很好。对于两个 400KB 的文件,在我的 Lumia 930 上以调试模式运行大约需要 0.4 秒但是,在发布模式下,最多需要 17 秒,原因不明。代码如下:

// Compares the content of the two streams
private static async Task<bool> ContentEquals(ulong size, [NotNull] Stream fileStream, [NotNull] Stream testStream)
{
    // Initialization
    const int bytes = 8;
    int iterations = (int)Math.Ceiling((double)size / bytes);
    byte[] one = new byte[bytes];
    byte[] two = new byte[bytes];

    // Read all the bytes and compare them 8 at a time
    for (int i = 0; i < iterations; i++)
    {
        await fileStream.ReadAsync(one, 0, bytes);
        await testStream.ReadAsync(two, 0, bytes);
        if (BitConverter.ToUInt64(one, 0) != BitConverter.ToUInt64(two, 0)) return false;
    }
    return true;
}

/// <summary>
/// Checks if the content of two files is the same
/// </summary>
/// <param name="file">The source file</param>
/// <param name="test">The file to test</param>
public static async Task<bool> ContentEquals([NotNull] this StorageFile file, [NotNull] StorageFile test)
{
    // If the two files have a different size, just stop here
    ulong size = await file.GetFileSizeAsync();
    if (size != await test.GetFileSizeAsync()) return false;

    // Open the two files to read them
    try
    {
        // Direct streams
        using (Stream fileStream = await file.OpenStreamForReadAsync())
        using (Stream testStream = await test.OpenStreamForReadAsync())
        {
            return await ContentEquals(size, fileStream, testStream);
        }
    }
    catch (UnauthorizedAccessException)
    {
        // Copy streams
        StorageFile fileCopy = await file.CreateCopyAsync(ApplicationData.Current.TemporaryFolder);
        StorageFile testCopy = await file.CreateCopyAsync(ApplicationData.Current.TemporaryFolder);
        using (Stream fileStream = await fileCopy.OpenStreamForReadAsync())
        using (Stream testStream = await testCopy.OpenStreamForReadAsync())
        {
            // Compare the files
            bool result = await ContentEquals(size, fileStream, testStream);

            // Delete the temp files at the end of the operation
            Task.Run(() =>
            {
                fileCopy.DeleteAsync(StorageDeleteOption.PermanentDelete).Forget();
                testCopy.DeleteAsync(StorageDeleteOption.PermanentDelete).Forget();
            }).Forget();
            return result;
        }
    }
}

现在,我完全不知道为什么在使用 .NET Native 工具链进行编译时,为什么同样的方法会从 0.4 秒一直到超过 15 秒。

我使用单个 ReadAsync 调用来读取整个文件来修复此问题,然后我从结果中生成了两个 MD5 哈希值并比较了两者。即使在发布模式下,这种方法在我的 Lumia 930 上也能在大约 0.4 秒内生效。

不过,我对这个问题很好奇,我想知道为什么会这样。

提前感谢您的帮助!

编辑:所以我调整了我的方法以减少实际 IO 操作的数量,这就是结果,看起来它到目前为止工作正常。

private static async Task<bool> ContentEquals(ulong size, [NotNull] Stream fileStream, [NotNull] Stream testStream)
{
    // Initialization
    const int bytes = 102400;
    int iterations = (int)Math.Ceiling((double)size / bytes);
    byte[] first = new byte[bytes], second = new byte[bytes];

    // Read all the bytes and compare them 8 at a time
    for (int i = 0; i < iterations; i++)
    {
        // Read the next data chunk
        int[] counts = await Task.WhenAll(fileStream.ReadAsync(first, 0, bytes), testStream.ReadAsync(second, 0, bytes));
        if (counts[0] != counts[1]) return false;
        int target = counts[0];

        // Compare the first bytes 8 at a time
        int j;
        for (j = 0; j < target; j += 8)
        {
            if (BitConverter.ToUInt64(first, j) != BitConverter.ToUInt64(second, j)) return false;
        }

        // Compare the bytes in the last chunk if necessary
        while (j < target)
        {
            if (first[j] != second[j]) return false;
            j++;
        }
    }
    return true;
}

【问题讨论】:

  • 我不知道您是否也遇到了同样的问题,但请尝试this answer 中的代码,看看它是否有所作为。

标签: c# xaml win-universal-app windows-10 windows-10-universal


【解决方案1】:

一次从 I/O 设备读取 8 个字节是性能灾难。这就是我们首先使用缓冲读取(和写入)的原因。提交、处理、执行并最终返回 I/O 请求需要时间。

OpenStreamForReadAsync 似乎没有使用缓冲流。因此,您的 8 字节请求实际上一次请求 8 个字节。即使使用固态驱动器,这也很慢。

不过,您不需要一次读取整个文件。通常的做法是找一个合理的缓冲区大小进行预读;一次读取 1 kiB 之类的东西应该可以解决您的整个问题,而无需您一次将整个文件加载到内存中。您可以在文件和阅读之间使用BufferedStream 来为您处理此问题。如果你喜欢冒险,你可以在 CPU 处理完成之前发出下一个读取请求——尽管这很可能对你的性能没有太大帮助,因为有多少工作只是 I/O。

对于异步 I/O,.NET 原生的开销似乎比托管的 .NET 大得多,这会使那些微小的异步调用更加成问题。减少对大数据的请求会有所帮助。

【讨论】:

  • 感谢您的解释!我尝试使用 AsStreamForRead 方法来获取缓冲流,并尝试使用不同的缓冲区大小(1KB、2KBs、4KBs),但它总是需要大约 10-15 秒才能运行,而且我在这里使用 500KBs 文件。这怎么可能,缓冲流不应该解决这个问题吗?
  • @Sergio0694 这可能是 .NET 本机在异步调用上性能不佳的部分。对真正的 I/O 操作来说没什么大不了的,但在使用缓冲时它会增加。您可能必须使用自己的byte[] 缓冲区并对其进行迭代,仅使用ReadAsync 来获取另一批。幸运的是,这很容易做到,因为BitConverter 接受到byte[] 参数的偏移量:)
  • 我已经用更新的方法编辑了我的问题,看起来修复了它,让我知道你的想法!
  • @Sergio0694 对我来说看起来不错。虽然我希望你不需要那么大的缓冲区:P
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多