Производительность записи в файл C # - PullRequest
4 голосов
/ 25 февраля 2012

Обзор моей ситуации:

Моя задача - прочитать строки из файла и переформатировать их в более полезный формат.После переформатирования ввода я должен записать его в выходной файл.

Вот пример того, что должно быть сделано.Пример строки файла:

ANO=2010;CPF=17834368168;YEARS=2010;2009;2008;2007;2006 <?xml version='1.0' encoding='ISO-8859-1'?><QUERY><RESTITUICAO><CPF>17834368168</CPF><ANO>2010</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><RESTITUICAO><CPF>17834368168</CPF><ANO>2009</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><RESTITUICAO><CPF>17834368168</CPF><ANO>2008</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><RESTITUICAO><CPF>17834368168</CPF><ANO>2007</ANO><SITUACAODECLARACAO>Sua declaração consta como Pedido de Regularização(PR), na base de dados da Secretaria da Receita Federal do Brasil</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><RESTITUICAO><CPF>17834368168</CPF><ANO>2006</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><STATUS><RESULT>TRUE</RESULT><MESSAGE></MESSAGE></STATUS></QUERY>

Этот входной файл содержит в каждой строке две важные данные: CPF - это номер документа, который я буду использовать, и файл XML (который представляет собой возвращениезапрос для документа в базе данных).

Что я должен достичь:

Каждый документ в этом old format имеет XML, содержащий запрос, возвращаемый длявсе годы (с 2006 по 2010 год).После его переформатирования каждая строка ввода преобразуется в 5 строк вывода:

CPF=17834368168;YEARS=2010; <?xml version='1.0' encoding='ISO-8859-1'?><QUERY><RESTITUICAO><CPF>17834368168</CPF><ANO>2010</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><STATUS><RESULT>TRUE</RESULT><MESSAGE></MESSAGE></STATUS></QUERY>
CPF=17834368168;YEARS=2009; <?xml version='1.0' encoding='ISO-8859-1'?><QUERY><RESTITUICAO><CPF>17834368168</CPF><ANO>2009</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><STATUS><RESULT>TRUE</RESULT><MESSAGE></MESSAGE></STATUS></QUERY>
CPF=17834368168;YEARS=2008; <?xml version='1.0' encoding='ISO-8859-1'?><QUERY><RESTITUICAO><CPF>17834368168</CPF><ANO>2008</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><STATUS><RESULT>TRUE</RESULT><MESSAGE></MESSAGE></STATUS></QUERY>
CPF=17834368168;YEARS=2007; <?xml version='1.0' encoding='ISO-8859-1'?><QUERY><RESTITUICAO><CPF>17834368168</CPF><ANO>2007</ANO><SITUACAODECLARACAO>Sua declaração consta como Pedido de Regularização(PR), na base de dados da Secretaria da Receita Federal do Brasil</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><STATUS><RESULT>TRUE</RESULT><MESSAGE></MESSAGE></STATUS></QUERY>
CPF=17834368168;YEARS=2006; <?xml version='1.0' encoding='ISO-8859-1'?><QUERY><RESTITUICAO><CPF>17834368168</CPF><ANO>2006</ANO><SITUACAODECLARACAO>Sua declaração não consta na base de dados da Receita Federal</SITUACAODECLARACAO><DATACONSULTA>05/01/2012</DATACONSULTA></RESTITUICAO><STATUS><RESULT>TRUE</RESULT><MESSAGE></MESSAGE></STATUS></QUERY>

Одна строка, содержащая каждый год информацию об этом документе.Таким образом, в основном выходные файлы * в 1023 * 5 раз длиннее входных файлов .

Производительность:

Каждый файл имеет 400 000 строк, и яесть 133 файла для обработки.

На данный момент, вот поток моего приложения:

  1. Открыть файл
  2. Читать строку
  3. Разобрать его в новомformat
  4. Записать строку в выходной файл
  5. Goto 2, пока не останется левой строки
  6. Goto1, пока не останется левого файла

