C# ускорить запись в файл
Доброго времени суток!
У меня есть такое задание:
Разработайте ПО, которая будет разбивать числа от 1 до 1 000 000 000 на группы, при этом числа в каждой отдельно взятой группе не делятся друг на друга...
Эти группы надо записать в файл. В общем - все хорошо до 100 млн. Записывается быстро(ну, или я так думаю) - 20 сек на разделение на группы, 20 сек на запись, 800 Мб веса.
Но если поставить хотя бы 200 млн - то все, ошибка переполнения памяти.
Из того, что вычитал и понял - BufferedStream ускоряет процесс.
Последний вариант к которому пришел:
private static void FullCalculate(long numN, string filePath)
{
string newFilepath = filePath.Insert(filePath.IndexOf('.'), "_inGroup");
if (File.Exists(newFilepath))
{
File.Delete(newFilepath);
}
StringBuilder nums = new StringBuilder("");
int count = 2;
Stopwatch st = new Stopwatch();
Stopwatch st2 = new Stopwatch();
st.Start();
**using (StreamWriter sw = new StreamWriter(new BufferedStream(File.OpenWrite(newFilepath), 2 * 1024 * 1024)))**
{
nums.Append($"Группа {count - 1}: {count - 1}.\r");//Не стал мучаться с выводом 1 группы
// в цикле, потому что она статична - "Группа 1: 1."
//nums.AppendLine();// Убрал AppendLine в надежде, что это повлияет на процесс
nums.Append($"Группа {count}: ");
for (int i = 2; i < numN + 1; i++)
{
if (i == Math.Pow(2, count))
{
nums.Append(".\r");
//nums.AppendLine();
nums.Append($"Группа {count}: ");
count++;
}
nums.Append(i + " ");
}
nums.Append('.');
st.Stop();
st2.Start();
sw.Write(nums); //запись в файл разбитых по группам чисел
st2.Stop();
}
st.Stop();
Я пробовал сначала вот так:
using (StreamWriter sw = new StreamWriter(Filepath))
По скорости выполнения и ошибкам, так же как и в последнем варианте.
Вопрос, собственно: Можно ли что то еще изменить, чтобы, если не повысить скорость, то хотя бы избавиться от ошибок???
По поводу переполнения памяти примерно понимаю - нужно записать StringBuilder в файл и продолжить дальше разделять на группы. И тогда(по идее) все запишется. Это верное направление или есть еще какие-нибудь варианты?
P.S.: Условия такие, что массивы использовать нельзя, делать в одном цикле.
Само разбиение на группы должно иметь примерно такой вывод(N =8):
Группа 1: 1.
Группа 2: 2 3.
Группа 3: 4 5 6 7.
Логика - разбивать по степеням 2-ки(вдруг нужно)
Ответы (1 шт):
Если решать в лоб, то получится как-то так, но вы делаете слишком много лишних вычислений. И генерируете просто огромного размера строки в памяти, это медленно.
private static void FullCalculate(long numN, string filePath)
{
string newFilepath = filePath.Insert(filePath.IndexOf('.'), "_inGroup");
if (File.Exists(newFilepath))
{
File.Delete(newFilepath);
}
int count = 2;
using (StreamWriter sw = new StreamWriter(new BufferedStream(File.OpenWrite(newFilepath), 2 * 1024 * 1024)))
{
sw.WriteLine($"Группа {count - 1}: {count - 1}.");//Не стал мучаться с выводом 1 группы
sw.Write($"Группа {count}: ");
for (int i = 2; i < numN + 1; i++)
{
if (i == Math.Pow(2, count))
{
sw.WriteLine(".");
sw.Write($"Группа {count}: ");
count++;
}
sw.Write(i + " ");
}
sw.WriteLine(".");
}
}
Я бы решал как-то так.
private static void FullCalculate(long numN, string filePath)
{
string newFilepath = filePath.Insert(filePath.IndexOf('.'), "_inGroup");
using (StreamWriter sw = new StreamWriter(new FileStream(newFilepath, FileMode.Create, FileAccess.Write, FileShare.None, 1024 * 1024, false)))
{
int count = 1;
long threshold = 1;
for (long i = 1; i <= numN; i++)
{
if (i == threshold)
{
sw.Write($"Группа {count++}: ");
threshold *= 2;
}
sw.Write(i.ToString());
if (i == threshold - 1)
sw.WriteLine(".");
else
sw.Write(" ");
}
}
}
И вот когда ясно, что память используется эффективно, уже можно начинать играться с буферизацией.
Кстати, FileStream имеет свой внутренний буфер. В дополнительном создании BufferedStream нет необходимости.
Еще можно распараллелить генерацию данных и запись на диск на 2 потока. Есть такой шаблон многопоточного проектирования - Producer/Consumer. Это когда один поток генерирует данные, а второй потребляет, и они друг друга не ждут. Самый простой представитель реализации этого шаблона - BlockingCollection
private static void FullCalculate(long numN, string filePath)
{
string newFilepath = filePath.Insert(filePath.IndexOf('.'), "_inGroup");
BlockingCollection<string> items = new BlockingCollection<string>();
Task task = Task.Run(() => WriterLoop(newFilepath, items));
int count = 1;
long threshold = 1;
for (long i = 1; i <= numN; i++)
{
if (i == threshold)
{
items.Add($"Группа {count++}: ");
threshold *= 2;
}
items.Add(i.ToString());
if (i == threshold - 1)
items.Add($".{Environment.NewLine}");
else
items.Add(" ");
}
items.CompleteAdding();
// здесь всё сгенерировано
task.Wait();
// здесь всё записано на диск
}
private static void WriterLoop(string path, BlockingCollection<string> items)
{
using (StreamWriter sw = new StreamWriter(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 1024 * 1024, false)))
{
foreach (string item in items.GetConsumingEnumerable())
sw.Write(item);
}
}