0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32

PLINQ: Читать файл, каждую строку преобразовывать и записывать преобразованную строку в другой файл

13.04.2015, 18:47. Показов 1538. Ответов 11
Метки нет (Все метки)

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

в общем, получилось вот так:

C#
1
2
3
4
5
6
7
8
9
/// <param name="Convertor">преобразователь строк</param>
/// <param name="Buf">строковый буфер</param>
/// <param name="Count">счетчик</param>
void DumpBuf(LineConvertor Convertor, List<string> Buf, ref int Count)
{
    Buf.AsParallel().ForAll(s => Convertor.Output(s));
    Buf.Clear();
    Count = 0;
}
замечательно все разливается по процессорным ядрам, но, конечно же, я столкнулся с тем, что порядок вывода строк в файл не соблюдается. еще немножко почитав умный хелп, я добавил метод AsOrdered():

C#
1
2
3
4
5
6
void DumpBuf(LineConvertor Convertor, List<string> Buf, ref int Count)
{
    Buf.AsParallel().AsOrdered().ForAll(s => Convertor.Output(s));
    Buf.Clear();
    Count = 0;
}
но это мало помогло. файл вот такого содержания

C#
1
2
3
4
5
6
7
8
9
10
00000001
00000002
00000003
00000004
00000005
00000006
00000007
00000008
00000009
00000010
при размере буфера 5 выдает беспорядочное следование строк в пределах буфера

C#
1
2
3
4
5
6
7
8
9
10
00000001
00000002
00000003
00000005
00000004
00000009
00000008
00000006
00000010
00000007
либо я не так понял назначение метода AsOrdered(), либо это ForAll() бермутит воду во пруду - т.е. делает все-равно все по-своему.
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
13.04.2015, 18:47
Ответы с готовыми решениями:

Скопировать файл логов в другой файл, добавив в каждую строку определенный префикс в начале
Есть задача: скопировать файл логов в другой файл, добавив в каждую строку определенный префикс в начале. Проблема в том, что исходный файл...

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

Как скопировать строку c одного файла и вставить в другой файл эту же строку с символом в конце “_”
Подскажите, как можно через cmd скопировать строку с одного файла 1.txt и вставить в другой 2.txt, эту же строку и ниже в столбик ее только...

11
Master of Orion
Эксперт .NET
 Аватар для Psilon
6102 / 4958 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
13.04.2015, 18:51
blackofe, файлы большие?
0
0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32
13.04.2015, 18:55  [ТС]
ну, вообще-то, да. около 2 гиг. в результате преобразований получаются файлы 5 гиг. кол-во строк - 7-8 млн.

Добавлено через 1 минуту
просто я попробовал распараллелить, и у меня получилось время обработки такого файла сократить вдвое - 22 минуты против 45. размер буфера я взял 16 (при 24-ядерной конфигурации хоста). т.е. результат обнадеживающий. но возникло дополнительное требование клиента: порядок строк должен сохраняться. вот и ломаю голову..
0
Master of Orion
Эксперт .NET
 Аватар для Psilon
6102 / 4958 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
13.04.2015, 19:11
Держите класс:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public static class ArrayPerformanceHelper
    {
        public static TResult[] PartitionedParallelMap<T, TResult>(this T[] array, Func<T, TResult> map)
        {
            var result = new TResult[array.Length];
            Parallel.ForEach(Partitioner.Create(0, array.Length),
                             range =>
                             {
                                 for (int i = range.Item1; i < range.Item2; ++i)
                                     result[i] = map(array[i]);
                             });
            return result;
        }
 
        public static void PartitionedParallelMapDirty<T>(this T[] array, Func<T, T> map)
        {
            Parallel.ForEach(Partitioner.Create(0, array.Length),
                             range =>
                             {
                                 for (int i = range.Item1; i < range.Item2; ++i)
                                     array[i] = map(array[i]);
                             });
        }
    }
на вход даете ему кусок буффера, на выходе получаете смаппленный в том же порядке. Производительность должна возрасти (имеется ввиду относительно AsParallel), порядок сохранится.

Буффер лучше читать большими кусками, по 128мб хотя бы. Причем считали, отдали на обработку, а сами подгружаем дальше, то есть не блокирующе ждем, пока там нам обработают, а FireAndForget. То есть один поток читает и кормит данными метод, метод высчитывает всё, после чего уже третий поток записывает всё в результирующий файл. Конвеерная обработка такая.

Первый метод возвращает новый массив, второй метод "грязный" - портит массив, который ему передали, зато работает быстрее, а главное требует в 2 раза меньше памяти.

