юли 24 2011

Game of Life (javascript + html5’s canvas)

Category: Dev,HTML5,WebLucho @ 23:55

Тези дни отново ме загриза съвестта, че не работя по някакъв сайд-проект – мой собствен или open source и за това днес реших да почета и понапиша Game of Life.

Оказа се доста прост алгоритъм с доста интересна история, която и сами може да си прочетете :-)

Демо може да видите тук: http://dailyffs.com/life/

Сорсът е тук: https://gist.github.com/1102964

Коментарът към сорса от страна на @skanev е тук: https://twitter.com/skanev/status/95213748092026880

Това, което искам да кажа в тази статия е, че колкото и да са бързи съвремените Javascript engine-и и колкото и разни светила и икони на браузър вендорите да се хвалят, че производителността скача многократно от версия до версия, това не значи че не може да забързате всичко още малко много. Цената на „забързването“ е „угрозняване“ и оптимизиране на кода. В моя конкретен случай – на една функция, която се вика 60000 пъти на преизчисление.

Въпросната фунцкия:


function getLiveNeighbours(id, data) {
  var x = id % canvas.width;
  var y = parseInt(id / canvas.width);

  var cnt = 0;
  for (var i = -1; i < 2; i++) {
    for (var j = -1; j < 2; j++) {
      if ((i != 0 || j != 0) &&
        !(x+i < 0 || x+i >= canvas.width || y+j < 0 || y+j >= canvas.height) &&
        data.data[4*((y+j)*canvas.width+(x+i))] != THE_BLACK) {
        cnt++;
      }
    }
  }
  return cnt;
}

Простата оптимизация на кода включва:

  1. Премахване на извикването на функцията (тялото на функцията се ползва директно) – спестява 60000 извиквания и времето за изпълнение на една итерация пада от 500ms на 270ms
  2. Заменяне на двата вложени цикъла проверяващи за броя на съседни клетки – от 270ms на 150ms
  3. Опростяване на пресмятанията (премахване на умножения) – от 150ms на 60ms
  4. Създаване на локални референции към често използваните данни (вместо постоянни извиквания от типа „obj.data“ при изчисленията се създава и използва локална променлива) – от 60ms на 50ms
// (4)
var width = canvas.width*4;
var d = data.data;
var nd = newdata.data;
var cw = canvas.width;
var ch = canvas.height;
var base = 0;

for (var i = 0; i < d.length/4; i++) {
  var x = i % cw;
  var y = parseInt(i / cw);

  var cnt = 0;
  // (1), (2), (3)
  if (x > 0 && d[base-4] != THE_BLACK) cnt++;
  if (x < cw-1 && d[base+4] != THE_BLACK) cnt++;
  if (x > 0 && y > 0 && d[base-4-width] != THE_BLACK) cnt++;
  if (y > 0 && d[base-width] != THE_BLACK) cnt++;
  if (x < cw-1 && y > 0 && d[base+4-width] != THE_BLACK) cnt++;
  if (x > 0 && y < ch-1 && d[base-4+width] != THE_BLACK) cnt++;
  if (y < ch-1 && d[base+width] != THE_BLACK) cnt++;
  if (x < cw-1 && y < ch-1 && d[base+4+width] != THE_BLACK) cnt++;
  ...

В крайна сметка производителността скочи 10 пъти, макар логиката на кода да изглежда по-зле от преди. Не, че преди изглеждаше много красива, но все пак :D

Разбира се има и още един доста по-умен начин за техническа оптимизация – Web Workers. Те са панацеята на всички проблеми, началото и края, давид и голиат, содом и гомор, ин и ян… за тях може да почетете тази статия - Mandelbrot + Web Workers

Забележка:

  • става дума за проста техническа оптимизация на кода, а не за подобрения на алгоритъма!
  • вероятно оптимизациите в javascript engine-ите не се фокусират върху случаи на извикване на функция 60 хиляди пъти… а би трябвало.

Етикети: , , ,


юли 14 2011

Upload на blob като файл чрез AJAX

Category: Dev,WebLucho @ 10:10

Заглавието и на мен не ми говори нищо, за това ще се опитам да обясня с прости думи казуса.

Наскоро забелязах, че при изпращането на голям скрийншот в http://screenshoot.me се случват някакви неприятни неща. По-точно грешка 413 Request entity too large. Оказа се, че хостингът ми не позволява изпращане на POST заявки с параметри по-големи от 1-2мб. За сметка на това пък позволява качване на файлове до 20мб. До тук единственото нещо, което ме притесняваше е дали може да „симулирам“ изпращане на файл чрез AJAX. Оказа се че може. FUCK YEAH!

Всъщност може да си префасонирате цялата заявка както ви скимне, което е много готино и същевремено много жалко, защото десетките хиляди фронт-енд дивелъпъри, ползващи jQuery и нямащи понятие от javascript никога няма да узнаят за този факт.

Вероятно бъркам, просто се опитвам да си обясня защо на всяко интервю винаги питат „а ти с jQuery знаеш ли как да работиш“ все едно манюъла е 500 страници и ти трябва висше :).

