...

понедельник, 30 декабря 2013 г.

[Из песочницы] Оптимизация вызовов функций из воркеров (web-workers)

Приветствую уважаемое Хабросообщество, и в качестве взноса в этот банк коллективного разума

— хочу поделиться своим опытом в работе с воркерами.

Воркеры (Web-workers), это технология, позволяющая запускать изолированные участки кода в отдельном потоке. Код из воркера не тормозит работу графического интерфейса, и выполняется быстрее, чем код на странице, что делает использование воркеров очень привлекательным, для ресурсоёмких расчётов, типа рисования графики или криптографии.


Кто ещё не встречался с этой технологией — здесь можно ознакомится с её основами.


Ниже, описан небольшой «лайфхак», который позволяет уменьшить количество кода, необходимого для вызова функций из воркера, если нужно вызывать больше одной функции.



Обычно, если в нашем воркере всего одна функция, то мы просто пишем:


снаружи(в коде страницы):



var worker = new Worker("myscript.js");
worker.onmessage (event.data){workerСallback(event.data);}
function workerСallback(data){ /*do something with data object;*/}
worker.postMessage({some:"some"});


внутри(в коде воркера):



onmessage = function (event) {postMessage(mySingleFunction(event.data));}


Пока, всё просто и изящно.


Однако, если в воркер добавить ещё одну функцию, вызываемую извне, то количество кода, обеспечивающего вызов этих функций — возрастёт, и он будет выглядеть уже не так изящно:


снаружи:



function firstFunctionСallback(data){ /*do something with data object;*/}

function secondFunctionСallback(data){ }

worker.onmessage (msg){
if(msg.data.callback == "firstFunctionСallback"){
firstFunctionСallback(msg.data.result);
}
if(msg.data.callback == "secondFunctionСallback"){
firstFunctionСallback(msg.data.result);
}
}

worker.postMessage({functionName: "firstFunction", data: data);


внутри:



onmessage = function (event) {
var functionName = event.data.functionName;
if(functionName == "firstFinction"){
postMessage({callback: "firstFunctionСallback", result: firstFinction(event.data.data)});
}
if(functionName == "secondFunction"){
postMessage({callback: "secondFunctionСallback", result: secondFunction(event.data.data)});
}
...
}


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


Чтобы избежать этого — воркер можно обернуть в объект, который будет выполнять эту работу.


Назовём такой объект, соответственно, Performer, и разместим его во внешнем коде:



function Performer(scriptSource) {
var worker = new Worker(scriptSource), callbacks = {}, nextRequestId = 0;
this.perform = function(functionName, params, callback) {
callbacks["request_" + (++nextRequestId)] = callback;
worker.postMessage(
{functionName: functionName, params: params, requestId: nextRequestId}
);
}
worker.onmessage = function(msg) {
callbacks["request_" + msg.data.requestId](msg.data.result);
delete callbacks["request_" + msg.data.requestId];
}
}


Во внутреннем коде воркера — изменим обработчик внешних сообщений:



onmessage = function (event) {
var requestId = event.data.requestId;
var workerFunction = eval(event.data.functionName);
var params = event.data.params;
var result = workerFunction(params);
postMessage({result: result, requestId: requestId});
}


Теперь, можно добавлять в воркер любые функции, и вызывать их извне, без написания вспомогательного кода, а также, использовать анонимные функции в коллбэках:



var performer = new Performer("myscript.js");
performer.perform("firstFunction", {some: "some"}, function(result){console.log("result1="+result);});
performer.perform("secondFunction", {some: "some"}, function(result){console.log("result2="+result);});


Ели воркер размещён не в отдельном файле скрипта, а встроен в страницу,

то в коде перформера должны быть учтены кроссбраузерные различия.


С их учётом, часть перформера, которая отвечает за инициализацию воркера, будет выглядеть так:



function Performer(scriptText) {
var worker = null;
try {// Firefox
var Url = window.webkitURL || window.URL;
worker = new Worker(Url.createObjectURL(new Blob([ scriptText ])));
} catch (browserNotSupportWindowUrl) {
try {// Chrome
worker = new Worker('data:application/javascript,' + encodeURIComponent(scriptText));
} catch (browserNotSupportWorkers) {// Opera
eval(scriptText);
worker = {
postMessage : function(data) {
var workerFunction = eval(data.functionName);
worker.onmessage({
data : {
result : workerFunction(data.params),
requestId : data.requestId
}
});
}
};
}
}
...
}


а создание, соответственно, так:



var performer = new Performer($('#myscript').text());


Таким образом, даже в браузерах не поддерживающих воркеры, код воркера всё равно будет выполняться, просто медленнее.


UPD: благодарю Неизвестного Благодетеля за инвайт).


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий