Что быстрее: ScriptDb или SpreadsheetApp?

Скажем, у меня есть a script, который выполняет итерацию по списку из 400 объектов. Каждый объект имеет от 1 до 10 свойств. Каждое свойство представляет собой строку разумного размера или несколько большое целое.

Есть ли существенная разница в эффективности сохранения этих объектов в ScriptDB и их сохранении в Таблице (без выполнения этой операции).

Ответ 1

ScriptDB устарел. Не используйте.

Ответ 2

Резюме

Да, есть существенная разница! Огромный! И я должен признать, что этот эксперимент не получился так, как я ожидал.

С таким количеством данных запись в электронную таблицу всегда была намного быстрее, чем использование ScriptDB.

Эти эксперименты подтверждают утверждения о массовых операциях в Google Apps Script Лучшие практики. Сохранение данных в электронной таблице с использованием одного вызова setValues() было на 75% быстрее, чем по очереди, и на два порядка быстрее, чем по умолчанию.

С другой стороны, рекомендации по использованию Spreadsheet.flush() должны быть тщательно рассмотрены из-за воздействия производительности. В этих экспериментах одна запись таблицы из 4000 ячеек заняла менее 50 мс, а добавление вызова к flush() увеличилось до 610 мс - еще меньше секунды, но налог на порядок выглядит смехотворным. Вызов flush() для каждой из 400 строк в электронной таблице с образцами заставил операцию заняться почти 12 секунд, когда она заняла всего 164 мс без нее. Если вы столкнулись с превышенными максимальными ошибками времени выполнения, вы можете воспользоваться как оптимизацией кода, так и удалением вызовов на flush().

Экспериментальные результаты

Все тайминги были получены по методу, описанному в Как измерить время, которое выполняет выполняемая функция. Время выражается в миллисекундах.

Вот результаты одного прохода из пяти разных подходов, два с использованием ScriptDB, три записи в электронные таблицы, все с одинаковыми исходными данными. (400 объектов с 5 строками и 5 атрибутами)

Эксперимент 1

  • Истекшее время для теста ScriptDB/Object: 53529
  • Истекшее время для ScriptDB/пакетного теста: 37700
  • Истекшее время для теста таблиц/объектов: 145
  • Истекшее время теста Spreadsheet/Attribute: 4045
  • Истекшее время для таблицы/массового теста: 32

Эффект Spreadsheet.flush()

Эксперимент 2

В этом эксперименте единственное отличие от эксперимента 1 заключалось в том, что мы вызывали Spreadsheet.flush() после каждого вызова setValue/s. Стоимость этого очень драматична (около 700%), но не изменяет рекомендации по использованию электронной таблицы по сравнению с ScriptDB по причинам скорости, поскольку запись в электронные таблицы еще быстрее.

  • Истекшее время для теста ScriptDB/Object: 55282
  • Истекшее время для ScriptDB/пакетного теста: 37370
  • Истекшее время для теста таблиц/объектов: 11888
  • Истекшее время теста Spreadsheet/Attribute: 117388
  • Истекшее время для таблицы/массового теста: 610

Примечание. Этот эксперимент часто убивали с превышенным максимальным временем выполнения.

Caveat Emptor

Вы читаете это на interwebs, так что это должно быть правдой! Но возьмите его с солью.

  • Это результаты с очень маленькими размерами выборки и могут быть не полностью воспроизводимыми.
  • Эти результаты измеряют то, что постоянно меняется - в то время как они наблюдались 28 февраля 2013 года, система, которую они измерили, может быть совершенно другой, когда вы читаете это.
  • Эффективность этих операций зависит от многих факторов, которые не контролируются в этих экспериментах; например, кэширование инструкций и промежуточных результатов и загрузки сервера.
  • Может быть, может быть, кто-то из Google прочитает это и повысит эффективность ScriptDB!

Код

Если вы хотите выполнить (или, еще лучше, улучшить) эти эксперименты, создайте пустую электронную таблицу и скопируйте ее в новый Script внутри нее. Это также доступно как инструмент.

/**
 * 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);
    }
  }
}