Добавлено через 7 минут
Ну а что касается порядка, нужно читать спеку, тогда не будете удивляться:
https://msdn.microsoft.com/ru-... 10%29.aspx

Operator ForAll<TSource>
Result when the source sequence is ordered Executes nondeterministically in parallel
Result when the source sequence is unordered Executes nondeterministically in parallel
1
0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32
13.04.2015, 20:04  [ТС]
написал ответ, но он куда-то подевался. попробую еще раз.

спасибо за ответ. с кодом буду разбираться.

два момента.

первый: под буфером (в контексте моей задачи) я подразумевал список строк. через AsParallel().ForAll() я хотел добиться одновременного (по возможности) выполнения операций над всеми (также по возможности) строками. поэтому нет смысла делать буфер слишком большим – не больше числа ядер в системе. и даже меньше. по наблюдениям задача работает быстрее с размером буфера 16 на 24-ядерном хосте, чем с размером буфера 24.

второй момент: наверное, я чуток подразобрался с AsOrdered() и изменил код так:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// <param name="Convertor">преобразователь строки</param>
/// <param name="Writer">выходной поток</param>
/// <param name="Buf">строковый буфер</param>
/// <param name="Count">счетчик</param>
void DumpBuf(LineConvertor Convertor, TextWriter Writer, List<string> Buf, ref int Count)
{
    var outbuf = Buf.AsParallel()
                .AsOrdered()
                .Select<string, string>(s => Convertor.OutputStr(s));
    foreach(var v in outbuf)
        Writer.WriteLine(v);
    Buf.Clear();
    Count = 0;
}
результат правильный: строки выводятся в первоначальном порядке, потому что Select работает как раз как надо, а потом уже foreach поочередно выводит строки в файл. сейчас хочу посмотреть, насколько ухудшится при этом скорость работы.

Добавлено через 4 минуты
только что проверил. падение производительности не катастрофическое - 23 минуты против 22. можно жить!

Code
1
2
3
4
5
6
7
8
9
10
11
before:
 
Convert() #start
Convert() #end | 00:22:35.7822392
---[ 00:22:36.1887821 ]-------------------------
 
after:
 
Convert() #start
Convert() #end | 00:23:32.7713049
---[ 00:23:33.2773759 ]-------------------------
0
Master of Orion
Эксперт .NET
 Аватар для Psilon
6102 / 4958 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
13.04.2015, 20:46
blackofe, еще раз: читаете массив строк, используете PartitionedParallelMapDirty, записываете получившийся строковый массив в файл, всё! Это в разы быстрее AsParallel...

Хотя конечно, если вы фанат LINQ и вам важен сам факт его использования, то AsParallel без конкурентов.

Добавлено через 3 минуты
Неужели вам не любопытно? Возьмите размер буфера 65536 элементов, попробуйте на нем метод, который я предложил выше, и выложите тест производительности.
1
0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32
14.04.2015, 16:18  [ТС]
попробую обязательно. просто хотелось и самому разобраться.

Добавлено через 16 часов 59 минут
попробовал использовать предложенный метод ArrayPerformanceHelper.PartitionedParall elMap (до dirty-версии пока не добрался). на небольших файлах (порядка 250 тыс. строк) он показал вполне ощутимый прирост производительности. файл, который AsParallel разбирал за 45 секунд, PartitionedParallelMap обработал за 35. но когда дело дошло до реальных файлов, он пасонул. на сервере с 24 ядрами и 48 гигами оперативки я получил следующие результаты:

AsParallel() без сохранения порядка:
Code
1
2
3
Convert() #start
Convert() #end | 00:22:35.7822392
---[ 00:22:36.1887821 ]-------------------------
AsParallel() c сохранением порядка:
Code
1
2
3
Convert() #start
Convert() #end | 00:23:32.7713049
---[ 00:23:33.2773759 ]-------------------------
с использованием ArrayPerformanceHelper (размер буфера 65536 строк. при средней длине строки 264 байта получаем примерно 17 с лишком мегабайт):
Code
1
2
3
4
Convert() #start
BUF SIZE: 65536
Convert() #end | 00:34:34.8510253
---[ 00:34:35.3678691 ]-------------------------
последний вариант оказывается раза в полтора медленнее. так что я остановлюсь на варианте AsParallel().AsOrdered().Select().

но за пример спасибо. возможно, в других случаях пригодится.
0
Master of Orion
Эксперт .NET
 Аватар для Psilon
