Measure space allocation

Which keys are expensive in a collection of objects?

Imagine we have a collection of objects with same schema (keys), but various values. If we try to store this collection in the browser's localStorage we quickly hit the size limit (around 5MB). We can see the total size of each key in the localStorage using local-storage-size.js browser console snippet in my code snippets repo.

But which key in the collection takes up more space than others? For example, in this collection, key bar is more expensive to store than foo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var items = [
{
foo: 'foo',
bar: 'barbarbar'
},
{
foo: 'foo',
bar: 'barbar'
},
{
foo: 'foo',
bar: 'bar'
}
];

We can take JSON length as the object's size in memory. Since JavaScript uses UTF-16 each character could take up to 2 bytes, thus I will take JSON.stringify(obj).length * 2 as the byte size of any object. This gives us the first function

1
2
3
4
5
6
7
8
9
function stringSize(str) {
// JavaScript strings are unicode UTF-16 up to 2 bytes per character
return str.length * 2;
}
function objectSize(obj) {
return stringSize(JSON.stringify(obj));
}
console.log('items size', objectSize(items));
// prints "items size 176"

We can find out how expensive individual keys are by going through the collection, picking just the value for each key and measuring it. We also add the length of the key itself for each item. The code to extract values for each key and measure total space taken up the key and its value in the collection is:

1
2
3
4
5
6
7
8
9
10
11
var value = function value(key) {
return function (object) {
return object[key];
};
}
var pickValue = function (key, items) {
return items.map(value(key));
}
function keySize(items, key) {
return stringSize(key) * items.length + objectSize(pickValue(key, items));
}

I use small composable functions in my code because I like functional programming for its simplicity and testability. Finally, we can measure space for each key and zip the result object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function zip(keys, values) {
var result = {};
keys.forEach(function (key, index) {
result[key] = values[index];
});
return result;
}
function propertySizes(keys, items) {
var keyInItemsSize = keySize.bind(null, items);
var sizes = keys.map(keyInItemsSize);
return zip(keys, sizes);
}
var sizes = propertySizes(['foo', 'bar'], items);
console.log(sizes);
// prints { foo: 56, bar: 74 }

You can find the full script expensive-keys.js in the code snippets repo. It has a few extra features, like figuring out list of keys from first object, if no list is provided, and printing sizes in megabytes.

Update: keys-vs-values

I added another script keys-vs-values.js to the code snippets repo. It measures space taken by keys vs space taken by values. For example, object o has keys that are much longer than the values it holds

1
2
3
var o = { foo: 'f', bar: 'b' };
keysVsValues([o])
// prints { keys: 12, values: 4}

So in a list with single object o keys take up 12 bytes, while values only take up 4 bytes.