Каждый входной файл занимает около 700 МБ, и на его чтение и запись преобразованной версии уходит целая вечность.Для файла размером 400 КБ требуется ~ 30 секунд.

Дополнительная информация:

Моя машина работает на процессоре Intel i5 с 8 ГБ ОЗУ.

Я не создаю тонны объекта, чтобы избежать мем.при открытии входного файла я использую условие using.

Что можно сделать, чтобы он работал быстрее?

Ответы [ 4 ]

11 голосов
/ 25 февраля 2012

Я не знаю, как выглядит ваш код, но вот пример, который на моем компьютере (правда, с SSD и i7, но ...) обрабатывает файл 400 КБ примерно за 50 мс.

Я даже не думал об оптимизации - я написал это самым чистым способом, каким мог. (Обратите внимание, что все это лениво оценивается; File.ReadLines и File.WriteAllLines позаботятся об открытии и закрытии файлов.)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;

class Test
{
    public static void Main()
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        var lines = from line in File.ReadLines("input.txt")
                    let cpf = ParseCpf(line)
                    let xml = ParseXml(line)
                    from year in ParseYears(line)
                    select cpf + year + xml;

        File.WriteAllLines("output.txt", lines);
        stopwatch.Stop();
        Console.WriteLine("Completed in {0}ms", stopwatch.ElapsedMilliseconds);
    }

    // Returns the CPF, in the form "CPF=xxxxxx;"
    static string ParseCpf(string line)
    {
        int start = line.IndexOf("CPF=");
        int end = line.IndexOf(";", start);
        // TODO: Validation
        return line.Substring(start, end + 1 - start);
    }

    // Returns a sequence of year values, in the form "YEAR=2010;"
    static IEnumerable<string> ParseYears(string line)
    {
        // First year.
        int start = line.IndexOf("YEARS=") + 6;
        int end = line.IndexOf(" ", start);
        // TODO: Validation
        string years = line.Substring(start, end - start);
        foreach (string year in years.Split(';'))
        {
            yield return "YEARS=" + year + ";";
        }
    }

    // Returns all the XML from the leading space onwards
    static string ParseXml(string line)
    {
        int start = line.IndexOf(" <?xml");
        // TODO: Validation
        return line.Substring(start);
    }
}
5 голосов
/ 25 февраля 2012

Это выглядит как хороший кандидат для конвейерной обработки .

Основная идея состоит в том, чтобы иметь 3 одновременных Задачи s, по одному для каждой "стадии" в конвейере, взаимодействующих друг с другом через очереди ( BlockingCollection ):

  1. Первая задача читает строку входного файла построчно и помещает строки чтения в очередь.
  2. Вторая задача получает строки из очереди, форматирует их и помещает результат в другую очередь.
  3. Третье задание получает отформатированные результаты из второй очереди и записывает их в результирующий файл.

В идеале задача 1 должна не ждать завершения задачи 2, прежде чем перейти к следующему файлу.

Вы даже можете прийти в бешенство и поместить конвейер каждого отдельного файла в отдельную параллельную задачу, но это может так сильно повредить голову вашего жесткого диска, что это, вероятно, повредит больше, чем поможет. С другой стороны, для твердотельных накопителей это может быть оправдано - во всяком случае, до принятия решения.

--- РЕДАКТИРОВАТЬ ---

Используя однопоточную реализацию Джона Скита в качестве базы, вот как должна выглядеть конвейерная версия (рабочий пример):

class Test {

    struct Queue2Element {
        public string CPF;
        public List<string> Years;
        public string XML;
    }

