【GAS】スクリプトが遅い時の高速化テクニック|getValues・setValuesとmap活用で61秒→1秒未満に

2021年8月9日

GASのスクリプトを組んで自動化したものの「実行が遅い」「6分制限や30分制限で途中終了してしまう」という問題に悩んでいませんか?

本記事では、GASが遅くなる最大の原因である「スプレッドシートへの読み書き回数」を削減する高速化テクニックを解説します。同じ処理が約61秒から1秒未満に改善した実例をコード付きで紹介します。

GASが遅くなる最大の原因:getValue/setValueのループ内呼び出し

スプレッドシートの値を取得するgetValue()getValues()、値を書き込むsetValue()setValues()は処理に時間がかかります。

1〜2回なら問題ありませんが、数百回とループ内で呼び出すと大きなボトルネックになります。

遅いコードの例(約61秒)

消費税を計算してスプレッドシートに書き出す処理を例に説明します。

スプレッドシートのサンプルデータ
// NG例:forループ内にgetValue/setValueが入っている(200行で約61秒)
function tax1(){
  const ROW = 200
  const ss = SpreadsheetApp.getActiveSpreadsheet()
  const sh = ss.getSheetByName("TAX")
  const TAX = sh.getRange("B2").getValue()

  let price, tax_price
  for(i = 5; i <= ROW + 4; i++){
    price     = sh.getRange(i, 2).getValue()  // 毎回スプレッドシートにアクセス
    tax_price = price * (1 + TAX)
    sh.getRange(i, 3).setValue(tax_price)      // 毎回スプレッドシートに書き込み
  }
}

200行のデータに対して400回(読み200回+書き200回)のスプレッドシートアクセスが発生するため非常に遅くなります。

高速化テクニック1:getValues/setValuesで一括処理する(1秒未満)

getValue/setValueをループの外に出し、配列で一括読み書きします。

// OK例:一括取得・一括書き込みで高速化(200行で1秒未満)
function tax2(){
  const ROW = 200
  const ss = SpreadsheetApp.getActiveSpreadsheet()
  const sh = ss.getSheetByName("TAX")
  const TAX = sh.getRange("B2").getValue()

  // 一括取得(スプレッドシートアクセス1回)
  let price_list = sh.getRange(5, 2, ROW, 1).getValues()

  let tax_price = []
  for(i = 0; i < ROW; i++){
    tax_price[i] = []
    tax_price[i][0] = price_list[i][0] * (1 + TAX)  // メモリ内で計算
  }

  // 一括書き込み(スプレッドシートアクセス1回)
  sh.getRange(5, 3, price_list.length, 1).setValues(tax_price)
}

スプレッドシートへのアクセスが合計2回(読み1回+書き1回)になるため劇的に高速化されます。

高速化テクニック2:forループをmapに置き換える(さらに高速化)

forループをmap()に置き換えることで、さらにコードをシンプルかつ高速にできます。

// ベスト例:mapを使ってシンプルに高速化
function tax3(){
  const ROW = 200
  const ss = SpreadsheetApp.getActiveSpreadsheet()
  const sh = ss.getSheetByName("TAX")
  const TAX = sh.getRange("B2").getValue()

  let price_list = sh.getRange(5, 2, ROW, 1).getValues()

  // mapで配列を変換(1行で書ける)
  let tax_price = price_list.map(elm => [elm[0] * (1 + TAX)])

  sh.getRange(5, 3, price_list.length, 1).setValues(tax_price)
}

パフォーマンス比較

  • tax1(forループ内でgetValue/setValue):約61秒
  • tax2(getValues/setValues一括処理):1秒未満
  • tax3(mapを使用):1秒未満

高速化のポイントまとめ

  • getValue/setValueはループ外に出す:ループ内で呼ぶ回数を最小限に
  • getValues/setValuesで一括処理:配列として一度に読み書きする
  • mapやfilterを活用:配列操作はforよりmap/filterが高速で可読性も高い
  • スプレッドシートアクセスは最小限に:一度取得したデータはメモリ上で処理する

GAS,Javascript

Posted by Next-k