Почему eval, это не всегда плохо

от автора

Хочу поделится с вами очень интересным приемом оптимизации в Javascript.

Давайте рассмотрим гипотетическую ситуацию. Допустим у нас есть стек серверов, которые, в любой момент времени, по своей внутренней прихоти, могут быть в состоянии «ok» или «down». Интерфейс сервера позволяет узнать только его имя и текущее состояние. Но как устроен сервер, и откуда он берется нам не известно, у нас нет к нему доступа. Пусть этот код будет конструктором для наших серверов:

var Server = function(name){ 	this.name = name; 	this.ping = function(){return Math.round(Math.random())? 'ok' : 'down';}; };  

Допустим есть компонент, наблюдатель, который будет следить за вверенными ему серверами, и по первому требованию отдавать нам хеш таблицу состояний по всем серверам. А какой-то демон, постоянно, яростно теребит этот компонент, следя за состоянием серверов. Пусть это будет конструктор нашего наблюдателя:

var CasualObserver = function(){ 	var stack = []; 	this.add = function(server){ 		stack.push(server); 		return this; 	}; 	this.check = function(){ 		var hashTable = {}; 		for(var i = 0, ln = stack.length; i < ln; i++){ 			hashTable[stack[i].name] = stack[i].ping(); 		} 		return hashTable; 	} }; 

Допустим вы программист которому поручили этот код, и, вежливо, попросили его оптимизировать, причем сохранив существующий интерфейс.

Ну вот сидите вы, и пялитесь в эти 14 строчек, и единственная (я иронизирую) мысль которая вертится у вас в голове — «стоит ли for на while заменить, или всё-же не стоит?». Мысль эта одновременно правильная и неправильная (эдакая суперпозиция мысли). Правильная в том, что самая трудоёмкая операция тут — цикл. Неправильная в том, что нужно думать не о том как оптимизировать его, а о том как избавится от него. Зачем нам динамически создавать хеш таблицу, когда можно работать с уже готовой, и просто вызывать .check для каждого элемента?

this.check = function(){ 		return { 			stack[0].name : stack[0].ping(), 			stack[1].name : stack[1].ping(), 			stack[2].name : stack[2].ping() 		}; 	} 

Вот так мы сразу же избавляемся от цикла. Конечно вы посмотрите на меня как на дурака, и скажите — «Ага, Вань. Но серверов то у нас может быть произвольное количество, а не просто три. Да и к тому-же в JS нет рефлекшенов.»

А вы знали что в Javascript возможна примитивная рефлексия? (знали? Ну тогда ступайте ниже) Да-да, JS позволяет изменять существующий код и создавать новый в прямо в рантайме!

var sum = new Function('a', 'b', 'return alert(a + b);'); sum(2, 3);  

С тем-же успехом, и для тех же целей, можно использовать всеми ненавистный eval. Подробнее об этом уже писали в других постах на хабре.
Ага, вы неверное уже всё сами поняли? Конечно! Давайте создадим конструктор для нашего наблюдателя, который сам для себя создать метод check:

var SelfModifyObserver = function(){ 	var stack = []; 	this.add = function(server){ 		stack.push(server); 		var code = 'return {'; 		for(var i = 0, ln = stack.length; i < ln; i++){ 			code += stack[i].name + ':' + 'stack[' + i + '].ping(),'; 		} 		code += '};'; 		this.check = eval('(function(){' + code +'});'); 		return this; 	}; 	this.check = function(){return {};} }; 

Выглядит дико, я с вами полностью согласен. Но оно работает!


Давайте проверим, оправдал ли себя этот подход или нет. Напишем небольшую функцию которая добавит наблюдателю 25 серверов, по одному на каждую букву латинского алфавита, а потом посмотрим сколько раз за секунду он сможет сделать проверку по ним.

var benchmark = function(instance, note){ 	for(var i=65; i<=90; i++){ 		instance.add(new Server(String.fromCharCode(i))); 	} 	var stamp = new Date().getTime(); 	var iterations = 0; 	while(new Date().getTime() - stamp <= 1000){ 		instance.check(); 		iterations++; 	} 	console.log(iterations + ' iterations per second for ' + note); 	return true; }; 

Вынужден признать, что сейчас у меня нет возможности протестировать это в Node.js, но тесты в офисе показали что на моем сервере прирост в производительности для подобного решения был порядка 30%.

А это результат в chrome на моем ноуте:

Видите! Выжали 30% практически из пустого места!

Спасибо если вам было интересно.

Идею подхватил в одном из докладов на JSConf EU 2012.

ссылка на оригинал статьи http://habrahabr.ru/post/158403/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *