Разбор многоядерного текстового файла

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

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

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

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

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

Ответов (7)

Решение

Я бы согласился с твоей первоначальной идеей. Если вас беспокоит, что очередь может стать слишком большой, создайте для нее буферную зону (например, если она превышает 100 строк, прекратите чтение файла, а если она станет меньше 20, то начните читать снова. Вам нужно будет провести некоторое тестирование найти оптимальные барьеры). Сделайте так, чтобы любой из потоков потенциально мог быть «потоком чтения», поскольку он должен блокировать очередь, чтобы вытащить элемент, в любом случае он также может проверить, была ли достигнута «область нижнего буфера», и начать чтение снова. При этом другие потоки могут считывать остальную часть очереди.

Или, если хотите, пусть один поток чтения назначит строки трем другим потокам процессора (через их собственные очереди) и реализует стратегию кражи работы . Я никогда этого не делал, поэтому не знаю, насколько это сложно.

Ответ Марка - более простое и элегантное решение. Зачем создавать сложную программу с межпотоковым взаимодействием, если в этом нет необходимости? Создайте 4 потока. Каждый поток вычисляет размер файла / 4, чтобы определить свою начальную точку (и точку остановки). Тогда каждый поток может работать полностью независимо.

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

Это устранит узкие места из-за того, что чтение будет выполняться одним потоком:

open file
for each thread n=0,1,2,3:
    seek to file offset 1/n*filesize
    scan to next complete line
    process all lines in your part of the file

Мой опыт работы с Java, а не с C#, поэтому извиняюсь, если эти решения не применимы.

Непосредственным решением, которое я могу придумать в своей голове, было бы наличие исполнителя, который запускает 3 потока (например, используя ). Для каждой строки / записи, считанной из входного файла, запускать задание у исполнителя (используя ). Исполнитель будет ставить запросы для вас в очередь и распределять их между 3 потоками.Executors .newFixedThreadPool ExecutorService .submit

Возможно, существуют лучшие решения, но, надеюсь, они сработают. :-)

ETA: Очень похоже на второе решение Wolfbyte. :-)

ETA2: System.Threading.ThreadPoolпохоже на очень похожую идею в .NET. Я никогда им не пользовался, но, возможно, оно того стоит!

Поскольку при работе с файлами узким местом обычно является обработка, а не чтение, я бы выбрал шаблон производитель-потребитель . Чтобы избежать блокировки, я бы посмотрел на списки свободных от блокировок. Поскольку вы используете C#, вы можете взглянуть на код Lock-Free List Джулиана Бакнелла .

@lomaxx

@ Дерек и Марк: Хотел бы я принять 2 ответа. Мне придется в конечном итоге использовать решение Wolfbyte, потому что если я разделю файл на n разделов, существует вероятность того, что поток столкнется с пакетом «медленных» транзакций, однако если бы я обрабатывал файл, в котором каждый процесс гарантированно требовал равного объема обработки, тогда мне действительно нравится ваше решение, состоящее в том, чтобы просто разбить файл на куски и назначить каждый фрагмент потоку и завершить работу с ним.

Не стоит беспокоиться. Если проблема заключается в кластеризованных «медленных» транзакциях, то решение с очередями - лучший вариант. В зависимости от того, насколько быстрой или медленной является средняя транзакция, вы также можете захотеть назначить несколько строк за раз каждому работнику. Это сократит накладные расходы на синхронизацию. Точно так же вам может потребоваться оптимизировать размер буфера. Конечно, обе эти оптимизации, вероятно, вам следует делать только после профилирования. (Нет смысла беспокоиться о синхронизации, если она не является узким местом.)

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

Предварительно проанализированные фрагменты данных (в которых вы уже выполнили все сравнения строк и "токенизировали" их) могут быть затем переданы той части кода, которая фактически будет проверять семантику и порядок токенизированных данных.

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

Разделите файл на части и проанализируйте его. Читайте только столько фрагментов, над которыми вы работаете за раз, плюс несколько для «упреждающего чтения», чтобы не останавливаться на диске, когда вы заканчиваете обработку фрагмента, прежде чем перейти к следующему фрагменту.

В качестве альтернативы, большие файлы могут быть отображены в памяти и загружены по запросу. Если у вас больше потоков, работающих над обработкой файла, чем процессоров (обычно количество потоков = 1,5-2X ЦП - хорошее число для приложений подкачки по запросу), потоки, которые останавливаются на вводе-выводе для файла с отображением памяти, будут автоматически останавливаться из ОС до тех пор, пока их память готова, и другие потоки продолжат обработку.