Ir al contenido principal

Test 1: Multithreading vs Singlethreading


¡¡ Bien !! Después de unos cuantos arreglos, ya tenemos disponible el primer TEST para nuestra plataforma.

Puesto que me gustaría mediante este blog interactuar con las entradas de Stackoverflow, para este primer test he seleccionado este interesante tema: multithreading slower than singlethreading

Y para introducirnos en la filosofía de los TEST, no es mi intención impartir una conferencia sobre este asunto, sino simplemente implementar las casuísticas que nos interesa evaluar, y medir los rendimientos. Vamos a ello.

En primer lugar grabamos en BBDD (a través del recién desarrollado interfaz) el test, y los testcases. La cosa quedaría así:


A nivel programático, debemos definir una clase cuyo nombre se corresponda con el campo "Clase" del Test. Y tantos métodos como "Casos" asociemos al test, haciendo igualmente coincidir los nombres.

En este punto les recuerdo que pueden descargar el proyecto íntegro en EnigmaLABS - GitHub

Para que la comparación de rendimiento de cada una de las técnicas empleadas sea certera, hemos de ejecutar en cada uno de los casos, un mismo problema. Para este caso he pensado en el cálculo de la serie fibonacci, en concreto vamos a calcular 200 elementos de esta serie:

 public static void CalcFibo(int numelements)
        {
            Dictionary<int, Int64> lstelements = new Dictionary<int, Int64>();

            int cont = 0;

            Int64 anterior = 0;
            Int64 anterioranterior = 0;

            while (cont < numelements)
            {
                if (cont == 0)
                {
                    lstelements[cont] = 0;
                    anterioranterior = 0;
                }
                else if (cont == 1)
                {
                    lstelements[cont] = 1;
                    anterior = 1;
                }
                else
                {
                    Int64 newelem = anterioranterior + anterior;

                    anterioranterior = anterior;
                    anterior = newelem;

                    lstelements[cont] = newelem;
                }

                cont++;
            }
        }

Nota: en próximas publicaciones veremos formas más elegantes de implementar este método.

Vamos a desarrollar tres casos:

1. Cálculo simultáneo de la serie. 500 hilos calcularán cada uno 200 elementos de la serie al unísono.
2. Cálculo secuencial: bucle de 500 iteraciones, en cada una de las cuales calcularemos igualmente 200 elementos de la serie.
3. Híbrido: 20 hilos, cada uno de los cuales calculará 25 veces, 200 elementos de la serie.

¿Adivinan cuál será el resultado?

Antes les dejaré el código de cada uno de los casos, y después le daremos al PLAY para ver los resultados.

CASE 1: Multithreading Case

        /// <summary>
        /// Cálculo simultáneo de la serie fibo (500 hilos, 200 elementos por hilo)
        /// </summary>
        /// <param name="_test"></param>
        /// <returns></returns>
        public TestCases MultithreadingCase(TestCases _test)
        {
            //registra inicio
            _test.dtBegin = DateTime.Now;

            SetMsg("- - - - -");
            SetMsg("MultithreadingCase iniciado a las " + _test.dtBegin);

            //500 hilos calculan la serie fibo
            int cont = 0;

            while (cont < 500)
            {
                Thread thfibo = new Thread(() => CalcFibo1(cont));

                _lst_process_control.Add(new objects.process_control()
                {
                    Estado = objects.process_control.enumEstadoProceso.Ejecutando,
                    Hilo = thfibo
                });

                thfibo.Start();

                Thread.Sleep(55);
                cont++;
            }

            while (_lst_process_control.Exists(pc => pc.Estado != objects.process_control.enumEstadoProceso.Finalizado))
            {
                Thread.Sleep(55);
            }

            //registra fin
            _test.dtEnd = DateTime.Now;

            TimeSpan _ts = _test.dtEnd - _test.dtBegin;

            SetMsg("MultithreadingCase finalizado a las " + _test.dtEnd);
            SetMsg("MultithreadingCase ejecutado en " + _ts.TotalMilliseconds + " milisegundos");

            _testobject.TestRecord(_test);

            _lst_process_control.Clear();

            return _test;
        }

        private static void CalcFibo1(int index)
        {
            try
            {
                //Console.WriteLine("CalcFibo1 - Index " + index.ToString());
                functions.fibo.CalcFibo(200);

                _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Finalizado;
            }
            catch (Exception)
            {
                _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Erroneo;
            }
        }

