将数据表存储为行对象数组,还是作为列数组的对象存储?

时间:2014-02-25 19:06:27

标签: javascript arrays memory profiling

  • 主要问题:是将数据表存储为行对象数组还是作为列数组的对象存储。
  • 近似问题:如何衡量对象的内存占用量。
  • 实际问题:如何阅读Chrome中的内存分析器?

背景

在浏览器和/或Node.js中使用Javascript中的矩形数据表。许多领先的库如D3和Crossfilter将数据存储为对象数组,例如

var rows = 
  [{name: 'apple', price: 1.79, ...}, 
   {name: 'berry', price: 3.49, ...}, 
   {name: 'cherry', price: 4.29, ...}, ...
  ]

然而,似乎有很多列(我的用例)和可能很多行,存储密钥的开销会变得非常繁重,存储每个列的数据(并迭代它)会更有效率数组,如:

var cols = {
   name: ['apple', 'berry', 'cherry', ...],
   price: [1.79, 3.49, 4.29, ...], 
   ...
}

分析问题

这篇文章的一个答案描述了使用Chrome内存配置文件:JavaScript object size

我在下面设置了以下简单的基准。代码可以复制/粘贴到Chrome的控制台并执行。然后我查看了Chrome分析器,但不知道如何阅读它。

乍一看,保留的大小显然有利于列:

  • window.rowData: 294,170,760 bytes
  • window.colData: 44,575,896 bytes

但如果我点击每一个,它们会给我相同的(巨大的)保留大小:

  • window.rowData338,926,668 bytes
  • window.colData338,926,668 bytes

基准代码

可以将以下代码复制/粘贴到Chrome控制台:

function makeid(len) {
  var text = "";
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  for (var i = 0; i < len; i++)
    text += possible.charAt(Math.floor(Math.random() * possible.length));

  return text;
}


/* create set of 400 string keys with 8 random characters.*/
var keys = window.keys = [], i, c;
for ( i=0;  i < 400; i++) {
  keys.push(makeid(8));
}

/* METHOD 1:  Create array of objects with {colName: cellValue} pairs */
var rows = window.rowData = [];
for ( i = 0; i < 10000; i++) {
  var row = {};
  for ( c = 0; c < 400; c++) {
    row[keys[c]] = Math.random();
  }

  rows.push(row);
}
/* METHOD 2: Create set of columns { colName: [values]} */
var cols = window.colData = {};
for ( c=0; c<400; c++) {
  var col = cols[keys[c]] = [];
  for ( i=0; i<10000; i++) {
    col[i] = rows[i][keys[c]];
  }
}

1 个答案:

答案 0 :(得分:1)

我会非常小心地以这种方式存储数据。

让我担心的主要问题是可用性。在我看来,将数据存储在这样的列中的最大缺点是,您现在负责以原子方式管理数据的插入和删除。您需要非常小心,以确保在一列中删除或插入值时,还要删除或插入所有其他列中相同位置的值。您还必须确保使用数据的任何内容都不会在删除/插入过程中读取值。如果在更新完成之前某些东西试图从数据中读取“行”,它将看到一个不一致的视图,这将是一件坏事。这一切听起来非常复杂,并且在Javascript中对我来说通常很不愉快。

当数据作为对象存储在数组中时,您可以非常简单地处理插入/删除。只需将整个对象移除或添加到阵列即可。整个操作都是原子操作,因此您不必担心计时,并且您永远不必担心忘记从列中删除项目。

就内存使用而言,它实际上取决于您存储的实际数据。如果您有测试示例中显示的数据,其中每个“行”在每个“列”中都有一个值,您可能会保存某些内存,因为解释器不需要存储键的名称对于对象中的每个值。然而,如何做到这一点是具体的实施,经过一些研究,我无法确定是否是这种情况。我可以很容易地想象一个聪明的解释器使用查找表来存储共享的密钥名称,在这种情况下,与列解决方案相比,在将数据存储在数组中时,开销几乎可以忽略不计。此外,如果您的数据恰好是稀疏的,I.E。并非每一行都有每列的值,您实际上可以使用 more 内存在列中存储数据。在列方案中,您需要在每一行中为每一行存储一个值,即使它是null或其他空白空间指示符,也要保持对齐。如果将对象存储在数组中,则可以在必要时保留键/值对。如果你可以留下很多键/值对,你可以节省大量的内存。

正如唐纳德克努特所说,“过早优化是万恶之源。”通过将数据存储在这样的列中,您将承担大量额外工作以确保数据一致(这可能导致代码易碎)并且您将使代码更难以阅读,因为人们赢了'期待数据存储就像那样。如果真的,真的需要,你应该只对自己施加这些东西。我的建议是坚持使用数组解决方案中的对象,因为它使您的代码很多更容易读写,并且您实际上不太可能需要保存将由以下内容保存的内存:列解决方案。如果您遇到性能问题,可以重新访问以这种方式存储数据的想法。即便如此,我还是愿意打赌,还有其他更简单的方法让事情变得更快。