.NET Native 比使用 ReadAsync 调用的调试构建慢得多
.NET Native incredibly slower than Debug build with ReadAsync calls
所以我刚刚在我的应用程序中发现了一个非常奇怪的问题,结果证明它是由 .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;
}
一次从 I/O 设备读取八个字节是性能灾难。这就是我们首先使用缓冲读取(和写入)的原因。 I/O 个请求从提交、处理、执行到最终返回都需要时间。
OpenStreamForReadAsync
似乎没有使用缓冲流。所以你的 8 字节请求实际上是一次请求 8 个字节。即使使用固态硬盘,这也非常慢。
不过,您不需要一次阅读整个文件。通常的做法是找一个合理的缓冲区大小进行预读;一次读取 1 kiB 之类的东西应该可以解决您的整个问题,而无需您立即将整个文件加载到内存中。您可以在文件和阅读之间使用 BufferedStream
来为您处理这个问题。如果你喜欢冒险,你可以在 CPU 处理完成之前发出下一个读取请求 - 尽管这很可能不会对你的性能有多大帮助,因为有多少工作只是I/O.
对于异步 I/O,.NET 本机似乎比托管 .NET 的开销要大得多,这会使那些微小的异步调用成为一个更大的问题。减少对较大数据的请求会有所帮助。
所以我刚刚在我的应用程序中发现了一个非常奇怪的问题,结果证明它是由 .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;
}
一次从 I/O 设备读取八个字节是性能灾难。这就是我们首先使用缓冲读取(和写入)的原因。 I/O 个请求从提交、处理、执行到最终返回都需要时间。
OpenStreamForReadAsync
似乎没有使用缓冲流。所以你的 8 字节请求实际上是一次请求 8 个字节。即使使用固态硬盘,这也非常慢。
不过,您不需要一次阅读整个文件。通常的做法是找一个合理的缓冲区大小进行预读;一次读取 1 kiB 之类的东西应该可以解决您的整个问题,而无需您立即将整个文件加载到内存中。您可以在文件和阅读之间使用 BufferedStream
来为您处理这个问题。如果你喜欢冒险,你可以在 CPU 处理完成之前发出下一个读取请求 - 尽管这很可能不会对你的性能有多大帮助,因为有多少工作只是I/O.
对于异步 I/O,.NET 本机似乎比托管 .NET 的开销要大得多,这会使那些微小的异步调用成为一个更大的问题。减少对较大数据的请求会有所帮助。