次の方法で共有


Excel の JavaScript API を使用した、パフォーマンスの最適化

プロセスを最小限に抑え、関数をバッチ処理し、ペイロード サイズを小さくすることで、より高速でスケーラブルな 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 に直接インポートすると、パフォーマンスが低下する可能性があります。 代わりに、次の方法を使用します。

  1. range.valuesを使用して 2D 配列全体を範囲に書き込みます。
  2. 設定された範囲 (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();
  });
}

次の手順

関連項目