С# 4.5 для чтения файлов с синхронизацией по сравнению с async
Мы пытаемся измерить производительность между чтением серии файлов с использованием методов синхронизации vs async. Ожидалось иметь примерно одно и то же время между ними, но получается, что с помощью 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
Пойми, если кто-нибудь сможет пролить свет на это, когда мы обыскали стек и сеть, но не можем найти правильное объяснение.
Ответы
Ответ 1
В тестовом коде много ошибок. В частности, ваш тест "асинхронный" не использует асинхронный ввод-вывод; с файловыми потоками, вы должны явно открыть их как асинхронные, иначе вы просто выполняете синхронные операции в фоновом потоке. Кроме того, размеры файлов очень малы и их можно легко кэшировать.
Я изменил тестовый код, чтобы выписать гораздо более крупные файлы, иметь сопоставимую синхронизацию с асинхронным кодом и сделать асинхронный код асинхронным:
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
Ответ 2
Все операции ввода/вывода выполняются async. Поток просто ждет (он приостанавливается) для завершения операции ввода-вывода. Вот почему, когда читал jffrey richter, он всегда говорил, чтобы делать айсин i/o, так что ваш поток не пропадает, ожидая.
от Джеффри Ричера
Также создание потока не из дешевых. Каждый поток получает 1 мб адресного пространства, зарезервированного для пользовательского режима, и еще 12 КБ для режима ядра. После этого ОС должна уведомить всю DLL в системе о том, что был создан новый поток. Это происходит, когда вы уничтожаете поток. Также подумайте о сложностях переключения контекста
Нашел отличный ответ SO здесь