C#加载xlsx文件性能差

3
我有一个关于在C#中加载Excel(Xlsx)文件的问题。我已经使用NPOI 2.0实现了Excel文件的加载,但性能非常差(在Win7运行时,加载10000行和60列需要15到25秒,使用Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz (4 CPUs),~2.5GHz)。我认为这是因为NPOI 2.0仍处于beta版本,所以我尝试了另一个库EPPlus,但它仍然需要大约相同的时间来加载Excel文件。
下面是我如何使用EPPlus进行加载:
var existingFile = new FileInfo(path);

var excelData = new ExcelViewModel(path);

// Open and read the XlSX file.
using (var package = new ExcelPackage(existingFile))
{
    // Get the work book in the file
    ExcelWorkbook workBook = package.Workbook;
    if (workBook != null)
    {
        // Here is some initializing......

        var viewSheetModel = new ExcelSheetViewModel(sheet.Name, numberOfColumns, titles);
        for (var row = titleRowIndex + 1; row <= end.Row; ++row)
        {
            var viewRowModel = new ExcelRowViewModel();

            for (int column = start.Column; column <= end.Column; ++column)
            {
                var cell = sheet.Cells[row, column];
                viewRowModel.AddCellValue(cell.Value != null ? cell.Value.ToString() : string.Empty);
            }

            viewSheetModel.Rows.Add(viewRowModel);
        }

        excelData.AddSheet(viewSheetModel);
    }
}

根据 dotTrace Profiler 的结果显示,大约 40% 的时间浪费在 get_Workbook 方法上(通过访问“package.Workbook”属性调用该方法),然后又有 30% 的时间浪费在 get_Item 和 get_Value 调用中,再有 5% 的时间浪费在 AddCellValue 方法上(这是我的数据模型),其余的时间则分散在各种方法调用中。
我是否做错了什么,还是这种性能正常?
谢谢。

我无法评论以上的性能,但您可以考虑查看像SpreadsheetGear这样的产品,它可以轻松处理这样的文件大小。我不知道您的工作簿包含什么类型的数据,但加载具有10K行和60列随机文本/数字数据的工作簿并读取每个单元格的值需要 SpreadsheetGear 大约1秒钟,我的Intel i7-3770K @ 3.50GHz上运行。顺便说一句,我让 SpreadsheetGear 在大约相同的时间内生成了这些测试工作簿。如果您愿意,我可以提供更多详情。免责声明:我为 SpreadsheetGear 工作。 - Tim Andersen
2
你使用的是哪个版本的EPPlus?我尝试了EPPlus 3.1.3,它加载得足够快。没有实际的Excel文件,很难说更多。也许Excel文件有其他类型的“隐藏”数据(隐藏行/列/图片,多个数据密集的工作表)? - Vincent Tan
2个回答

2

我发现FOR循环非常耗费时间。以下是我如何解决在1秒内加载85000 x 26的表格。

ExcelWorksheet ws = ...

Int32 maxLength = ws.Dimension.End.Row + 1;
Int32 maxWidth = ws.Dimension.End.Column + 1;

// Fetch the entire sheet as one huge range
ExcelRange cells = ws.Cells[1, 1, maxLength, maxWidth];

// cells.Values now contains a 2 dimensional object array
// Feel free to stop here

// I wanted a jagged array of type string, so I converted it.
// Start by converting the 2D array to 1D.
object[] obj_values = ((object[,]) cells.Value).Cast<object>().ToArray();

// Convert object[] to string[]
string[] str_values = Array.ConvertAll(obj_values, p => p == null ? "" : p.ToString());

// Chunk 1D array back into a jagged array and convert nulls to String.Empty
Int32 j = 0;
string[][] values = str_values.GroupBy(p => j++ / maxWidth).Select(q => q.ToArray()).ToArray();

// This was very fast compared to FOR loops!

2
我认为,是的,EPPlus的观察性能对于它来说是正常的。我在EPPlus 4.5.2.1中遇到了类似的问题,五年后仍然如此。分析显示get_Worksheet占了59%的时间,在i5-4200U上单线程读取电子表格大约可以达到每秒120,000个单元格。虽然这比原帖中提到的约50,000个单元格/秒有所改善,但很可能是由于硬件差异造成的。

作为比较,SpreadsheetLight benchmark 在看起来是i7-7700上每秒处理425,000个单元格,比我测量的EPPlus快三倍左右。我自己编写的未经优化的C#解析器从.csv文件中读取同样的数据,每秒读取大约430,000个单元格,而@Tim Andersen关于SpreadsheetGear的评论则表明可以达到400,000个单元格/秒。我还没有找到EPPlus和其他Excel库(如ClosedXML、NPIO、Aspose或Microsoft的Open XML SDK)之间的比较基准。

在EPPlus中,我进行了分析的方法,从最快到最慢:

  1. ExcelWorksheet.Cells[1, 1, dimension.Rows, dimension.Columns].Value(本质上与@Kevin M的答案相同,但没有偏移)
  2. ExcelWorksheet.GetValue<string>(row, column)
  3. ExcelWorksheet.GetValue(row, column)
  4. ExcelWorksheet.Cells[row, column].Text
  5. ExcelWorksheet.Cells[row, column].Value

从EPPlus 4.5.2.1开始,在第一种方法中从ExcelRange.Value获取object[,]比GetValue()重载快几个百分点。通过Cells[row,column]逐个访问单元格比GetValue()慢大约25%。

EPPlus源代码的审查表明,需要EPPlus内部的代码更改才能改进。在我进行过性能测试的所有路径上,工作簿访问都很昂贵,并且是单线程的,无法通过额外的核心实现线性扩展。还有来自单元格地址转换和可提升的System.Globalization调用的非微不足道的开销,这与其他库大致快三倍的速度相一致,而EPPlus则较慢。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接