Та реших да разширя леката библиотечка („парче код“ е правилната дума), която ползвам за AJAX обаждания и съответно резултата е тук – https://github.com/lucho870601/ajax-blob-upload

Накратко:

jx.generateBoundary = function(fieldsData) { // Функция, която генерира уникален разделител валиден за всеки фрагмент
  var boundary = parseInt(Math.random()*Math.pow(10, 16)).toString(36) + '' + parseInt(Math.random()*Math.pow(10, 16)).toString(36);
  for (var i = 0; i < fieldsData.length; i++) {
    if (fieldsData[i].indexOf(boundary) > -1) {
      // generate new boundary and check all fields again
      boundary = parseInt(Math.random()*Math.pow(10, 16)).toString(36) + '' + parseInt(Math.random()*Math.pow(10, 16)).toString(36);
      i = 0;
    }
  }
  return boundary;
};

jx.loadFile = function(url, fileData, fileName, callback, opt) {
  var http = this.init(); //The XMLHttpRequest object is recreated at every call - to defeat Cache problem in IE
  if(!http||!url) return;
  var parts = url.split('?');
  var url = parts[0];
  var parameters = parts[1] ? parts[1].split('&') : [];

  var fieldsData = [fileData];
  for (var i = 0; i < parameters.length; i++) {
    fieldsData.push(parameter[i][1]);
  }

  var boundary = this.generateBoundary(fieldsData);
  var body = '';
  for (var i = 0; i < parameters.length; i++) {
    // строим фрагментите за данни
    var p = parameters[i].split('=');
    body += "--" + boundary + "\r\n\
Content-Disposition: form-data; name='"+p[0]+"'\r\n\
\r\n\
"+(p[1] || '')+"\r\n";
  }

  // фрагмента за псевдо-файла
  body += "--" + boundary + "\r\n\
Content-Disposition: form-data; name='" + fileName + "'; filename='" + fileName + "'\r\n\
Content-Type: application/octet-stream\r\n\
\r\n\
"+ fileData + "\r\n\
--" + boundary + "--\r\n";

  http.open("POST", url, true);
  http.setRequestHeader("Content-Type", "multipart/form-data; boundary="+boundary);
  http.setRequestHeader("Content-Length", body.length);
  http.setRequestHeader("Connection", "close");
  ...
  http.send(body);
};

… и получавате данните си като файл накрая.

Кофтито в цялата история е, че не мога да убедя браузъра ми да не слага „charset“ и вероятно заради това blob-а пристига в съвсем друг вид. За това пък решението е доста лесно – кодиране на бинарните данни в base64. Неприятно е, че request-а ще набъбне с 1/3 повече и ще трябва да отворите и decode-нете файла сами. На теория декодирането на файла трябва да стане автоматично с Content-Transfer-Encoding директива, но на практика не стана :D

Удачно е да ползвате този подход не само при гореописания проблем, но и винаги, когато пращате индустриални количества бинарни данни, защото получаването на файл е по-бързо и ангажира по-малко ресурси отколкото получаването на данните като параметър.  Поне така ми се струва :-P

П.П. Преди да имплементирам алтернативното решение се помъчих да увелича лимита на размера на POST заявките с конфигуриране на .htaccess и ini_set на разни магически PHP параметри, но непотръгна.


