プロセスを最小限に抑え、関数をバッチ処理し、ペイロード サイズを小さくすることで、より高速でスケーラブルな Excel アドインを作成します。 この記事では、一般的な操作の最適化に役立つパターン、アンチパターン、コード サンプルについて説明します。
迅速な改善
これらの戦略を最初に適用して、直ちに最大の影響を与えます。
- バッチ読み込みと書き込み: グループ プロパティ
load呼び出し、1 つのcontext.sync()を行います。 - オブジェクトの作成を最小限に抑える: 多数の単一セル範囲ではなくブロック範囲に対して動作します。
- 配列にデータを書き込み、ターゲット範囲に 1 回割り当てます。
- 大きな変更に関してのみ、画面の更新または計算を中断します。
- 繰り返しごとの
Excel.runやループ内のcontext.sync()は避けてください。 - ループ内でクエリを再実行する代わりに、ワークシート、テーブル、および範囲オブジェクトを再利用します。
- 割り当て前にチャンクまたは集計することで、ペイロードをサイズ制限以下に保ちます。
重要
load呼び出しとsync呼び出しの推奨される使用により、多くのパフォーマンスの問題に対処できます。 アプリケーション固有の API を効率的に操作する方法のアドバイスについては、「 Office アドインのリソース制限とパフォーマンスの最適化 」の「アプリケーション固有の API によるパフォーマンスの向上」セクションを参照してください。
Excel のプロセスを一時的に中断する
Excel では、ユーザー入力アクションとアドイン アクションに対応するバックグラウンド タスクが実行されます。 選択したプロセスを一時停止すると、大規模な操作のパフォーマンスが向上する可能性があります。
計算を一時的に中断する
大きな範囲を更新する必要があり (値を割り当てて依存する数式を再計算する場合など)、中間再計算の結果が必要ない場合は、次の context.sync()まで一時的に計算を中断します。
非常に便利な方法で計算を中断し、再起動するための suspendApiCalculationUntilNextSync() API の使用方法については、「Application Object」リファレンスドキュメントを参照してください。 次のコードは、計算を一時的に中断する方法を示しています。
await Excel.run(async (context) => {
let app = context.workbook.application;
let sheet = context.workbook.worksheets.getItem("sheet1");
let rangeToSet: Excel.Range;
let rangeToGet: Excel.Range;
app.load("calculationMode");
await context.sync();
// Calculation mode should be "Automatic" by default
console.log(app.calculationMode);
rangeToSet = sheet.getRange("A1:C1");
rangeToSet.values = [[1, 2, "=SUM(A1:B1)"]];
rangeToGet = sheet.getRange("A1:C1");
rangeToGet.load("values");
await context.sync();
// Range value should be [1, 2, 3] now
console.log(rangeToGet.values);
// Suspending recalculation
app.suspendApiCalculationUntilNextSync();
rangeToSet = sheet.getRange("A1:B1");
rangeToSet.values = [[10, 20]];
rangeToGet = sheet.getRange("A1:C1");
rangeToGet.load("values");
app.load("calculationMode");
await context.sync();
// Range value should be [10, 20, 3] when we load the property, because calculation is suspended at that point
console.log(rangeToGet.values);
// Calculation mode should still be "Automatic" even with suspend recalculation
console.log(app.calculationMode);
rangeToGet.load("values");
await context.sync();
// Range value should be [10, 20, 30] when we load the property, because calculation is resumed after last sync
console.log(rangeToGet.values);
});
数式の計算のみが中断されます。 変更された参照は引き続き再構築されます。 たとえば、ワークシートの名前を変更しても、数式内のすべての参照がそのワークシートに更新されます。
画面の更新を停止する
Excel では、変更が発生すると表示されます。 大規模で反復的な更新の場合は、中間画面の更新を抑制します。
Application.suspendScreenUpdatingUntilNextSync() は、次の context.sync() または Excel.runの終了までビジュアル更新を一時停止します。 中断中に UI がアイドル状態に表示されるため、状態テキストや進行状況バーなどのフィードバックをユーザーに提供します。
注:
suspendScreenUpdatingUntilNextSyncを繰り返し呼び出さないでください (ループなど)。 呼び出しを繰り返すと、Excel ウィンドウがちらつきになります。
イベントの有効化と無効化
イベントを無効にすると、パフォーマンスが向上することがあります。 イベントを有効化および無効化する方法を示すコード サンプルは、「イベントの操作」の記事に記載されています。
テーブルへのデータのインポート
TableRowCollection.add()を繰り返し呼び出すなど、大規模なデータセットを Table に直接インポートすると、パフォーマンスが低下する可能性があります。 代わりに、次の方法を使用します。
-
range.valuesを使用して 2D 配列全体を範囲に書き込みます。 - 設定された範囲 (
worksheet.tables.add()) の上にテーブルを作成します。
既存のテーブルの場合は、 table.getDataBodyRange() の値を一括で設定します。 テーブルは自動的に展開されます。
このアプローチの例を次に示します。
await Excel.run(async (context) => {
let sheet = context.workbook.worksheets.getItem("Sheet1");
// Write the data into the range first.
let range = sheet.getRange("A1:B3");
range.values = [["Key", "Value"], ["A", 1], ["B", 2]];
// Create the table over the range
let table = sheet.tables.add('A1:B3', true);
table.name = "Example";
await context.sync();
// Insert a new row to the table
table.getDataBodyRange().getRowsBelow(1).values = [["C", 3]];
// Change a existing row value
table.getDataBodyRange().getRow(1).values = [["D", 4]];
await context.sync();
});
注:
Table.convertToRange() メソッドを使用すると、Table オブジェクトを Range オブジェクトに簡単に変換できます。
ペイロード サイズ制限のベスト プラクティス
Excel JavaScript API には、API 呼び出しのサイズ制限があります。
Excel on the webは、要求と応答を 5 MB に制限します。 この制限を超えると、API から RichAPI.Error エラーが返されます。 すべてのプラットフォームでは、取得操作の範囲は 500 万セルに制限されています。 大きな範囲は、多くの場合、両方の制限を超えています。
要求のペイロード サイズは、次を組み合わせています。
- API 呼び出しの数。
-
Rangeオブジェクトなどのオブジェクトの数。 - 設定または取得する値の長さ。
RequestPayloadSizeLimitExceededが得られる場合は、次の方法を適用して、操作を分割する前にサイズを小さくします。
戦略 1: 変更されていない値をループから移動する
パフォーマンスを向上させるために、ループ内のプロセスを制限します。 次のコード サンプルでは、 context.workbook.worksheets.getActiveWorksheet() は、そのループ内で変更されないため、 for ループから移動できます。
// DO NOT USE THIS CODE SAMPLE. This sample shows a poor performance strategy.
async function run() {
await Excel.run(async (context) => {
let ranges = [];
// This sample retrieves the worksheet every time the loop runs, which is bad for performance.
for (let i = 0; i < 7500; i++) {
let rangeByIndex = context.workbook.worksheets.getActiveWorksheet().getRangeByIndexes(i, 1, 1, 1);
}
await context.sync();
});
}
次のコード サンプルは、同様のロジックを示していますが、戦略が改善されています。
context.workbook.worksheets.getActiveWorksheet()値は変更されないため、ループの前に取得されます。 ループ内で取得する必要があるのは、変化する値だけです。
// This code sample shows a good performance strategy.
async function run() {
await Excel.run(async (context) => {
let ranges = [];
// Retrieve the worksheet outside the loop.
let worksheet = context.workbook.worksheets.getActiveWorksheet();
// Only process the necessary values inside the loop.
for (let i = 0; i < 7500; i++) {
let rangeByIndex = worksheet.getRangeByIndexes(i, 1, 1, 1);
}
await context.sync();
});
}
戦略 2: 作成する範囲オブジェクトの数を減らします
より少ない範囲のオブジェクトを作成してパフォーマンスを向上させ、ペイロード サイズを小さくします。 次の 2 つの方法があります。
各範囲配列を複数の配列に分割する
範囲オブジェクトを少なくする方法の 1 つは、各範囲配列を複数の配列に分割し、ループと新しい context.sync() 呼び出しで各新しい配列を処理することです。
重要
この方法は、ペイロード サイズの制限を超えたことを確認した後でのみ使用してください。 複数のループを使用すると、各ペイロード要求のサイズが小さくなりますが、さらに context.sync() 呼び出しが追加され、パフォーマンスが低下する可能性があります。
次のコード サンプルでは、1 つのループで範囲の大きな配列を処理してから、1 つの context.sync() 呼び出しを処理します。 1 つの context.sync() 呼び出しで範囲の値を処理しすぎると、ペイロード要求サイズが 5 MB の制限を超えます。
// This code sample does not show a recommended strategy.
// Calling 10,000 rows would likely exceed the 5MB payload size limit in a real-world situation.
async function run() {
await Excel.run(async (context) => {
let worksheet = context.workbook.worksheets.getActiveWorksheet();
// This sample attempts to process too many ranges at once.
for (let row = 1; row < 10000; row++) {
let range = sheet.getRangeByIndexes(row, 1, 1, 1);
range.values = [["1"]];
}
await context.sync();
});
}
次のコード サンプルは、前のコード サンプルと同様のロジックを示していますが、5 MB のペイロード要求サイズ制限を超えないようにする戦略を使用しています。 次のコード サンプルでは、範囲は 2 つの個別のループで処理され、各ループの後に context.sync() 呼び出しが続きます。
// This code sample shows a strategy for reducing payload request size.
// However, using multiple loops and `context.sync()` calls negatively impacts performance.
// Only use this strategy if you've determined that you're exceeding the payload request limit.
async function run() {
await Excel.run(async (context) => {
let worksheet = context.workbook.worksheets.getActiveWorksheet();
// Split the ranges into two loops, rows 1-5000 and then 5001-10000.
for (let row = 1; row < 5000; row++) {
let range = worksheet.getRangeByIndexes(row, 1, 1, 1);
range.values = [["1"]];
}
// Sync after each loop.
await context.sync();
for (let row = 5001; row < 10000; row++) {
let range = worksheet.getRangeByIndexes(row, 1, 1, 1);
range.values = [["1"]];
}
await context.sync();
});
}
配列に範囲の値を設定する
範囲オブジェクトを少なくするもう 1 つの方法は、配列を作成し、ループを使用してその配列内のすべてのデータを設定し、配列値を範囲に渡すことです。 これにより、パフォーマンスとペイロード サイズの両方にメリットがあります。
range.valuesは、ループ内の各範囲に対してrange.valuesを呼び出す代わりに、ループの外部で 1 回呼び出されます。
次のコード サンプルは、配列を作成し、その配列の値を for ループに設定し、その配列値をループの外側の範囲に渡す方法を示しています。
// This code sample shows a good performance strategy.
async function run() {
await Excel.run(async (context) => {
const worksheet = context.workbook.worksheets.getActiveWorksheet();
// Create an array.
const array = new Array(10000);
// Set the values of the array inside the loop.
for (let i = 0; i < 10000; i++) {
array[i] = [1];
}
// Pass the array values to a range outside the loop.
let range = worksheet.getRange("A1:A10000");
range.values = array;
await context.sync();
});
}
次の手順
- ホスト レベル の制約のリソース制限とパフォーマンスの最適化 を確認します。
- 複数の範囲を使用して作成するオブジェクトの数を減らします。
- 操作時間や行数などのデータのテレメトリを追加して、パフォーマンスの最適化をさらに進めます。
関連項目
Office Add-ins