CASE 2: Singlethreading Case

        /// <summary>
        /// Cálculo secuencial de la serie fibo (500 iteraciones, 200 elementos por iteración)
        /// </summary>
        /// <param name="_test"></param>
        /// <returns></returns>
        public TestCases SinglethreadingCase(TestCases _test)
        {
            //registra inicio
            _test.dtBegin = DateTime.Now;

            SetMsg("- - - - -");
            SetMsg("SinglethreadingCase iniciado a las " + _test.dtBegin);

            //500 iteraciones calculando 200 elementos de la serie fibo
            int cont = 0;

            while (cont < 500)
            {
                try
                {
                    functions.fibo.CalcFibo(200);
                    cont++;
                    Thread.Sleep(55);
                }
                catch (Exception)
                {

                }
            }

            //registra fin
            _test.dtEnd = DateTime.Now;

            TimeSpan _ts = _test.dtEnd - _test.dtBegin;

            SetMsg("SinglethreadingCase finalizado a las " + _test.dtEnd);
            SetMsg("SinglethreadingCase ejecutado en " + _ts.TotalMilliseconds + " milisegundos");

            _testobject.TestRecord(_test);

            return _test;
        }

CASE 3: Hybrid Case

        public TestCases HybridCase(TestCases _test)
        {
            //registra inicio
            _test.dtBegin = DateTime.Now;

            SetMsg("- - - - -");
            SetMsg("HybridCase iniciado a las " + _test.dtBegin);

            //20 hilos calculan 25 veces cada uno la serie fibo
            int cont = 0;

            while (cont < 50)
            {
                Thread thfibo = new Thread(() => CalcFibo2(cont));

                _lst_process_control.Add(new objects.process_control()
                {
                    Estado = objects.process_control.enumEstadoProceso.Ejecutando,
                    Hilo = thfibo
                });

                thfibo.Start();

                Thread.Sleep(55);
                cont++;
            }

            while (_lst_process_control.Exists(pc => pc.Estado != objects.process_control.enumEstadoProceso.Finalizado))
            {
                Thread.Sleep(55);
            }

            //registra fin
            _test.dtEnd = DateTime.Now;

            TimeSpan _ts = _test.dtEnd - _test.dtBegin;

            SetMsg("HybridCase finalizado a las " + _test.dtEnd);
            SetMsg("HybridCase ejecutado en " + _ts.TotalMilliseconds + " milisegundos");

            _testobject.TestRecord(_test);

            _lst_process_control.Clear();

            return _test;
        }

        private static void CalcFibo2(int index)
        {
            try
            {
                //Console.WriteLine("CalcFibo1 - Index " + index.ToString());
                int cont = 0;

                while (cont < 25)
                {
                    try
                    {
                        functions.fibo.CalcFibo(200);
                        cont++;
                        Thread.Sleep(55);
                    }
                    catch (Exception)
                    {

                    }
                }

                _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Finalizado;
            }
            catch (Exception)
            {
                _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Erroneo;
            }
        }

Todo listo. Como pueden apreciar la clase va dejando mensajes en una lista que va recogiendo el contenedor para mostrarlos cada N segundos. Estos mensajes dejarán registro del momento de inicio y fin de ejecución de cada caso.

        private void SetMsg(string Msg)
        {
            this.Mensajes.Add(new test_types.mensajes()
            {
                id = Guid.NewGuid(),
                mensaje = Msg,
                leido = false
            });
        }

¿Ejecutamos? ¿Sí? ¡Ay qué nervios! Vamos allá, ¡dale al PLAY!

Aquí tienen los esperados resultados. Saquen Uds. sus propias conclusiones:


Por si los resultados se vieran afectados por lo que la máquina este procesando en el momento de la ejecución conviene ejecutarlo varias veces. Hagas Uds. mismos la prueba. Ya les anticipo que los resultados van a ser siempre similares.

