Observable o dirty, menos lobos caperucita.

Ni que decir tiene que hacer  Binding con Observable sobre el papel debe de ser más rápido que utilizar Dirty . Pero cuando uno analiza un Binding se debe de preocupar de muchas cosas y la más importante y la que quizá de mejor experiencia de usuario es la carga inicial y por tanto el uso inmediato de nuestra página.

Todos sabemos que existen varios tipos de binding. Aunque en este post solo voy a hacer énfasis sobre OneWay.

1. OneWay: actualiza la propiedad de destino únicamente cuando cambia la propiedad de origen(descripción copiada íntegramente de la página de msdn).

Y porque solamente este, pues sencillo porque voy a hacer medidores de rendimiento de tres frameworks JavaScript y en ningún caso me va a preocupar el rendimiento de TwoWay, por malo que sea, será inapreciable por parte del usuario.

Yo personalmente siempre he utilizado Dirty para hacer Binding y jamás me he preocupado del rendimiento en ese aspecto y también puedo decir que ningún cliente se me ha quejado de esto.

Bueno la verdad es que miento, alguna vez que otra he desarrollado una app con xaml y he utilizado Observable y esa magnífica interfaz llamada INotifyPropertyChanged. Vamos mi amada.

Lo que también digo es que en todos esos desarrollos he terminado por poner un if y la definición de un flag para evitar que se actualizara algo en mi property set, de eso puedo dar fe, sobre todo cuando utilizamos TwoWay.

Dicho esto os voy a decir que tres Framework JavaScript voy a analizar.

Angularjs: Utiliza dirty.

Knockoutjs: Utiliza observable

Durandaljs: Utiliza knockoutjs para los binding y por tanto es observable.

Una cosa que os recomiendo es esta lectura Databinding in angularjs puesto que describe a la perfección el patrón ENGAÑABOBOS (Muestra a los humanos lo que sus ojos pueden ver y despreocúpate del rendimiento,puesto que es implícito cuando aplicas esta técnica ).

Para ver realmente las bondades de observable y dirty os voy a proponer el siguiente supuesto,requisito,etc.

Una empresa nos encarga un software y tenemos que hacer una sencilla página.

Un vendedor está en la calle y se dedica entre otras cosas a la gestión de cobros. Cuando va a un cliente le tienen que aparecer las facturas pendientes de cobro y un botón para cambiar el estado, damos por hecho que las cobra todas,para no complicar el ejemplo que al final es de lo que se trata.

Todas estás pruebas las he hecho simplemente con las herramientas de desarrollo de Google Chrome.

Chrome DevTools Revolutions 2013.

Voy a hacer medidores de tiempo para el siguiente numero de registros.

100. Que alguien te deba 100 facturas  y que lo permitas ya es complicado.

El resto de mediciones las voy a realizar sobre 500,1000,5000 y 10000 simplemente por ver la capacidad de estrés de estos tres Frameworks.

En estos medidores voy a tomar tiempos del Binding,Completed y Program.

Binding: Tiempo en ejecutar el binding y no renderizar, que es lo realmente preocupante.

Completed: La ejecución integra del StackTrace o CallStack.

Program: Aunque no he logrado encontrar documentación al respecto,parece estar ligado a la liberación completa por parte del explorador después de ejecutar Completed y por tanto el momento exacto en el que el usuario puede volver a interactuar con nuestra página. Pero que a diferencia de Binding y Completed que van juntos de la mano puesto que se ejecutan en la misma Pila de llamadas (un usuario pulsa un click y por tanto se ejecuta binding). Este parece ser otro thread que hay que sumar a completed.

Para calcular el binding de cada librería he implementado un sencillo tomador de tiempo y he modificado las librerías originales para insertarlo en el punto donde se inicia el binding y de esta forma comprobar que la herramienta Chrome era fiable.

image

Por cierto alguien ha dicho que no se puede implementar en Prototype Pattern miembros privados, es que me ha parecido haberlo escuchado un montón de veces. ¿Es cierto? Lengua fuera.

Dicho todo esto os voy a mostrar un ejemplo con Angularjs para que veáis como he tomado los tiempos.

image 

Como bien podéis observar por el numero de la consola escrito con mi medidor de tiempos ese 94 coincide con el 93,21 mostrado en el Binding. Para ser exactos hay una diferencia de 0,79 milisegundos que me confirman que estoy tomando bien los tiempos del Binding.

Por resumiros un poco todo si os fijáis creo un nuevo clock antes de llamar a scope.$Apply que es la función que a su ves llama $rootscope.$digest  que ejecuta el famoso DIRTY de Angularjs.

finally

Y porque “Evitar su uso” pues sencillo el propio Chrome la marca como función no optimizada de JavaScript.

Menos mal que Angularjs está desarrollada, apadrinada por el propio Google.

Dejando al margen a Angularjs del que os prometo capítulos muy interesantes a lo largo del 2014, vamos a pasar a ver números de los tres frameworks.

Angularjs

image

Knockoutjs.

image

Durandaljs.