Етикети: , ,


юни 13 2011

Mandelbrot + Web Workers

Category: Dev,Firefox,HTML5,WebLucho @ 22:28

Обичам фрактална графика и поради липса на по-смислено занимание тези дни, реших да напиша визуализатор за Mandelbrot. Имплементацията ползва готините Web Workers, за да се справи с тоновете сметки и Canvas за да изобрази резултатът на екрана.

48 web worker-a се борят да запълнят 800х600 пиксела пространство и да си призная се справят по-добре от очакваното… е, все още си е бавно, но е 48 пъти по-добре отколкото без възможност за паралелно изпълнение на алгоритъма :D.

Резултатът може да видите тук – http://dailyffs.com/mandelbrot/

А кодът е достъпен тук – https://gist.github.com/1023445

Забележка: работи само с Firefox, защото другите браузъри или нямат web workers или не поддържат предаването на по-сложни обекти от/към worker-ите.


Етикети: , , ,


май 17 2011

jslinux

Category: Dev,WebLucho @ 16:56

How cool is that…

Не съм сигурен как точно работи това, но очевидно javascript енджините вече са ненормално бързи, за да е възможно емиулирането на операционна система в браузър. Ако още не вярвате на очите си, най-добре го разгледайте сами – http://bellard.org/jslinux/

Let the time of browser-based VMs begin!


Етикети: , ,


май 14 2011

Learn to love Javascript

Category: Dev,WebLucho @ 13:43

Дойде това време от годината, когато човек може да се наслади на изобилие от качествени презентации, благодарение на Google IO. Първото видео, което изгледах беше от Alex Russell, който говори за бъдещия стандарт на Javascript – Harmony. Harmony ще направи опит да очовечи езика, чрез оператори и похвати, които са дефакто-стандарт на повечето функционалните скриптови езици. Все пак не става дума за кардинална промяна, а за „захаросване“ на синтаксиса, така че кодът написан за предни версии на езика да си е напълно валиден.

Както и да е, това не е най-интересното от презентацията. Най-интересното от нея е едно изречение, което характеризира перфектно Javascript и очертава основното различие между този език и останалите скриптови езици:

You don’t create classes that are state with behaivour attached,
you create behaivour that holds the state that it needs.

Enjoy :)


Етикети: ,


яну 11 2011

Ontwik

Category: Dev,HTML5,WebLucho @ 12:38

Ако сте мързелив програмист и не обичате да четете книги или пък харесвате dev конференции и искате да слушате лекции на любимите си гурута, то на драго сърце ще ви препоръчам Ontwik.

Ontwik е сайт, който събира в себе си записи от различни конференции на web тематика и дава възможност на всеки да ги гледа… безплатно. Темите, които следя са предимно Javascript, Ruby, Rails, Python, Django, Git, HTML5, но има още много други. Голямата част от лекциите са наистина качествени и интересни, като има записи на web гурута като David Hansson – създателят на Rails,  Tom Werner – един от създателите на Github, Линус Торвалдс, Brendan Eich – създателят на Javascript, и още много други.

Оставям ви с една много готина презентация на David Hansson:

„Why Ruby?“ – RubyConf X Keynote from David Heinemeier Hansson on Vimeo.


Етикети: , , , , ,


ное 09 2010

Бързо, лесно, вкусно – jsFiddle

Category: Dev,WebLucho @ 16:08

Понякога ми се налага да напиша някое малко парче код html с javascript за прототип или демо. В общия случай това значи, че трябва да създам нов файл, в който да пиша, да отворя редактор, да отворя браузър и милиони натискания на alt+tab в процеса на писане. Въобще досада отвсякъде!

За щастие наскоро попаднах на един много интересен онлайн туул, който предлага цялата тая функционалност наготово. Туулчето се казва jsFiddle и освен, че може да пишете html, css, javascript код директно в браузъра и да виждате резултата своевременно, можете да ползвате и някоя от популярните javascript библиотеки наготово и накрая да споделите готовото парче код в Twitter.

Nice, a?! ;-)


Етикети: , , , ,


Следваща страница »