Sí cabe la posibilidad que los resultados se vean afectados por las características de la máquina, sobre todo, del procesador o procesadores y núcleos. Esto sería interesante verlo, pero debido a la cuarentena que padecemos no dispongo de medios suficientes. ¿Quieren compartir sus ejecuciones conmigo? ¡No lo duden! Comenten libremente.

Espero les guste la filosofía de los TEST. ¿Quieren proponerme algún desarrollo que poner a prueba? ¿Quieren desarrollar su propia clase? ¡¡No lo duden!! Estaremos encantados de probar soluciones, y más soluciones.

Hasta la próxima amigos.

Comentarios

  1. off topic
    off language
    off todo

    ..en todo caso les recomiendo a mis queridos lectores que si necesitan servicios de fumigación en un país árabe-parlante indeterminado no duden en contactar con nuestro amigo ahmed

    ResponderEliminar

Publicar un comentario

Entradas populares de este blog

Test 3: ¿Son eficientes los ORMs?

Bienvenidos amigos. Me complace anunciar que por fin estrenamos la categoría " SQL Server Tips ", y lo hacemos por todo lo alto, entrando de lleno en un aspecto altamente polémico entre programadores. ¿Es eficiente un ORM en los accesos a datos? Ya conocen la filosofía de nuestros Tests, no vamos a teorizar demasiado, pero sí una pequeña base va a ser necesaria para conseguir una buena respuesta a nuestra pregunta. He leído un interesante  artículo de nuestros súper amigos de Deloitte ( cuando usar ORM ) argumentando que el uso o no de un ORM hay que decidirlo en relación a la complejidad de nuestro modelo de datos, y al rendimiento que requeriremos en nuestras soluciones, pero, ¿cuándo no deseamos el mejor rendimiento para nuestro software? Lo cierto es que, como ya hemos visto, el ORM facilita mucho las cosas, aporta claridad al código, de eso no cabe duda, pero, ¿es eficiente? He ahí la cuestión. Sobre este asunto vamos a poner a funcionar nuestros apreciados Test. C...

Trazabilidad y control de errores - 2ª parte: trazabilidad estructurada

En esta nueva entrega de la publicación  Trazabilidad y control de errores vamos a centrarnos en el que es quizá el más interesante aspecto de la trazabilidad.  Structured Logging es la técnica que nos permitirá realizar análisis automatizados de nuestra trazabilidad, mediante software de detección de eventos. En la primera parte de la publicación incorporamos a nuestra plataforma de test Open Source la librería NLog , y configuramos la generación de dos ficheros de texto plano para trazas, uno para dejar la información de los posibles errores no controlados, y otro para los avisos, o warnings . Además asociamos la consola para crear trazas de información para la depuración. Hoy vamos a configurar la creación de un tercer fichero, que almacenará igualmente información de los errores, pero en este caso guardará la información no en texto plano, sino estructurada, con notación JSON que después podría ser procesada. Para este fin NLOG nos proporciona el JSON Layout . Vamos a ...

Proyecto GEOW. Implementando el patrón CQRS

Me siento muy feliz de poder ofrecerles esta nueva publicación y este nuevo proyecto Open Source. Naturalmente seguiremos evolucionando el proyecto de tests de c#, pero he querido hacer este paréntesis para hablarles del patrón CQRS , o lo que es lo mismo,  Command Query Responsibility Segregation . Una primera versión plenamente operativa del proyecto GEOW está ya disponible para todos Uds. en  GitHub . Les recomiendo encarecidamente que la clonen y jueguen con ella. No solo tiene un elevado potencial pedagógico , además sus propiedades visuales son altamente hipnóticas, por lo que les pido mucha precaución a la hora de ponerlo a funcionar :)  Por esta misma razón me he decidido a crear un canal en YouTube para irles mostrando videitos con los resultados de los dos proyectos que nos traemos entre manos. Espero lo disfruten. Son muchos los detalles técnicos que se desprenden en esta publicación, además de la propia implementación del patrón...