С# очень медленный StreamReader

Я использую какой-то неоптимальный код, написанный мной... :-|

У меня есть следующий код:

string fmtLine = "";
            string[] splitedFmtLine;
            int counterFMTlines = 0;

            foreach (string fmtF in fmtFiles)
            {
                using (StreamReader sr = new StreamReader(fmtF))
                {
                    while ((fmtLine = sr.ReadLine()) != null)
                    {
                        Console.WriteLine(counterFMTlines++);
                        foreach (L3Message message in rez)
                        {
                            splitedFmtLine = Regex.Split(fmtLine, "\t");

                            if (message.Time == splitedFmtLine[0])
                            {
                                message.ScramblingCode = splitedFmtLine[7];      
                            }
                        }
                    }
                }
            }

Я тестировал этот код, когда список был пуст и был только один файл (с разделителями табуляции, 280000 строк), и даже тогда требовалось время жизни (1 минута), чтобы просмотреть все 280000 строк моего файла. Это означает, что выполнение пропустило цикл foreach, где находится мой список myObjs.

Не могу понять, почему так долго?

Например, я заполнял свой список myObjs (древовидная иерархия) другим текстовым файлом (исходный файл), но больше, чем этот разделитель табуляции (разделитель табуляции: 16 МБ, исходный файл: 36 МБ), и это заняло всего секунду по сравнению с этой 1 минутой.


person JohnDoeKazama    schedule 31.03.2012    source источник


Ответы (2)


Помимо проблемы с записью в консоль, у вас также есть O(m*n) время выполнения, где n — количество строк в файле, а m — количество сообщений. Это плохо, если m или n большие. Вы можете сократить это до операции O(n), используя вместо этого Dictionary и исключая внутренний цикл.

Вы можете поместить свои сообщения в Dictionary, используя время в качестве ключа. В цикле вам нужно только запросить словарь для сообщений в определенное время:

        string fmtLine = "";
        string[] splitedFmtLine;
        int counterFMTlines = 0;

        var messageTimes = new Dictionary<string, LinkedList<L3Message>>();
        foreach (L3Message message in rez)
        {
            LinkedList<L3Message> list=null;
            messageTimes.TryGetValue(message.Time, out list);

            list = list ?? new LinkedList<L3Message>();

            list.AddLast(message);
            messageTimes[message.Time] = list;
        }

        foreach (string fmtF in fmtFiles)
        {
            using (StreamReader sr = new StreamReader(fmtF))
            {
                while ((fmtLine = sr.ReadLine()) != null)
                {
                    //Console.WriteLine(counterFMTlines++);
                    splitedFmtLine = fmtLine.Split('\t');

                    LinkedList<L3Message> messageList = null;
                    messageTimes.TryGetValue(splitedFmtLine[0], out messageList);

                    if(messageList != null)
                    {
                        foreach (var message in messageList)
                        {
                            message.ScramblingCode = splitedFmtLine[7];                                
                        }
                        messageTimes.Remove(splitedFmtLine[0]); //see comments
                    }

                    if(messageTimes.Count==0) break; //see comments
                }
            }
            if(messageTimes.Count==0) break; //see comments
        } 

Это должно быть супер быстро.

Редактировать. Я изменил пример, чтобы он поддерживал случаи, когда существует более одного сообщения за один раз.

Edit2: я добавил оптимизацию, основанную на том факте, что время сообщения и ScramblingCode всегда коррелируют (см. комментарии).