image

Analisis de la información.

Observamos que efectivamente Observable es más rápido que DIRTY pero con unas diferencias muy pequeñas que nunca pasan de los 500 milisegundos en casos extremos.

Pero también podemos ver una cosa muy significativa y es que un Binding no es lo más importante, al final lo verdaderamente costoso es la renderización y es aquí donde Angularjs gana por goleada en cuanto a experiencia de usuario se refiere.

Jamás pasa de los 5 segundos mientras que Knockoutjs y por tanto Durandal superan los 20 segundas en situaciones de estrés extremo, incluso en las pruebas he llegado a ver 38 segundos por parte de Durandaljs. Me temo que por un abuso de promise (Que esto no es c# que es JavaScript).

Que  no es bueno poner muchos.

setTimeout(function(){},1);

Para quien quiera verlo le recomiendo revisar el archivo system.js y dentro de este la función acquire del objecto system.

image

Vamos que deja los task  para c# y piensa que el JavaScript es síncrono excepto en cuatro cositasSonrisa.

Pero hay un dato muy significativo que quizás no habéis observado y es la poca diferencia que hay en el Binding de Knockoutjs y Durandal para el Load y el Update(cambiar el estado de las facturas) y es que los tiempos se parecen, entonces es que estoy poniendo un ejemplo malo para ver la potencia de Observable frente a Dirty. Pues noooooooo.

Lo que pasa es que he tuneado Knockoutjs  para que la carga sea más rápida utilizando Mustachejs, porque sino señores ni con Observable cualquiera de los otros dos frameworks es más rápido en ninguna situación.

Simplemente por llevar la contraria a mi gran amigo @gulnor (Juanma) si hombre el bueno de Koalite. He hecho que los Bindings en Knockoutjs  sean menos VERBOSE Sonrisa gracias a Mustachejs.

Es decir de tener que hacer un curso de ingeniería para hacer un Binding o más bien acordarte por donde vas.

He convertido esto.

image

En esto otro y que funciona en Knockoutjs   gracias a este otro script ko.mustache.js vamos y el tío se pone Copyright XDDDDD. Por implementar estas cuatro líneas.

image

Más agregar esta línea de código.

ko.setTemplateEngine(new ko.mustacheTemplateEngine());

La verdad que es libre de poner Copyright o lo que le apetezca pero el caso es que de lo VERBOSE pasamos a algo más agradable.

image

A costa de evidentemente mejorar el Load de la página y penalizar el único sentido de Observable.

Con lo cual mejoramos el binding gracias Mustachejs y no Knockoutjs  en el Load y lo penalizamos en el Update. Puesto que aquí Knockoutjs pierde la noción del tiempo y ya no sabe que es lo que realmente ha cambiado y vuelve a renderizar todo.

En definitiva que de este análisis sacamos una cosa clara y es que por mucho que me cuenten no es el Binding lo más importante sino la situación completa es decir “desde que el usuario pulsa un boton hasta que obtiene sus resultados”. Y es en esto donde AngularJs gana por goleada.

Con lo cual si utilizamos lo propuesto por Knockoutjs  === Xaml nos vamos de los 4040 milisegundos en la carga a los 8800 utilizando el método no VERBOSE.

Y en el Update===Observable bajamos de los 6718 milisegundos a 248 milisegundos frente a los 265 milisegundos que tarda Angularjs en cambiar el estado de los 10000 registros.

Vamos que ni con esas ganan en situaciones extremas y si a eso le sumo el código que necesito en uno y otro sitio como que me quedo donde estoy y espera a verlas llegar. Es decir me quedo con Angularjs y todos los días mi obligación es estar al tanto de que es lo que mejor y hasta el momento se quien es. Os presento al ganador de la batalla.

image

 

Por último os muestro el código de Angularjs Durandal con el que he hecho las pruebas, aunque si alguno lo necesita que lo pida por twitter y se lo mando.

Angularjs.

image

Durandaljs

image

Madre mía ese ko.observable y ese ko.observableArray hace que entre en éxtasis y desenfreno pensando que llamo a PropertyChanged de c#. Pero el máximo es la siguiente línea para modificar el estado de las facturasSonrisa .

oldvalues[i].state(!oldvalues[i].state());

No os parece más normal esto en Angularjs.

image

que no esto otro en Durandal/Knockoutjs.

image

Que no hemos acabado, que viene lo mejor, que es mi critica personal a ambas librerías(Angular Knockoutjs) por no tener en cuenta lo que los viejos guerreros llamamos fuente de la sabiduría.

Es decir no hacerle caso a este señor, cuando lo que vas a leer y ver está soportado desde el principio de los principios en todos los browser y que no es otro que DOM DocumentFragments.

Vamos para los desarrolladores de componentes entre los que me incluyo el DoubleBuffered de toda la vida.

Construye y lee poquito y  cuando todo esté listo pinta y no lo hagas a pasitos que las pantallas parpadean.

Bueno como uno de los lemas de JavaScript es el Duck typing y el otro es si eres desarrollador de JavaScript eres un Duck(cua,cua y guarro como tu solo). Yo me he convertido en patito y os voy a demostrar como con un código guarro y pensado para lo que es gana y por goleada.

Es decir un lenguaje de Script y que solo se debe de utilizar para esto y en esto es bueno, os voy a demostrar como esos segundos los bajo a milisegundos. Es decir voy a pasar de casi los 20 segundos de Knockoutjs y los 5 segundos de Angularjs. A …. “replique de tambores” ¿Cuantos? 3 segundos, no 820 milisegundos  y lo puedo baja más.

Pintar en el Browser una lista con 10000 elementos.

image

Venga os paso el html y JavaScript a ver si os convierto en patitos, que por cierto bastante guarros son. Y sino regalar a los niños uno por reyes y veréis como acaba el Jardín.

Html .

image

JavaScript.

image

Venga os copio la vista completa, que solo sea copiar y pegar Sonrisa

   1: @{

   2:     ViewBag.Title = "DocumentFragment";

   3: }

   4:  

   5:  

   6: <script id="template" type="text/template">
   1:  

   2:     {{#invoices}}<li><span>{{id}}-{{description}} Dicen que soy lento pero están equivocados,SOY: <strong>{{who}}</strong> | estado : {{state}}</span></li>{{/invoices}}    

</script>

   1:  

   2: <button onclick="render();return false;">Ejecutar como un spa</button>

   3: <button onclick="clearlist(); return false;">Limpiar Lista</button>

   4: <ul id="list">

   5: </ul>

   6:  

   7: @section scripts{

   8:     

   9:     

  10:     

  11:     <script src="~/Scripts/mustache.js">

   1: </script>

   2:     <script>

   3:         var render = function () {

   4:             var fragment, i, templateText = document.getElementById('template').innerText.trim(), clock = util.clock();

   5:             list = document.getElementById('list'),

   6:             createInvoices = function () {

   7:                 var array = [], i, invoiceNumber;

   8:                 for (i = 0; i < util.records; i++) {

   9:                     invoiceNumber = i + 1;

  10:                     array.push({ id: invoiceNumber, description: "Invoice:" + "(" + invoiceNumber + ")", who: 'DocumentCreateFragment', state: false });

  11:                 }

  12:                 return array;

  13:             },

  14:             array = createInvoices(),

  15:             html = Mustache.to_html(templateText, { invoices: array }),

  16:             element = document.createElement('div');

  17:  

  18:             element.innerHTML = html;

  19:             fragment = document.createDocumentFragment();

  20:             for (i = 0; i < element.childNodes.length; i++) {

  21:                 fragment.appendChild(element.childNodes[i]);

  22:             }

  23:  

  24:             list.appendChild(fragment.cloneNode(true));

  25:             console.log(clock.writeTime());

  26:         },

  27:         clearlist = function () {

  28:             list = document.getElementById('list');

  29:             list.innerHTML = "";

  30:         };     

  31:     

</script>

   7:  

   8: }

   9:  

  10:  

Así que hoy hemos visto el patrón CUAC-CUAC.

Es aquel que dice que para pasar de un entorno a otro lo primero es preocuparte de conocer el nuevo y una vez hecho esto no hay otra cosa más sencilla que elegir lo más apropiado, pero si eres desarrollador de c# y piensas que pasar a JavaScript es utilizar TypeScript y los frameworks que más se asemejan a mis conocimientos lo tienes claro, es más no claro la leche que te vas a dar va a ser no pequeña sino muy grande.

Con lo cual musa mía @jersiovic aplícate lo leído y dile al ausente del @pacomonfort que me debe una consultoría que me va a pagar con unos gustoso G….tonic y PAGINA.

Colorín, colorado. Que bien que hemos empezado el Año.

Va por ti “Manolete” que tu si que eras grande.

2 comentarios en “Observable o dirty, menos lobos caperucita.”

  1. Hola Pedro,

    Completamente de acuerdo en que “para pasar de un entorno a otro lo primero es preocuparte de conocer el nuevo”. A veces se tiende a dejar todo en manos de frameworks sin entender realmente cómo funcionan las cosas y eso impide sacarles el máximo partido.

    En cuanto a lo que comentas al final con los document fragments y el double buffering, es similar a la idea que se aplica en React JS y, especialmente, en Om, una adaptación de esa librería para ClojureScript que se basa en aprovechar estructuras de datos inmutables (con lo que el dirty checking es mucho más barato al poder comparar referencias), minimizar el número de cambios al DOM aplicando diff entre el estado actual y el próximo, y empaquetar los cambios en una llamada a requestAnimationFrame.

    Tienes más info en este enlace http://swannodette.github.io/2013/12/17/the-future-of-javascript-mvcs/, donde además realiza una prueba parecida a la tuya comparando Om con Backbone y el resultado es también muy interesante.

    Disclaimer: no tengo ni idea de React ni de Om, igual es completamente inútil, pero me ha parecido interesante 😉

    Saludos,

    Juanma.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *