Office.js:将大量行写入 Excel 的性能越来越差
Office.js: Increasingly poorer performance writing a large amount of rows to Excel
我有大约 100 列宽的行,但我只想写大约 8000 行。当我写出其中的 3000 行(以 500 为一批)时,它始终以每 500 行约 2-3 秒的速度写入。
但是,当我尝试写出包含 8000 行类似数据(多几列)的更大数据集时,它在前 3000 行中表现良好(每 500 行约 3-4 秒) ,但是从第 2500 -3000 行开始,性能变得越来越慢并且 excel 开始爬行。例如:
write rows address: Sheet1!A3:DC502
batch write time: 3.0766400244386167 seconds
write rows address: Sheet1!A503:DC1002
batch write time: 3.3348399202363796 seconds
write rows address: Sheet1!A1003:DC1502
batch write time: 3.7307800745354034 seconds
write rows address: Sheet1!A1503:DC2002
batch write time: 4.149179874582915 seconds
write rows address: Sheet1!A2003:DC2502
batch write time: 3.8166401331085944 seconds
write rows address: Sheet1!A2503:DC3002
batch write time: 7.215600102149649 seconds
write rows address: Sheet1!A3003:DC3502
batch write time: 31.93173993128445 seconds
write rows address: Sheet1!A3503:DC4002
batch write time: 95.68281983804563 seconds
write rows address: Sheet1!A4003:DC4502
batch write time: 148.84947986377625 seconds
write rows address: Sheet1!A4503:DC5002
batch write time: 203.41412001861877 seconds
write rows address: Sheet1!A5003:DC5502
batch write time: 270.2974798251381 seconds
write rows address: Sheet1!A5503:DC6002
...
前 30 列左右包含公式,并且单元格中包含条件格式。其余的只是将数据写入普通的白色单元格。我只是使用 range.values 将数据交给 excel,这就是花了这么长时间的原因。 如何才能获得稳定的性能?
这是我的代码:
async writeRows(data, formulas, sheetName, startCol, startRow) {
return await Excel.run(async (ctx) => {
let sheet = ctx.workbook.worksheets.getItem(sheetName);
let endRow = startRow;
let startRowOffset = startRow;
let batchSize = 500;
for (let i = 0; i < data.length; i = i + batchSize) {
let t0 = performance.now();
let min = Math.min(batchSize, data.length - i);
let endCol = intToColumn(data[0].length);
startRow = startRowOffset + i;
endRow = startRow + min - 1;
let address = sheetName + "!" + startCol + startRow + ":" + endCol + endRow;
console.log("write rows address: ", address);
let range = sheet.getRange(address);
ctx.application.suspendApiCalculationUntilNextSync();
range.values = data.slice(i, i + min)
range.formulas = formulas.slice(i, i + min);
await ctx.sync();
let t1 = performance.now();
console.log("batch write time: ", (t1 - t0) / 1000, ' seconds');
}
return endRow;
});
}
如果您认为它只是繁重的公式,那么现在是 运行 相同行但不向 range.formulas 分配任何内容的时间:
batch write time: 2.072960040280297 seconds
batch write time: 1.893160016976646 seconds
batch write time: 2.239300093637197 seconds
batch write time: 2.4051598865728154 seconds
batch write time: 2.4535400113378855 seconds
batch write time: 4.228719875053808 seconds
batch write time: 21.932359953223656 seconds
batch write time: 65.58508005044697 seconds
batch write time: 99.76420028338683 seconds
batch write time: 133.58046007197566 seconds
batch write time: 181.46535997193905 seconds
...
这是任务管理器的屏幕截图:
有什么想法吗?
我不认识你的标签中的其他人,只认识 excel
。据我了解,您尝试在 Excel 中推送数据。因此,我的建议 - 在您这边编写整个 csv 文件并将其导出到 Excel 或通过 Excel 批量导入。
.
你在一个循环中有一个 ctx.sync
。这可能是性能杀手。尝试重构该方法,使其通过一次同步写入所有内容。在我对这个问题的回答中看到模式可能会有所帮助:.
我正在使用类似的批处理方法将大约 35k 行加载到 officejs 中,但是批处理之间的性能没有太大下降。 35 批 1000 行总共需要大约 7 秒:
这是我的代码:
getWorksheetDataInChunks() {
return ready.then(() => {
return Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const dataRange = sheet
.getUsedRange()
.load('columnIndex, rowIndex, columnCount, rowCount, address');
await context.sync();
const rowsTotal = dataRange.rowCount;
const batchSize = config.batchSize;
const data = [];
for (let i = 0; i < rowsTotal; i += batchSize) {
const chunk = `chunk${i / batchSize}`;
const chunkStart = `${chunk}-start`;
const chunkEnd = `${chunk}-end`;
performance.mark(chunkStart);
const rowsRemaining = rowsTotal - i;
const rowOffset = rowsRemaining >= batchSize ? batchSize : rowsRemaining;
const currentRange = sheet
.getCell(dataRange.rowIndex + i, dataRange.columnIndex)
.getResizedRange(rowOffset - 1, dataRange.columnCount - 1)
.load('values, columnIndex, rowIndex, columnCount, rowCount, address');
await context.sync();
data.push(...currentRange.values);
performance.mark(chunkEnd);
performance.measure(chunk, chunkStart, chunkEnd);
}
return data;
}).catch(buildErrorHandler('getWorksheetDataInChunks'));
});
},
你能测试读取数据是否和写入数据一样慢吗?
如果循环中没有 context.sync(),您将无法通过,因为超出限制 excel 的方式将在一次同步中处理。这是您首先进行批处理的唯一原因。尝试为每个循环迭代创建一个带有 Excel.run() 的新上下文,也许您可以在前一批之后 "cleanup"。
最大的问题:
未设置列格式(range.numberFormat
)。在将数据分配给 range.values
之前先设置这些,这一切都不同了。
//Initial pass over every column to set types
if (type === "Date") {
range.numberFormat = <any>'m/d/yyyy';
} else if (type === "Double") {
range.numberFormat = <any>"#,##0.00";
} else {
range.numberFormat = <any>"#";
}
最终时间:
如您所料,以一致的时间分批写入 1000 行:
batch write time: 3.0076802402327596 seconds
batch write time: 3.0477398637461594 seconds
batch write time: 2.9507200432747487 seconds
batch write time: 3.0690198313384025 seconds
batch write time: 2.988500015243975 seconds
batch write time: 3.048739928042458 seconds
batch write time: 3.0736401102757082 seconds
batch write time: 3.097120038203488 seconds
batch write time: 2.068400111446943 seconds
其他有用的更改:
最初,我在一个二维数组中逐个单元格地构建了我的公式。例如:
//explicity writing out each formula
range.formulas = [
[=A1+B1, =B2+C2],
[=A2+B2, =B2+C2],
...
[=A100+B100, =B100+C100]
];
正确的做法是:
//writing out formula for the first cell, and let excel expand it to the range.
let range = sheet.getRange('A1:A100');
range.formulas = '=A1+B1' as any
let range2 = sheet.getRange('B1:B100');
range2.formulas = '=B1+C1' as any
来源:感谢@Slai 分享这个link:https://github.com/OfficeDev/office-js-docs-pr/blob/master/docs/excel/performance.md
其他:
因为我正在为大约前 30 列编写公式,所以最初,我写出了从 A 列到 DC 列的整行,前 30 列为空。我认为这是不必要地写入 30 列空值,这可能会减慢速度。最终,我决定省略那些空列,只写出有数据的列。
我也玩过 ctx.sync(),但是在 Office 2016 的桌面版本上,我个人并没有注意到任何一种方式有太大差异(循环内,循环外、嵌套等)。但是,如果我像@Rick Kirkham 在他的 link.
中提到的那样使用 Office online 做任何事情,我会更加担心。
通过阅读这篇文章:https://github.com/OfficeDev/office-js/issues/12#issuecomment-374741210,我还更新了我的 Excel 桌面版本,使其晚于 build 9021。
我有大约 100 列宽的行,但我只想写大约 8000 行。当我写出其中的 3000 行(以 500 为一批)时,它始终以每 500 行约 2-3 秒的速度写入。
但是,当我尝试写出包含 8000 行类似数据(多几列)的更大数据集时,它在前 3000 行中表现良好(每 500 行约 3-4 秒) ,但是从第 2500 -3000 行开始,性能变得越来越慢并且 excel 开始爬行。例如:
write rows address: Sheet1!A3:DC502
batch write time: 3.0766400244386167 seconds
write rows address: Sheet1!A503:DC1002
batch write time: 3.3348399202363796 seconds
write rows address: Sheet1!A1003:DC1502
batch write time: 3.7307800745354034 seconds
write rows address: Sheet1!A1503:DC2002
batch write time: 4.149179874582915 seconds
write rows address: Sheet1!A2003:DC2502
batch write time: 3.8166401331085944 seconds
write rows address: Sheet1!A2503:DC3002
batch write time: 7.215600102149649 seconds
write rows address: Sheet1!A3003:DC3502
batch write time: 31.93173993128445 seconds
write rows address: Sheet1!A3503:DC4002
batch write time: 95.68281983804563 seconds
write rows address: Sheet1!A4003:DC4502
batch write time: 148.84947986377625 seconds
write rows address: Sheet1!A4503:DC5002
batch write time: 203.41412001861877 seconds
write rows address: Sheet1!A5003:DC5502
batch write time: 270.2974798251381 seconds
write rows address: Sheet1!A5503:DC6002
...
前 30 列左右包含公式,并且单元格中包含条件格式。其余的只是将数据写入普通的白色单元格。我只是使用 range.values 将数据交给 excel,这就是花了这么长时间的原因。 如何才能获得稳定的性能?
这是我的代码:
async writeRows(data, formulas, sheetName, startCol, startRow) {
return await Excel.run(async (ctx) => {
let sheet = ctx.workbook.worksheets.getItem(sheetName);
let endRow = startRow;
let startRowOffset = startRow;
let batchSize = 500;
for (let i = 0; i < data.length; i = i + batchSize) {
let t0 = performance.now();
let min = Math.min(batchSize, data.length - i);
let endCol = intToColumn(data[0].length);
startRow = startRowOffset + i;
endRow = startRow + min - 1;
let address = sheetName + "!" + startCol + startRow + ":" + endCol + endRow;
console.log("write rows address: ", address);
let range = sheet.getRange(address);
ctx.application.suspendApiCalculationUntilNextSync();
range.values = data.slice(i, i + min)
range.formulas = formulas.slice(i, i + min);
await ctx.sync();
let t1 = performance.now();
console.log("batch write time: ", (t1 - t0) / 1000, ' seconds');
}
return endRow;
});
}
如果您认为它只是繁重的公式,那么现在是 运行 相同行但不向 range.formulas 分配任何内容的时间:
batch write time: 2.072960040280297 seconds
batch write time: 1.893160016976646 seconds
batch write time: 2.239300093637197 seconds
batch write time: 2.4051598865728154 seconds
batch write time: 2.4535400113378855 seconds
batch write time: 4.228719875053808 seconds
batch write time: 21.932359953223656 seconds
batch write time: 65.58508005044697 seconds
batch write time: 99.76420028338683 seconds
batch write time: 133.58046007197566 seconds
batch write time: 181.46535997193905 seconds
...
这是任务管理器的屏幕截图:
有什么想法吗?
我不认识你的标签中的其他人,只认识 excel
。据我了解,您尝试在 Excel 中推送数据。因此,我的建议 - 在您这边编写整个 csv 文件并将其导出到 Excel 或通过 Excel 批量导入。
.
你在一个循环中有一个 ctx.sync
。这可能是性能杀手。尝试重构该方法,使其通过一次同步写入所有内容。在我对这个问题的回答中看到模式可能会有所帮助:
我正在使用类似的批处理方法将大约 35k 行加载到 officejs 中,但是批处理之间的性能没有太大下降。 35 批 1000 行总共需要大约 7 秒:
这是我的代码:
getWorksheetDataInChunks() {
return ready.then(() => {
return Excel.run(async (context) => {
const sheet = context.workbook.worksheets.getActiveWorksheet();
const dataRange = sheet
.getUsedRange()
.load('columnIndex, rowIndex, columnCount, rowCount, address');
await context.sync();
const rowsTotal = dataRange.rowCount;
const batchSize = config.batchSize;
const data = [];
for (let i = 0; i < rowsTotal; i += batchSize) {
const chunk = `chunk${i / batchSize}`;
const chunkStart = `${chunk}-start`;
const chunkEnd = `${chunk}-end`;
performance.mark(chunkStart);
const rowsRemaining = rowsTotal - i;
const rowOffset = rowsRemaining >= batchSize ? batchSize : rowsRemaining;
const currentRange = sheet
.getCell(dataRange.rowIndex + i, dataRange.columnIndex)
.getResizedRange(rowOffset - 1, dataRange.columnCount - 1)
.load('values, columnIndex, rowIndex, columnCount, rowCount, address');
await context.sync();
data.push(...currentRange.values);
performance.mark(chunkEnd);
performance.measure(chunk, chunkStart, chunkEnd);
}
return data;
}).catch(buildErrorHandler('getWorksheetDataInChunks'));
});
},
你能测试读取数据是否和写入数据一样慢吗?
如果循环中没有 context.sync(),您将无法通过,因为超出限制 excel 的方式将在一次同步中处理。这是您首先进行批处理的唯一原因。尝试为每个循环迭代创建一个带有 Excel.run() 的新上下文,也许您可以在前一批之后 "cleanup"。
最大的问题:
未设置列格式(range.numberFormat
)。在将数据分配给 range.values
之前先设置这些,这一切都不同了。
//Initial pass over every column to set types
if (type === "Date") {
range.numberFormat = <any>'m/d/yyyy';
} else if (type === "Double") {
range.numberFormat = <any>"#,##0.00";
} else {
range.numberFormat = <any>"#";
}
最终时间:
如您所料,以一致的时间分批写入 1000 行:
batch write time: 3.0076802402327596 seconds
batch write time: 3.0477398637461594 seconds
batch write time: 2.9507200432747487 seconds
batch write time: 3.0690198313384025 seconds
batch write time: 2.988500015243975 seconds
batch write time: 3.048739928042458 seconds
batch write time: 3.0736401102757082 seconds
batch write time: 3.097120038203488 seconds
batch write time: 2.068400111446943 seconds
其他有用的更改:
最初,我在一个二维数组中逐个单元格地构建了我的公式。例如:
//explicity writing out each formula
range.formulas = [
[=A1+B1, =B2+C2],
[=A2+B2, =B2+C2],
...
[=A100+B100, =B100+C100]
];
正确的做法是:
//writing out formula for the first cell, and let excel expand it to the range.
let range = sheet.getRange('A1:A100');
range.formulas = '=A1+B1' as any
let range2 = sheet.getRange('B1:B100');
range2.formulas = '=B1+C1' as any
来源:感谢@Slai 分享这个link:https://github.com/OfficeDev/office-js-docs-pr/blob/master/docs/excel/performance.md
其他:
因为我正在为大约前 30 列编写公式,所以最初,我写出了从 A 列到 DC 列的整行,前 30 列为空。我认为这是不必要地写入 30 列空值,这可能会减慢速度。最终,我决定省略那些空列,只写出有数据的列。
我也玩过 ctx.sync(),但是在 Office 2016 的桌面版本上,我个人并没有注意到任何一种方式有太大差异(循环内,循环外、嵌套等)。但是,如果我像@Rick Kirkham 在他的 link.
中提到的那样使用 Office online 做任何事情,我会更加担心。通过阅读这篇文章:https://github.com/OfficeDev/office-js/issues/12#issuecomment-374741210,我还更新了我的 Excel 桌面版本,使其晚于 build 9021。