什么更快:ScriptDb 还是 SpreadsheetApp?

12
假设我有一个脚本,可以迭代处理包含400个对象的列表。 每个对象具有1到10个属性,每个属性都是一个适当大小的字符串或相当大的整数。
将这些对象保存到ScriptDB和将它们保存到电子表格中(不进行批量操作)是否会在性能上有显著差异?
2个回答

30

执行摘要

是的,有很大的差别!非常大!我必须承认这个实验结果并不如我预期。

在这么多数据的情况下,写入电子表格总比使用ScriptDB快得多。

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

另一方面,建议使用 Spreadsheet.flush() 应该慎重考虑,因为它会对性能产生影响。在这些实验中,一个 4000 单元格电子表格的单次写入只需不到 50 毫秒,而添加一个 flush() 调用将其增加到了 610 毫秒 - 仍然不到一秒,但是数量级的差异似乎很荒谬。对于样本电子表格中的每个 400 行调用 flush() 让操作时间几乎达到了 12 秒,在没有调用它时只需 164 毫秒。如果您一直遇到“超过最大执行时间”错误,则可能从优化代码和删除对 flush() 的调用中受益。

实验结果

所有计时均采用 如何测量函数执行时间 中描述的技术。时间以毫秒为单位。

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

实验1

  • ScriptDB/Object测试的经过时间:53529
  • ScriptDB/Batch测试的经过时间:37700
  • Spreadsheet/Object测试的经过时间:145
  • Spreadsheet/Attribute测试的经过时间:4045
  • Spreadsheet/Bulk测试的经过时间:32

Spreadsheet.flush()的效果

实验2

在这个实验中,与实验1唯一的区别是我们在每次setValue/s调用后调用了Spreadsheet.flush()。这样做的代价非常大(约为700%),但并不改变使用电子表格而不是ScriptDB来提高速度的建议,因为写入电子表格仍然更快。

  • ScriptDB/Object测试的经过时间:55282
  • ScriptDB/Batch测试的经过时间:37370
  • Spreadsheet/Object测试的经过时间:11888
  • Spreadsheet/Attribute测试的经过时间:117388
  • Spreadsheet/Bulk测试的经过时间:610
注意:这个实验经常会因为“超过最大执行时间”而被终止。
买家需谨慎
你正在网上阅读这篇文章,所以它一定是真的!但请保持怀疑态度。
以下是需要注意的几点:
- 这些结果来自非常小的样本量,可能无法完全重现。 - 这些结果测量的是一个不断变化的东西 - 尽管它们是在2013年2月28日观察到的,但当您阅读此文时,所测量的系统可能完全不同。 - 这些操作的效率受到许多因素的影响,在这些实验中无法控制;例如指令和中间结果的缓存以及服务器负载。 - 也许,只有可能,谷歌的某个人会阅读这篇文章,并提高 ScriptDB 的效率!
代码
如果您想执行(或更好地改进)这些实验,请创建一个空白电子表格,并将其复制到其中的新脚本中。 这也可以作为一个要点提供
/**
 * 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);
    }
  }
}

哇,真是太棒了...这证实了我一直以来的感觉。电子表格服务最近(相当)得到了改进,我注意到我写于两年前的某些脚本运行速度比我最初编写它们时要快得多。还有另一系列实验具有有趣的结果在这里megabyte1024 - Serge insas
@Serge 感谢您指出这个问题!您是否再次尝试了那些基准测试?我想知道它们是否有所改进。Eric_Koleda的答案说他会报告任何进展情况 - 也许有一些他不知道的进展? - Mogsdad
2
我只是在提到这个实验并没有探讨数据库查询方面。假设你有一个包含百万项或更多的DB,并且你想要获取单个特定项,我觉得ScriptDb在查找方面会更好,因为你不必将整个数据集加载到数组中。但正如你所说的,你不知道直到你测试过。 - Phil Bozak
2
又是一个出色答案的+1。以下是一些平均速度...查询一个属性并返回结果中的第一条记录(电子表格:使用getDataRange().getValues()并通过该方法进行迭代)- 电子表格179ms,ScriptDb 87ms(请注意,对于ScriptDb,result = db.query({query obj})花费0ms(平均值!),而firstRecord = result.next()花费87ms)。将一条记录写入数据库(电子表格:使用appendRow())- 电子表格111ms,ScriptDb 164ms。 - AdamL
显然,电子表格无法像scriptDB一样处理任意嵌套对象。 - JSDBroughton
显示剩余5条评论

4

ScriptDB已被弃用,请勿使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接