在c#中并行读取一百万行的csv文件

时间:2012-08-09 11:00:15

标签: c# multithreading performance parallel-processing

我有一个包含超过1百万行数据的CVS文件。我打算并行阅读它们以提高效率。我可以做类似以下的事情,还是有更有效的方法?

namespace ParallelData
{
public partial class ParallelData : Form
{
    public ParallelData()
    {
        InitializeComponent();
    }

    private static readonly char[] Separators = { ',', ' ' };

    private static void ProcessFile()
    {
        var lines = File.ReadLines("BigData.csv");
        var numbers = ProcessRawNumbers(lines);

        var rowTotal = new List<double>();
        var totalElements = 0;

        foreach (var values in numbers)
        {
            var sumOfRow = values.Sum();
            rowTotal.Add(sumOfRow);
            totalElements += values.Count;
        }
        MessageBox.Show(totalElements.ToString());
    }

    private static List<List<double>> ProcessRawNumbers(IEnumerable<string> lines)
    {
        var numbers = new List<List<double>>();
        /*System.Threading.Tasks.*/
        Parallel.ForEach(lines, line =>
        {
            lock (numbers)
            {
                numbers.Add(ProcessLine(line));
            }
        });
        return numbers;
    }

    private static List<double> ProcessLine(string line)
    {
        var list = new List<double>();
        foreach (var s in line.Split(Separators, StringSplitOptions.RemoveEmptyEntries))
        {
            double i;
            if (Double.TryParse(s, out i))
            {
                list.Add(i);
            }
        }
        return list;
    }

    private void button2_Click(object sender, EventArgs e)
    {
        ProcessFile();
    }
}
}

3 个答案:

答案 0 :(得分:10)

我不确定这是个好主意。根据您的硬件,CPU不会成为瓶颈,磁盘读取速度也会如此。

另一点:如果您的存储硬件是磁性硬盘,那么磁盘读取速度与文件在磁盘中的物理存储方式密切相关;如果文件没有碎片(即所有文件块都按顺序存储在磁盘上),如果你按顺序逐行读取,你的性能会更好。

一种解决方案是一次读取整个文件(如果你有足够的内存空间,100万行应该没问题)使用File.ReadAllLines,将所有行存储在字符串数组中,然后处理(如果行顺序不重要,则在string.Split中使用Parallel.Foreach ...等进行解析。

答案 1 :(得分:0)

通常,您应该尽量避免在多个线程上进行磁盘访问。磁盘是一个瓶颈,会阻塞,因此可能会影响性能。

如果文件中的行大小不是问题,您应该首先读取整个文件,然后并行处理。

如果文件太大而无法实现,那么您可以使用BlockingCollection加载它。使用一个线程读取文件并填充BlockingCollection,然后使用Parallel.ForEach处理其中的项目。 BlockingCollection允许您specify the max size of the collection,因此它只会读取文件中的更多行,因为处理和删除了集合中已有的行。

        static void Main(string[] args)
    {
        string  filename = @"c:\vs\temp\test.txt";
        int maxEntries = 2;

        var c = new BlockingCollection<String>(maxEntries);

        var taskAdding = Task.Factory.StartNew(delegate
        {
            var lines = File.ReadLines(filename);
            foreach (var line in lines)
            {
                c.Add(line);    // when there are maxEntries items
                                // in the collection, this line 
                                // and thread will block until 
                                // the processing thread removes 
                                // an item
            }

            c.CompleteAdding(); // this tells the collection there's
                                // nothing more to be added, so the 
                                // enumerator in the other thread can 
                                // end
        });

        while (c.Count < 1)
        {
            // this is here simply to give the adding thread time to
            // spin up in this much simplified sample
        }

        Parallel.ForEach(c.GetConsumingEnumerable(), i =>
           {
               // NOTE: GetConsumingEnumerable() removes items from the 
               //   collection as it enumerates over it, this frees up
               //   the space in the collection for the other thread
               //   to write more lines from the file
               Console.WriteLine(i);  
           });

        Console.ReadLine();
    }

与其他一些人一样,我不得不提出这样一个问题:这是否真的需要尝试通过并行化进行优化,或者单线程解决方案是否能够运行良好?多线程增加了很多复杂性,有时候不值得。

您希望改进哪种表现?

答案 2 :(得分:0)

我检查了计算机上的这些行,看起来像使用Parallel来读取csv文件而没有任何cpu昂贵的计算没有意义。并行运行它比在一个线程中花费更多时间。这是我的结果: 对于上面的代码:

2699ms 2712ms (检查两次以确认结果)

然后用:

private static IEnumerable<List<double>> ProcessRawNumbers2(IEnumerable<string> lines)
{
        var numbers = new List<List<double>>();
        foreach(var line in lines)
        {
            lock (numbers)
            {
                numbers.Add(ProcessLine(line));
            }
        }
    return numbers;
}

给我: 2075ms 2106ms

所以我认为如果csv中的那些数字不需要在程序中以某种方式(通过一些大量的计算等)计算然后存储在程序中,那么在这种情况下使用并行性是没有意义的,这样就添加一些开销。