    public static void Main() {

        Stopwatch stopwatch = Stopwatch.StartNew();

        var queue1 = new BlockingCollection<string>();
        var task1 = new Task(
            () => {
                foreach (var line in File.ReadLines("input.txt"))
                    queue1.Add(line);
                queue1.CompleteAdding();
            }
        );

        var queue2 = new BlockingCollection<Queue2Element>();
        var task2 = new Task(
            () => {
                foreach (var line in queue1.GetConsumingEnumerable())
                    queue2.Add(
                        new Queue2Element {
                            CPF = ParseCpf(line),
                            XML = ParseXml(line),
                            Years = ParseYears(line).ToList()
                        }
                    );
                queue2.CompleteAdding();
            }
        );

        var task3 = new Task(
            () => {
                var lines = 
                    from element in queue2.GetConsumingEnumerable()
                    from year in element.Years
                    select element.CPF + year + element.XML;
                File.WriteAllLines("output.txt", lines);
            }
        );

        task1.Start();
        task2.Start();
        task3.Start();
        Task.WaitAll(task1, task2, task3);

        stopwatch.Stop();
        Console.WriteLine("Completed in {0}ms", stopwatch.ElapsedMilliseconds);

    }

    // Returns the CPF, in the form "CPF=xxxxxx;"
    static string ParseCpf(string line) {
        int start = line.IndexOf("CPF=");
        int end = line.IndexOf(";", start);
        // TODO: Validation
        return line.Substring(start, end + 1 - start);
    }

    // Returns a sequence of year values, in the form "YEAR=2010;"
    static IEnumerable<string> ParseYears(string line) {
        // First year.
        int start = line.IndexOf("YEARS=") + 6;
        int end = line.IndexOf(" ", start);
        // TODO: Validation
        string years = line.Substring(start, end - start);
        foreach (string year in years.Split(';')) {
            yield return "YEARS=" + year + ";";
        }
    }

    // Returns all the XML from the leading space onwards
    static string ParseXml(string line) {
        int start = line.IndexOf(" <?xml");
        // TODO: Validation
        return line.Substring(start);
    }

}

Как оказалось, параллельная версия выше лишь немного быстрее, чем последовательная версия. По-видимому, задача связана с вводом-выводом больше, чем что-либо еще, поэтому конвейерная обработка не сильно помогает. Если вы увеличите объем обработки (например, добавите надежную проверку), это может изменить ситуацию в пользу параллелизма, но сейчас вам, вероятно, лучше всего сосредоточиться на последовательных улучшениях (как отметил сам Джон Скит, код не так как можно быстрее).

(Кроме того, я протестировал с кэшированными файлами - мне интересно, есть ли способ очистить кэш файлов Windows и посмотреть, позволит ли аппаратная очередь ввода-вывода глубиной 2 позволить жесткому диску оптимизировать движения головы по сравнению с I / O глубина 1 серийной версии.)

2 голосов
/ 25 февраля 2012

Это определенно не проблема ввода-вывода - проверьте свою обработку, используйте профилировщик, чтобы узнать, кто и где хранит все временные интервалы.

Покажите ваш код обработки, возможно, вы используете неэффективные строковые операции ...

1 голос
/ 25 февраля 2012

Есть несколько основных вещей, которые вы можете сделать прямо сейчас ...

  1. Запускать несколько потоков, чтобы обрабатывать несколько файлов одновременно.
  2. Используйте вместо StringBuilder или StringBufferstring concat
  3. Если вы используете XmlDocument для анализа XML, замените его на XmlTextReader и XmlTextWriter
  4. Не преобразовывайте строки в числа и обратно в строки, если они вам действительно не нужны
  5. Удалите все ненужные строковые операции.Например, не делайте str.Contains, чтобы просто выполнить str.IndexOf на следующей строке.Вместо этого вызовите str.IndexOf, чтобы сохранить результат в локальной переменной var и проверить, если> 0.

Запустите разные части вашего алгоритма сами и измерьте время.Начните с чтения всего файла построчно и измерьте это.Запишите те же строки в новый файл и измерьте это.Разделите информацию префикса из xml и измерьте ее.Разбор XML .... Таким образом, вы будете знать, что является узким местом и сосредоточиться на этой части.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...