GAS パフォーマンス改善の話
#技術ログ
受け口が複数あるアンケートの結果を、各受け口ごとに抽出してシートを分けるコードを書いた。
こういうフォーム(例。実際に書いたコードを適用するフォームには受け口とそれぞれに対するアンケート項目がもっとある)
https://gyazo.com/7028c784c71b776484947ae67230d883
https://gyazo.com/a7f6047c985bd5d0335940ddf5a4b201
こういう入力に対して、こんな感じの抽出結果がほしい(東京だけのサンプル)
https://gyazo.com/9867cb137af33cbee4187fffa2f3aa69
とりあえずシート内の行を指定の条件(上記なら住所)で抽出してヘッダー調整やら結果行の空セル詰めやらして体裁を整えたstringの二次元配列を新しいシートに詰め直せば良いかと思いコードを書いたが、パフォーマンスが非常に悪かった。
不必要にsheetへの読み書きを行うAPIを呼び出すことがパフォーマンス悪化の要因だったようなので、不必要なAPIの呼び出しをしている箇所を修正することとした。
※GASの開発にTypeScriptを利用しているので、以下サンプルコードもTypeScriptとする。
1. そもそもフィルタリングするモジュールにシートそのものを渡していた
モジュール毎にシートから行を取得 -> フィルタリングの実行・整形 -> 整形結果(string[][])とやっていて、モジュールが増えるごとにAPIの呼び出しが増える。行の取得・整形にもAPIを使っていたので倍々ゲーム。
code:typescript
const module = (抽出条件: 条件) => (sheet: GoogleAppsScript.Spreadsheet.Sheet): string[][] => {
const header: string[] = extractHeader(sheet) /* 条件に従いsheetからヘッダーを抽出・整形 */
const body: string[] = extractBody(sheet) /* 条件に従いsheetから結果行を抽出・整形 */
return header.concat(body)
}
const module1Result = module(条件.条件1)(sheet)
const module2Result = module(条件.条件2)(sheet)
モジュールに渡す要素をシートの行の配列にして対処。
code:typescript
const sheetRows: string[][] = sheet.getRange(...).getValues()
const module = (抽出条件: 条件) => (sheet: string[][]): string[][] => {
const header: string[] = extractHeader(sheet) /* 条件に従いsheetからヘッダーを抽出・整形 */
const body: string[] = extractBody(sheet) /* 条件に従いsheetから結果行を抽出・整形 */
return header.concat(body)
}
const module1Result = module(条件.条件1)(sheetRows)
const module2Result = module(条件.条件2)(sheetRows)
ヘッダー・結果行の抽出もstring[][]を受け取る形とした。これで抽出処理のAPI呼び出しは初期のgetRange().getValues()のみとなった。
2. 抽出結果の出力をappendRow()で行っていた
code:typescript
const makeResultSheet = (sheetName: string) => (rows: string[][]) => {
const sheet = app.insertSheet(sheetName)
rows.forEach(row => sheet.appendRow(row))
}
makeResultSheet('抽出結果1')(module1Result)
makeResultSheet('抽出結果2')(module2Result)
抽出結果行が増える度にAPI呼び出しが増える。抽出条件ごとにそれが起こるので倍々ゲーム。
多分これが一番重かった。
sheet.getRange().setValues()を用いて対処。
code:typescript
module1Result, module2Result.forEach((data, i) => {
const sheet = app.insertSheet(抽出結果${i + 1})
sheet.getRange(1, 1, data.length, data0.length).setValues(data)
})
setValues()は、Rangeに対して二次元配列を引数として渡してやると当該範囲を引数で埋めてくれる。
Rangeを抽出結果に相応しい範囲とするため、getRange()の第3引数(行数)に抽出結果の行数、第4引数に抽出結果のヘッダーの要素数を指定
これで抽出結果出力のAPI呼び出しは抽出結果毎にinsertSheet(), getRange().setValues()のみとなった。
参考ページ
Class Sheet  |  Apps Script  |  Google Developers
Class Range  |  Apps Script  |  Google Developers