6102 / 4958 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
14.04.2015, 16:41
blackofe, во-первых стоит использовать Dirty, т.к. вам не нужно оригинальный массив сохранять в целости, во-вторых можно поиграть с размером буфера - больше или меньше.
0
0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32
14.04.2015, 16:50  [ТС]
я как раз собираюсь попробовать "грязный" метод. буфер делал меньше, но на небольших файлах. в общем, в процессе.
0
0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32
15.04.2015, 20:42  [ТС]
докладываю. протестировал "грязный" метод. производительность показывает лучше, чем "чистый":

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Convert() #start
BUF SIZE: 2048
Convert() #end | 00:35:57.0407880
---[ 00:35:57.4490902 ]-------------------------
 
Convert() #start
BUF SIZE: 8192
Convert() #end | 00:25:20.3594743
---[ 00:25:20.7802348 ]-------------------------
 
Convert() #start
BUF SIZE: 4096
Convert() #end | 00:28:20.2104483
---[ 00:28:20.6245674 ]-------------------------
 
Convert() #start
BUF SIZE: 16384
Convert() #end | 00:25:36.5024515
---[ 00:25:36.9179491 ]-------------------------
 
Convert() #start
BUF SIZE: 32768
Convert() #end | 00:27:28.7140280
---[ 00:27:29.1233523 ]-------------------------
наилучший результат я получил при размере буффера 4-8 тыс. строк. но, однако же результат все-равно уступает варианту с упорядоченным AsParallel():

Code
1
2
3
4
Convert() #start
pool size = 16
Convert() #end | 00:23:28.7707673
---[ 00:23:29.2153615 ]-------------------------
0
Master of Orion
Эксперт .NET
 Аватар для Psilon
6102 / 4958 / 905
Регистрация: 10.07.2011
Сообщений: 14,522
Записей в блоге: 5
15.04.2015, 20:52
blackofe, ну, это очень странно, AsParallel по идее не может быть быстрее, т.к. базируется на тех же примитивах, только еще и больше ресурсов тратит, было даже целое исследование на эту тему. ИМХО как-то что-то не так делаете
0
0 / 0 / 0
Регистрация: 30.05.2014
Сообщений: 32
15.04.2015, 22:36  [ТС]
возможно не так. к примеру, в реализации с AsParallel я реюзаю буфер. а в случае с ArrayPerformanceHelper я каждый раз набиваю буфер заново, причем каждую строку снабжаю своим конвертором, чтобы избежать "наводок". так что сравнение нечестное. по хорошему, надо было бы во-первых, сделать конвертор статическим (по идее это можно было бы сделать), а во-вторых, реюзать буфер. тогда, наверняка ArrayPerformanceHelper показал бы результат лучше, чем AsParallel.

если дойдут руки, попробую это реализовать.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
15.04.2015, 22:36
Помогаю со студенческими работами здесь

Как записывать в файл на новую строку?
Помогите пожалуйста.. Как записывать в файл на новую строку (запись производится в конец предыдущей строки).. Программа состряпана до жути...

Вывести строку, содержащую эти же слова, но расположенные в обратном порядке. Записать полученную строку в другой файл
Доброго времени суток! Крайне необходима помощь экспертов в С++! Кто может - не оставьте меня в беде))) Вот задания: ...

Не работает код (программа считывает из файла строку, убирает лишние пробелы и записывает в другой файл строку, словами наоборот)
#include &quot;stdafx.h&quot; #include &lt;iostream&gt; #include &lt;fstream&gt; #include &lt;string&gt; #include &lt;algorithm&gt; using namespace std; string...

Как читать строку из .ini файла?
Привет вмем! У меня такая прблема, пишу в TIniFile следушее TIniFile *ini = new TIniFile(L&quot;C:\\test.ini&quot;); ...

Дан файл. Удалить из него третью строку. Результат записать в другой файл
Дан файл. Удалить из него третью строку. Результат записать в другой файл. Формат входных данных В файле записано несколько...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
12
Ответ Создать тему
Опции темы

Новые блоги и статьи
Символьное дифференцирование
igorrr37 13.02.2026
/ * Логарифм записывается как: (x-2)log(x^2+2) - означает логарифм (x^2+2) по основанию (x-2). Унарный минус обозначается как ! */ #include <iostream> #include <stack> #include <cctype>. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL3_image
8Observer8 10.02.2026
Содержание блога Библиотека SDL3_image содержит инструменты для расширенной работы с изображениями. Пошагово создадим проект для загрузки изображения формата PNG с альфа-каналом (с прозрачным. . .
Установка Qt-версии Lazarus IDE в Debian Trixie Xfce
volvo 10.02.2026
В общем, достали меня глюки IDE Лазаруса, собранной с использованием набора виджетов Gtk2 (конкретно: если набирать текст в редакторе и вызвать подсказку через Ctrl+Space, то после закрытия окошка. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru