Как стать автором
Обновить

Оптимизация JSON датасетов (массивов записей)

Время на прочтение 11 мин
Количество просмотров 8.9K
Под датасетом будем понимать массив записей JavaScript:

var dataset = [
  { foo: 'xxx', bar: xxx' },
  { foo: 'yyy', bar: 'yyy' },
  ...
  { foo: 'zzz', bar: 'zzz' }
];


Это обычное JavaScript представление некоторой части таблицы (или выборки) из реляционной базы данных: имена свойств соответствуют именам столбцов, значения свойств — значениям полей записи.



При передаче датасетов между сервером и клиентом их приходится переводить в JSON. Плюсы такого представления в JSON: можно опустить NULL поля (вообще не указывать). Минусы: повторяются названия столбцов, избыточность очень велика.

Решение простое: избавится от повторения названий столбцов.

Идея: вспоминаем, что JSON представление обычного массива содержит только данные.

Решение: сделаем из массива записей запись массивов.


function shrink(array) {
	var r = {};
	for (var i = 0; i < array.length; i++) {
		var rec = array[i];
		for (var name in rec) 
		 if (rec.hasOwnProperty(name)) {
			if (!r.hasOwnProperty(name)) {
				r[name] = new Array(i);
			}
			r[name].push(rec[name]);
		}
	}
	return r;
}



Функция shrink() из массива записей создает одну запись, каждое свойство которой — это массив значений. Небольшая хитрость связана с корректной обработкой пропущенных свойств.

Разумеется, нам понадобится обратная операция:



function unshrink( r ) {

	if (r.constructor == Array) return; // датасет не был сжат
	
	var size = 0; // определяем сколько записей в таблице
	for (var name in r) 
	 if (r.hasOwnProperty(name)) {
		size = Math.max(size, r[name].length);
	}
	
	var array = new Array(size); // создаем массив пустых записей нужного размера
	for (var i = size; i--; ) {
		array[i] = {};
	}
	
	for (var name in r) // переносим данные в записи
	 if (r.hasOwnProperty(name)) {
		var values = r[name];
		for (var i = values.length; i--; ) {
			array[i][name] = values[i];
		}
	}
	return array;
}



Функция unshrink() делает массив записей из записи массивов. Ее хитрость в том, что если она натыкается на массив, то ничего не делает и молча возвращает управление. Таким образом, ее можно сразу встроить в текущую цепочку обработки данных; датасеты в виде массивов будут ходить через нее прозрачно. Как только появится датасет в виде записи массивов, его тут же преобразуют в массив записей.

Давайте попробуем взять типичный датасет типичной задачи типичного IT-проекта и проверим насколько удалось оптимизировать размер.

Возьмем первых 13 человек из списка известных людей, которые погибли от алкоголизма.

en.wikipedia.org/wiki/List_of_deaths_through_alcohol


var people = [
		{ name:'Alan Watts', death_date:'November 16, 1973',location:'Chislehurst, England',cause:'Alcohol poisoning'} // ,age:58}
		,{ name:'John Barrymore', death_date:'29 May 1942',location:'Hollywood, United States',cause:'Various',age:60}
		,{ name:'Brendan Behan', death_date:'20 March 1964',location:'Dublin, Ireland',cause:'Alcoholism',age:41}
		,{ name:'Bix Beiderbecke', death_date:'6 August 1931',location:'New York, United States'} // ,cause:'Pneumonia',age:28}
		,{ name:'George Best', death_date:'25 November 2005',location:'London, United Kingdom',cause:'Alcoholism',age:59}
		,{ name:'John Bonham', death_date:'25 September 1980',location:'Windsor, United Kingdom',cause:'Asphyxiation',age:32}
		,{ name:'Julia Bruns', death_date:'24 December 1927',location:'New York City, United States',cause:'Alcohol poisoning',age:32}
		,{ name:'Rob Buck', death_date:'19 December 2000',location:'Pittsburgh, United States',cause:'Liver failure',age:42}
		,{ name:'Richard Burton', death_date:'5 August 1984',location:'Geneva, Switzerland',cause:'Cerebral haemorrhage',age:58}
		,{ name:'David Byron', death_date:'28 February 1985',location:'Reading, United Kingdom',cause:'Alcoholism',age:38}
		,{ name:'Truman Capote', death_date:'25 August 1984',location:'Los Angeles, United States',cause:'Liver disease',age:59}
		,{ name:'Leonard Chadwick', death_date:'18 May 1940',location:'Boston, United States',cause:'Asphyxiation',age:61}
		,{ name:'Raymond Chandler', death_date:'26 March 1959',location:'La Jolla, United States',cause:'Pneumonia',age:70}
];



Дополним это кодом для прямого и обратного преобразования и вычисления итогов.


function JSON(obj) {
  /* вернуть JSON от obj */
 ...
}

function print(obj) {
	var s = JSON(obj);
	document.write(s + '<HR>' + s.length + ' b.

');
	return s.length;
}

var before = print(people);
var shrinked_people = shrink(people);
var after = print(shrinked_people);

document.write('Выигрыш составил: <b>' + Math.round(100 * (after - before) / before ) + '%</b><HR>');

var unshrinked_people = unshrink(shrinked_people);
print(unshrinked_people);




Получаем:

[ { "name":"Alan Watts" ,"death_date":"November 16, 1973" ,"location":"Chislehurst, England" ,"cause":"Alcohol poisoning" } ,{ "name":"John Barrymore" ,"death_date":"29 May 1942" ,"location":"Hollywood, United States" ,"cause":"Various" ,"age":60 } ,{ "name":"Brendan Behan" ,"death_date":"20 March 1964" ,"location":"Dublin, Ireland" ,"cause":"Alcoholism" ,"age":41 } ,{ "name":"Bix Beiderbecke" ,"death_date":"6 August 1931" ,"location":"New York, United States" } ,{ "name":"George Best" ,"death_date":"25 November 2005" ,"location":"London, United Kingdom" ,"cause":"Alcoholism" ,"age":59 } ,{ "name":"John Bonham" ,"death_date":"25 September 1980" ,"location":"Windsor, United Kingdom" ,"cause":"Asphyxiation" ,"age":32 } ,{ "name":"Julia Bruns" ,"death_date":"24 December 1927" ,"location":"New York City, United States" ,"cause":"Alcohol poisoning" ,"age":32 } ,{ "name":"Rob Buck" ,"death_date":"19 December 2000" ,"location":"Pittsburgh, United States" ,"cause":"Liver failure" ,"age":42 } ,{ "name":"Richard Burton" ,"death_date":"5 August 1984" ,"location":"Geneva, Switzerland" ,"cause":"Cerebral haemorrhage" ,"age":58 } ,{ "name":"David Byron" ,"death_date":"28 February 1985" ,"location":"Reading, United Kingdom" ,"cause":"Alcoholism" ,"age":38 } ,{ "name":"Truman Capote" ,"death_date":"25 August 1984" ,"location":"Los Angeles, United States" ,"cause":"Liver disease" ,"age":59 } ,{ "name":"Leonard Chadwick" ,"death_date":"18 May 1940" ,"location":"Boston, United States" ,"cause":"Asphyxiation" ,"age":61 } ,{ "name":"Raymond Chandler" ,"death_date":"26 March 1959" ,"location":"La Jolla, United States" ,"cause":"Pneumonia" ,"age":70 } ]
1658 b.

{ "name":[ "Alan Watts" ,"John Barrymore" ,"Brendan Behan" ,"Bix Beiderbecke" ,"George Best" ,"John Bonham" ,"Julia Bruns" ,"Rob Buck" ,"Richard Burton" ,"David Byron" ,"Truman Capote" ,"Leonard Chadwick" ,"Raymond Chandler" ] ,"death_date":[ "November 16, 1973" ,"29 May 1942" ,"20 March 1964" ,"6 August 1931" ,"25 November 2005" ,"25 September 1980" ,"24 December 1927" ,"19 December 2000" ,"5 August 1984" ,"28 February 1985" ,"25 August 1984" ,"18 May 1940" ,"26 March 1959" ] ,"location":[ "Chislehurst, England" ,"Hollywood, United States" ,"Dublin, Ireland" ,"New York, United States" ,"London, United Kingdom" ,"Windsor, United Kingdom" ,"New York City, United States" ,"Pittsburgh, United States" ,"Geneva, Switzerland" ,"Reading, United Kingdom" ,"Los Angeles, United States" ,"Boston, United States" ,"La Jolla, United States" ] ,"cause":[ "Alcohol poisoning" ,"Various" ,"Alcoholism" ,"Alcoholism" ,"Asphyxiation" ,"Alcohol poisoning" ,"Liver failure" ,"Cerebral haemorrhage" ,"Alcoholism" ,"Liver disease" ,"Asphyxiation" ,"Pneumonia" ] ,"age":[ undefined ,60 ,41 ,59 ,32 ,32 ,42 ,58 ,38 ,59 ,61 ,70 ] }
1117 b.

Выигрыш составил: -33%
[ { "name":"Alan Watts" ,"death_date":"November 16, 1973" ,"location":"Chislehurst, England" ,"cause":"Alcohol poisoning" ,"age":undefined } ,{ "name":"John Barrymore" ,"death_date":"29 May 1942" ,"location":"Hollywood, United States" ,"cause":"Various" ,"age":60 } ,{ "name":"Brendan Behan" ,"death_date":"20 March 1964" ,"location":"Dublin, Ireland" ,"cause":"Alcoholism" ,"age":41 } ,{ "name":"Bix Beiderbecke" ,"death_date":"6 August 1931" ,"location":"New York, United States" ,"cause":"Alcoholism" ,"age":59 } ,{ "name":"George Best" ,"death_date":"25 November 2005" ,"location":"London, United Kingdom" ,"cause":"Asphyxiation" ,"age":32 } ,{ "name":"John Bonham" ,"death_date":"25 September 1980" ,"location":"Windsor, United Kingdom" ,"cause":"Alcohol poisoning" ,"age":32 } ,{ "name":"Julia Bruns" ,"death_date":"24 December 1927" ,"location":"New York City, United States" ,"cause":"Liver failure" ,"age":42 } ,{ "name":"Rob Buck" ,"death_date":"19 December 2000" ,"location":"Pittsburgh, United States" ,"cause":"Cerebral haemorrhage" ,"age":58 } ,{ "name":"Richard Burton" ,"death_date":"5 August 1984" ,"location":"Geneva, Switzerland" ,"cause":"Alcoholism" ,"age":38 } ,{ "name":"David Byron" ,"death_date":"28 February 1985" ,"location":"Reading, United Kingdom" ,"cause":"Liver disease" ,"age":59 } ,{ "name":"Truman Capote" ,"death_date":"25 August 1984" ,"location":"Los Angeles, United States" ,"cause":"Asphyxiation" ,"age":61 } ,{ "name":"Leonard Chadwick" ,"death_date":"18 May 1940" ,"location":"Boston, United States" ,"cause":"Pneumonia" ,"age":70 } ,{ "name":"Raymond Chandler" ,"death_date":"26 March 1959" ,"location":"La Jolla, United States" } ]
1675 b.


Размер JSON массива записей: 1658 байт.
Размер JSON записи массивов: 1117 байт.
Выиграли треть (33%).

Минусы:
— небольшая потеря производительности;
— плохо подходит для сильно разреженных таблиц (с большим количество NULL полей).

По поводу последнего минуса. NULL ячейки будут представлены строкой «undefined» в JSON представлении записи массивов. Если Ваша функция JSON позволяет задать строку, представляющую «undefined», то возможна дальнейшая оптимизация (замена литеры undefined на один символ, например «U»):


function JSON(obj, undefined) {
 ///
}


var json = JSON(shrink(dataset),"U");
...

var U = undefined;
var array = unshrink(eval('(' + json + ')'));



Засим все.

Update

Скачал gzip, попробовал.

Первое представление упаковалось в 588 байт, оптимизированное упаковалось в 564 байт.

Update

Взял другой пример. Вот пакет из рабочей программы, он содержит массив (в object).


{"message":{"what":"client_context;push","cmd_id":1,"type":"depth","instrument_code":"LKOH ","instrument_subcode":"EQBR","exg":"MICEX"},"object":[ { "buysell":"B","price":1526.7,"qty":47},{ "buysell":"B","price":1526.82,"qty":5},{ "buysell":"B","price":1526.83,"qty":30},{ "buysell":"B","price":1526.86,"qty":50},{ "buysell":"B","price":1526.9,"qty":44},{ "buysell":"B","price":1526.94,"qty":49},{ "buysell":"B","price":1526.97,"qty":11},{ "buysell":"B","price":1527.17,"qty":27},{ "buysell":"B","price":1527.21,"qty":68},{ "buysell":"B","price":1527.39,"qty":16},{ "buysell":"B","price":1527.41,"qty":8},{ "buysell":"B","price":1527.45,"qty":15},{ "buysell":"B","price":1527.59,"qty":46},{ "buysell":"B","price":1527.61,"qty":38},{ "buysell":"B","price":1527.73,"qty":34},{ "buysell":"B","price":1527.75,"qty":20},{ "buysell":"B","price":1527.86,"qty":29},{ "buysell":"B","price":1527.98,"qty":68},{ "buysell":"B","price":1528.03,"qty":32},{ "buysell":"B","price":1553.67,"qty":5},{ "buysell":"S","price":1634.15,"qty":5},{ "buysell":"S","price":1634.23,"qty":48},{ "buysell":"S","price":1634.24,"qty":116},{ "buysell":"S","price":1634.29,"qty":19},{ "buysell":"S","price":1634.39,"qty":22},{ "buysell":"S","price":1634.4,"qty":31},{ "buysell":"S","price":1634.59,"qty":39},{ "buysell":"S","price":1634.64,"qty":70},{ "buysell":"S","price":1634.68,"qty":2},{ "buysell":"S","price":1634.73,"qty":58},{ "buysell":"S","price":1634.8,"qty":35},{ "buysell":"S","price":1634.82,"qty":23},{ "buysell":"S","price":1634.83,"qty":18},{ "buysell":"S","price":1634.92,"qty":7},{ "buysell":"S","price":1635,"qty":49},{ "buysell":"S","price":1635.01,"qty":68},{ "buysell":"S","price":1635.02,"qty":8},{ "buysell":"S","price":1635.06,"qty":41},{ "buysell":"S","price":1635.08,"qty":1},{ "buysell":"S","price":1635.09,"qty":18}]}



Берем 1000 похожих пакетов. И сравниваем два метода.

Повышение скорости генерации JSON: на 40% (т.е. второй метод еще и быстрее)
Снижение размера: на 55%.

Теперь упакуем оба получившихся JSON gzip.

Получаем:

31916 против 8713 байт.

То есть размер gzip уменьшился в 4 раза.

Сомневающиеся тетери пишите e-mail, вышлю вам соотв. файлы.

Теги:
Хабы:
+8
Комментарии 34
Комментарии Комментарии 34

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн