Сравнение производительности Switch и If…else в JavaScript
Что будем делать ?
Попробуем сравнить разницу в производительности Switch и If-else.
Зачем?
Безумно интересно.
Как?
Напишем функции которая будет генерировать N переходов по списку M вариантов. Условно будем переходить или доходить до нужного элемента и делать break. Для всего это будем использовать Workers JS.
На чем?
Для тестов используем механизм Workers и браузеры поддерживающие этот механизм FF, Chrome, Safari. IE в топку – он в любом случае будем тормозить.
Итак коды исходников:
run.js
// Вывод строки логов
function logmsg(msg) {
var lb = document.getElementById('output')
lb.innerHTML += '> ' + msg + '<br>'
}
// объект описывающий методы нашего теста
var testObj = {
// шаг тестирования
testStep: 50,
// число элементов
elemCnt: 4000,
// количество запусков
runCnt: 5000,
// тип теста (switch/ifelse)
test: 'switch',
// Значение передаваемое тестируемой функцие
runVal: 1,
results: {
// данные о тесте switch
_switch: [],
// данные о тесте if ... else
ifelse: [],
// массив точек тестирования
points: []
}
}
//объявляем Workers
var worker = new Worker('worker.js');
worker.onmessage = function(e) {
var d = e.data
calcPersent((testObj.elemCnt / testObj.testStep + 1) * 2)
// записываем результаты теста
testObj.results[d.test == 'switch' ? '_switch' : 'ifelse'].push(d.time)
testObj.results.points.push(d.runVal)
logmsg('task finish: ' + d.test + d.elemCnt + ' ( ' + d.runVal + ' ) x ' + d.runCnt + ' >> ' + d.time + 'ms')
// запускаем следующий
if (d.runVal == 1) d.runVal = 0
if (d.runVal < d.elemCnt) runTest(d.runVal + testObj.testStep, d.test)
else {
if (d.test == 'switch') {
d.test = 'ifelse'
logmsg('<span>Run If...Else testing ...</span>')
runTest(1, d.test)
} else {
logmsg('<span>Creating chart ...</span>')
drawChart()
}
}
}
// функция постановки задачи тестирования в очередь
function runTest(runVal, test) {
//logmsg('push task: (' + runVal + ', ' + test + ')')
testObj.runVal = runVal
testObj.test = test
worker.postMessage(testObj)
}
// запускаем процессы
logmsg('<span>Run Switch testing ...</span>')
runTest(1, 'switch')
//рисуем график
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('string', 'Call number');
data.addColumn('number', 'Switch');
data.addColumn('number', 'If...Else');
var pointsVals = []
with(testObj.results) {
for (var i = 0; i < points.length / 2; i++) {
pointsVals.push([points[i].toString(), _switch[i], ifelse[i]])
}
}
data.addRows(pointsVals);
var options = {
width: 600,
height: 300,
title: 'Results chart'
};
logmsg('<div id="chartid"></div>')
var chart = new google.visualization.LineChart(document.getElementById('chartid'));
chart.draw(data, options);
}
var currP = false
docTitle = document.title
function calcPersent(fullP) {
currP = currP || 1
document.title = '(' + (currP / fullP * 100).toFixed(2) + '%)' + docTitle
//console.log(title)
currP++
}
worker.js
// создание функции с N элементами case
function createSwitchFun(N) {
var funBody = 'switch (v){'
for (var i = 0; i < N; i++) {
funBody += 'case ' + (i + 1) + ': return v;'
}
funBody += '}'
return Function('v', funBody)
}
// создание функции с N элементами if...else
function createIfElseFun(N) {
var funBody = 'if (v==1){return v}'
for (var i = 1; i < N; i++) {
funBody += 'else if(v==' + (i + 1) + '){return v}'
}
return Function('v', funBody)
}
// функция запускаю функцию fun M раз со значением val и
// возвращает время выполнения в милисекундах
function runFun(M, fun, val) {
var start = new Date();
for (var i = 0; i < M; i++) fun(val)
var end = new Date();
return end - start
}
// обрабатываем сообщения
self.onmessage = function(e) {
var fun = e.data.test == 'switch' ?
createSwitchFun(e.data.elemCnt) :
createIfElseFun(e.data.elemCnt)
e.data.time = runFun(e.data.runCnt, fun, e.data.runVal)
postMessage(e.data);
};
В комментариях все достаточно подробно описано - останавливаться на кодах подробно не буду. А перейду сразу к результатам.
Результаты
Я запускали тесты с шагом в 50 т.е. подавал на поиск значения 1, 50, 100 …. и т.д., при количестве элементов 4000. По каждому значения я производил запуск 10000 раз. И в конце строил графики при помощи Гугла. И вот что получилось.
FF
Самые лучшие показатели на If и просто великолепные на Switch (очень похоже что он преобразует switch в объект)
Safari
Показывает отвратительные показатели для switch и средние для If
Chrome
Все показатели растут примерно одинаково, из этого мы можем сделать вывод, что switch работает как и положено на основе if else
Opera (для оперы пришлось делать фиксы в виде генерации функций на PHP и количестве вариантов 1000 и проводился тест с шагов в 10 и количеством в 100000, так что считать его сопоставим будем условно)
Switch видно что работает также как и в FF, а вот c if время выполнения неприятно растет
Выводы
Операторы которые во многих языках являются одним и тем же - в различных браузерах реализованы по разному и зачастую не являются одним и тем же.
Live можно посмотреть здесь (работает только для FF, Chrome, Safari) http://sandbox.conceptblocks.ru/switch_ifelse_testing/
|
На IE9 тоже надо было сделать тесты, ведь [Цитирую]Безумно интересно.[\Цитирую]. Да и IE9 вполне нормальный браузер
Поддерживаю
Мне вот тоже интересно конечно! НО в IE нету Worker, а использовать эмулятор этого дела совсем не хочется так как это сильно исказит показания!!!!
А почему вы считаете, что switch положено реализовывать как набор if?
Ведь даже компиляторы могут (и часто так и делают) генерить таблицу переходов для switch.
Замечание по поводу реализации функции logmsg: добавлять запись с помощью innerHTML+= плохо, так как если логов собралось много, то эта инструкция всё считывает и переписывает заново. Намного лучше для этого использовать appendChild.