12

假设我有一个迭代 400 个对象列表的脚本。每个对象都有 1 到 10 个属性。每个属性都是合理大小的字符串或稍大的整数。

将这些对象保存到 ScriptDB 与将它们保存到电子表格(无需在一次批量操作中执行)的性能是否存在显着差异。

4

2 回答 2

30

执行摘要

是的,有很大的不同!巨大的!我不得不承认,这个实验并没有达到我的预期。

有了这么多数据,写入电子表格总是比使用 ScriptDB 快得多。

这些实验支持Google Apps 脚本最佳实践中关于批量操作的断言。使用单个setValues()调用在电子表格中保存数据比逐行快 75%,比逐个单元快两个数量级。

另一方面Spreadsheet.flush(),由于性能影响,应仔细考虑使用建议。在这些实验中,单次写入 4000 个单元格的电子表格花费了不到 50 毫秒,而添加一个调用将flush()其增加到 610 毫秒 - 仍然不到一秒,但一个数量级的税收似乎很可笑。调用flush()示例电子表格中的 400 行中的每一行,使操作花费了将近 12 秒,而没有它只需要 164 毫秒。如果您一直遇到Exceeded maximum execution time错误,您可能会从优化代码和删除对flush().

实验结果

所有计时都是按照如何测量函数执行所用时间中描述的技术得出的。时间以毫秒为单位。

以下是五种不同方法的一次传递的结果,两种使用ScriptDB,三种写入电子表格,所有方法都具有相同的源数据。(400 个对象,具有 5 个字符串和 5 个数字属性)

实验一

  • ScriptDB/Object 测试经过的时间:53529
  • ScriptDB/Batch 测试经过的时间:37700
  • 电子表格/对象测试的经过时间:145
  • 电子表格/属性测试的经过时间:4045
  • 电子表格/批量测试的经过时间:32

效果Spreadsheet.flush()

实验二

在这个实验中,与实验 1 的唯一区别是我们Spreadsheet.flush()在每次通话后都进行setValue/s通话。这样做的成本是巨大的(大约 700%),但出于速度原因,并没有改变使用电子表格而不是 ScriptDB 的建议,因为写入电子表格仍然更快。

  • ScriptDB/Object 测试经过的时间:55282
  • ScriptDB/Batch 测试经过的时间:37370
  • 电子表格/对象测试的经过时间:11888
  • 电子表格/属性测试的经过时间:117388
  • 电子表格/批量测试经过的时间:610

注意:这个实验经常被Exceeded maximum execution time杀死。

买者自负

您正在互联网上阅读此内容,所以它一定是真的!但要带着一粒盐。

  • 这些是来自非常小的样本量的结果,可能无法完全重现。
  • 这些结果测量的是不断变化的东西——虽然它们是在 2013 年 2 月 28 日观察到的,但当您阅读本文时,它们测量的系统可能会完全不同。
  • 这些操作的效率受这些实验中不受控制的许多因素的影响;例如,缓存指令和中间结果以及服务器负载。
  • 也许,只是也许,谷歌的某个人会读到这篇文章,并提高 ScriptDB 的效率!

编码

如果您想执行(或更好地改进)这些实验,请创建一个空白电子表格,并将其复制到其中的新脚本中。这也可以作为 gist获得。

/**
 * Run experiments to measure speed of various approaches to saving data in
 * Google App Script (GAS).
 */
function testSpeed() {
  var numObj = 400;
  var numAttr = 10;
  var doFlush = false;  // Set true to activate calls to SpreadsheetApp.flush()

  var arr = buildArray(numObj,numAttr);
  var start, stop;  // time catchers
  var db = ScriptDb.getMyDb();
  var sheet;

  // Save into ScriptDB, Object at a time
  deleteAll(); // Clear ScriptDB
  start = new Date().getTime();
    for (var i=1; i<=numObj; i++) {
      db.save({type: "myObj", data:arr[i]});
    }
  stop = new Date().getTime();
  Logger.log("Elapsed time for ScriptDB/Object test: " + (stop - start));

  // Save into ScriptDB, Batch
  var items = [];
  // Restructure data - this is done outside the timed loop, assuming that
  // the data would not be in an array if we were using this approach.
  for (var obj=1; obj<=numObj; obj++) {
    var thisObj = new Object();
    for (var attr=0; attr < numAttr; attr++) {
      thisObj[arr[0][attr]] = arr[obj][attr];
    }
    items.push(thisObj);
  }
  deleteAll(); // Clear ScriptDB
  start = new Date().getTime();
    db.saveBatch(items, false);
  stop = new Date().getTime();
  Logger.log("Elapsed time for ScriptDB/Batch test: " + (stop - start));

  // Save into Spreadsheet, Object at a time
  sheet = SpreadsheetApp.getActive().getActiveSheet().clear();
  start = new Date().getTime();
    for (var row=0; row<=numObj; row++) {
      var values = [];
      values.push(arr[row]);
      sheet.getRange(row+1, 1, 1, numAttr).setValues(values);
      if (doFlush) SpreadsheetApp.flush();
    }
  stop = new Date().getTime();
  Logger.log("Elapsed time for Spreadsheet/Object test: " + (stop - start));

  // Save into Spreadsheet, Attribute at a time
  sheet = SpreadsheetApp.getActive().getActiveSheet().clear();
  start = new Date().getTime();
    for (var row=0; row<=numObj; row++) {
      for (var cell=0; cell<numAttr; cell++) {
        sheet.getRange(row+1, cell+1, 1, 1).setValue(arr[row][cell]);
        if (doFlush) SpreadsheetApp.flush();
      }
    }
  stop = new Date().getTime();
  Logger.log("Elapsed time for Spreadsheet/Attribute test: " + (stop - start));

  // Save into Spreadsheet, Bulk
  sheet = SpreadsheetApp.getActive().getActiveSheet().clear();
  start = new Date().getTime();
    sheet.getRange(1, 1, numObj+1, numAttr).setValues(arr);
    if (doFlush) SpreadsheetApp.flush();
  stop = new Date().getTime();
  Logger.log("Elapsed time for Spreadsheet/Bulk test: " + (stop - start));
}

/**
 * Create a two-dimensional array populated with 'numObj' rows of 'numAttr' cells.
 */
function buildArray(numObj,numAttr) {
  numObj = numObj | 400;
  numAttr = numAttr | 10;
  var array = [];
  for (var obj = 0; obj <= numObj; obj++) {
    array[obj] = [];
    for (var attr = 0; attr < numAttr; attr++) {
      var value;
      if (obj == 0) {
        // Define attribute names / column headers
        value = "Attr"+attr;
      }
      else {
        value = ((attr % 2) == 0) ? "This is a reasonable sized string for testing purposes, not too long, not too short." : Number.MAX_VALUE;
      }
      array[obj].push(value);
    }
  }
  return array
}

function deleteAll() {
  var db = ScriptDb.getMyDb();
  while (true) {
    var result = db.query({}); // get everything, up to limit
    if (result.getSize() == 0) {
      break;
    }
    while (result.hasNext()) {
      var item = result.next()
      db.remove(item);
    }
  }
}
于 2013-03-01T03:25:17.143 回答
4

ScriptDB 已被弃用。不使用。

于 2014-05-23T18:57:00.920 回答