person aKzenT    schedule 31.03.2012
comment
Это выглядит великолепно... Я проверю, когда мой профайлер VS будет готов. - person JohnDoeKazama; 31.03.2012
comment
Спасибо за редактирование... Вот уловка, в FMTfile есть несколько строк с одинаковым временем, но мне нужна только одна (для того же времени ScramblingCode одинаков). Считаете ли вы, что здесь есть какая-то возможная оптимизация? - person JohnDoeKazama; 31.03.2012
comment
Может ли быть более одного сообщения с одинаковым временем, где вы должны установить код скремблирования? - person aKzenT; 31.03.2012
comment
Вы можете добавить messageTimes.Remove(splitedFmtLine[0]) внутри оператора if. Таким образом, вы не найдете никаких сообщений при следующем чтении строки с уже обновленным временем. В конце foreach вы можете проверить, пуст ли messageTimes, и в этом случае полностью выйти из цикла (поскольку все сообщения уже обновлены). - person aKzenT; 31.03.2012
comment
Да! Может быть больше сообщений за одно и то же время, когда я заполняю свой список‹L3Message›, в зависимости от того, сколько параметров находится в исходном файле, если в исходном файле (текстовый файл с строками, ориентированными на древовидную иерархию) есть один из параметров, чем я создал объект L3Message с этот параметр и все его подпараметры (они находятся в древовидной иерархии в файле)... вот проблема, которую я решил (вывод с разделителями табуляции в этом квесте. Это не мой fmtFile, здесь FMTfile - второй входной файл, создающий мой объект L3Message) stackoverflow.com/questions/9887506/< /а> - person JohnDoeKazama; 31.03.2012
comment
Хорошо, тогда смотрите мое предложение выше, чтобы оптимизировать это больше. также вы должны принять ответ в сообщении, на которое вы ссылаетесь, а также этот ответ, если он вам помог. Таким образом, вы не только получаете очки (репутацию) на этой странице, но и люди с большей готовностью помогут вам в будущем. - person aKzenT; 31.03.2012
comment
Я даю вам согласие на ответ из-за кода. . . И этот парень в первом ответе тоже хорошо замечает. Мне нужно получить эту возможность голосования :-) - person JohnDoeKazama; 31.03.2012
comment
Возникла проблема... Выходной файл с разделителями табуляции с последним редактированием отличается от файла с предыдущим редактированием. Я пытаюсь увидеть, что происходит... - person JohnDoeKazama; 31.03.2012

Вы пишете 280 000 раз в консоль, что очень медленно. Удалите вывод консоли. Кроме того, используйте string.Split('\t'), который намного быстрее, чем этот конкретный вызов регулярного выражения.

person usr    schedule 31.03.2012
comment
Как сказал @usr, избавьтесь от console.write в цикле, переместите его в первый цикл с файлами, если вы хотите иметь какую-то информацию во время выполнения. - person cichy; 31.03.2012
comment
спасибо за информацию, я знаю, что выход из консоли влияет на производительность, но я думал, что выражения Regex более оптимальны, чем строковые методы... - person JohnDoeKazama; 31.03.2012
comment
Нет, методы Regex имеют нетривиальные накладные расходы. Я никогда не видел случая, когда ручная операция со строкой не могла быть выполнена быстрее, чем регулярное выражение. Регулярные выражения - это удобные функции. - person usr; 31.03.2012
comment
Ручные операции со строками, как правило, требуют больше памяти, потому что вы создаете много строк, которые вам не нужны (при условии, что вы выполняете несколько операций со строками). - person aKzenT; 31.03.2012
comment
@aKzenT этого всегда можно избежать. В крайнем случае вы перекодируете конечный автомат регулярного выражения. Если производительность важна, вы всегда можете получить результат с помощью регулярных выражений, а в большинстве случаев и лучше. Регулярное выражение - это просто метод, созданный во время выполнения, который вы можете воспроизвести самостоятельно. Вы даже можете сохранить скомпилированное регулярное выражение в сборку на диске. - person usr; 31.03.2012
comment
@usr, конечно, но опять же, вы можете кодировать непосредственно на ассемблере, получая еще большую производительность ;-) в конце концов, это всегда компромисс между производительностью и сложностью решения. Во многих случаях Regex обеспечивает приемлемую производительность и снижает сложность кода. - person aKzenT; 31.03.2012