Прочитать большой файл txt многопоточным?

У меня есть большой файл txt со 100000 строк. Мне нужно запустить n-count потоков и дать каждому потоку uniq строку из этого файла. Каков наилучший способ сделать это? Я думаю, мне нужно прочитать файл по строкам, и итератор должен быть глобальным, чтобы заблокировать его. Загрузка txt файла в список будет трудоемкой, и я могу получить исключение OutofMemory. Есть идеи? Справка plz с некоторым кодом.

Ответ 1

Вы можете использовать File.ReadLines Method, чтобы читать файл по очереди, не загружая сразу весь файл в память, и Parallel.ForEach Method для параллельной обработки строк в нескольких потоках:

Parallel.ForEach(File.ReadLines("file.txt"), (line, _, lineNumber) =>
{
    // your code here
});

Ответ 2

После выполнения моих собственных тестов для загрузки 61,277,203 строк в память и сдвига значений в словарь /ConcurrentDictionary () результаты, похоже, поддерживают ответ @dtb выше, что использование следующего подхода является самым быстрым:

Parallel.ForEach(File.ReadLines(catalogPath), line =>
{

}); 

Мои тесты также показали следующее:

  • File.ReadAllLines() и File.ReadAllLines(). Кажется, что AsParallel() работает почти с одинаковой скоростью в файле такого размера. Глядя на активность моего процессора, кажется, что оба они, похоже, используют два из моих 8 ядер?
  • Чтение всех данных сначала с использованием File.ReadAllLines() выглядит намного медленнее, чем использование File.ReadLines() в цикле Parallel.ForEach().
  • Я также попробовал шаблон производителя/потребителя или MapReduce, где для чтения данных использовался один поток, а для его обработки использовался второй поток. Это также, похоже, не превосходит простой шаблон выше.

Я включил пример этого шаблона для справки, так как он не указан на этой странице:

var inputLines = new BlockingCollection<string>();
ConcurrentDictionary<int, int> catalog = new ConcurrentDictionary<int, int>();

var readLines = Task.Factory.StartNew(() =>
{
    foreach (var line in File.ReadLines(catalogPath)) 
        inputLines.Add(line);

        inputLines.CompleteAdding(); 
});

var processLines = Task.Factory.StartNew(() =>
{
    Parallel.ForEach(inputLines.GetConsumingEnumerable(), line =>
    {
        string[] lineFields = line.Split('\t');
        int genomicId = int.Parse(lineFields[3]);
        int taxId = int.Parse(lineFields[0]);
        catalog.TryAdd(genomicId, taxId);   
    });
});

Task.WaitAll(readLines, processLines);

Вот мои тесты:

enter image description here

Я подозреваю, что при определенных условиях обработки шаблон производителя/потребителя может превзойти простой шаблон Parallel.ForEach(File.ReadLines()). Однако в этой ситуации этого не произошло.

Ответ 4

Что-то вроде:

public class ParallelReadExample
{
    public static IEnumerable LineGenerator(StreamReader sr)
    {
        while ((line = sr.ReadLine()) != null)
        {
            yield return line;
        }
    }

    static void Main()
    {
        // Display powers of 2 up to the exponent 8:
        StreamReader sr = new StreamReader("yourfile.txt")

        Parallel.ForEach(LineGenerator(sr), currentLine =>
            {
                // Do your thing with currentLine here...
            } //close lambda expression
        );

        sr.Close();
    }
}

Думаю, это сработает. (Нет компилятора С#/IDE здесь)

Ответ 5

Как упоминалось выше @dtb, самый быстрый способ прочитать файл, а затем обработать отдельные строки в файле: 1) сделать File.ReadAllLines() в массив 2) Используйте цикл Parallel.For для итерации по массиву.

Здесь вы можете прочитать больше тестов производительности.

Основной смысл кода, который вам нужно написать:

string[] AllLines = File.ReadAllLines(fileName);
Parallel.For(0, AllLines.Length, x =>
{
    DoStuff(AllLines[x]);
    //whatever you need to do
});

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

Ответ 6

Если вы хотите ограничить количество потоков на n, проще всего использовать AsParallel() вместе с WithDegreeOfParallelism(n), чтобы ограничить количество потоков:

string filename = "C:\\TEST\\TEST.DATA";
int n = 5;

foreach (var line in File.ReadLines(filename).AsParallel().WithDegreeOfParallelism(n))
{
    // Process line.
}