Probando el rendimiento de LINQ en ASP.Net

LINQ
Después de haber aprendido lo que es LINQ y cómo funciona, es obligado ver si, además de simplificar el código, mejora la eficiencia de nuestro proyecto.

Me he decidido por probar la lectura de archivos XML por parte de LINQ y determinar quien tiene mayor velocidad de lectura, la librería de acceso a XML de .Net o la nueva tecnología: LINQ. Para ello, he creado un XML con 100.000 registros que simula una tabla de alumnos y realizaré consultas a dicho XML.

Éstas son las consultas que he decidido realizar utilizando ambos sistemas:

  • Prueba 1: Extraer todos los alumnos que se llamen Jorge.
  • Prueba 2: Contar cuantos alumnos han aprobado.
  • Prueba 3: De entre los alumnos que se llaman Jorge, ¿cuántos han suspendido?
  • Prueba 4: De entre los alumnos que han sacado más de un 8, ¿cual es el nombre más predominante?
  • Prueba 5: Extraer sólo los nombres distintos de los alumnos y ordenarlos..

Y tras lanzar las mismas aquí están, en segundos, los tiempos que ha tardado cada método en cada una de las consultas anteriores:

Prueba XML Reader LINQ
1 0,301934 0,000036
2 0,589991 0,090168
3 0,390582 0,054944
4 0,432305 0,091441
5 0,281885 0,042886

Se puede apreciar claramente la diferencia en velocidades de acceso, siendo el XML Reader entre 7 y 8 veces más lento que LINQ. Pero además, las consultas en LINQ me han ocupado entre 1 y 3 líneas (y porque he querido dividirlo en líneas) mientras que las realizadas con el antiguo método ocupaban entre 10 y 30 líneas.

Por todo esto, LINQ parece batir al XML Reader tanto en velocidad de acceso como en simplificación de código, lo que reduce errores y costes de producción o mantenimiento.

Sé que existen otros métodos para acceder a un XML en .Net (la librería de XML ofrece otras opciones), pero yo me he decantado por ésta, quizás alguna otra resultase más eficiente, pero tras realizar varias pruebas con LINQ, realmente me ha convencido como tecnología de acceso a conjuntos de datos.

Por último, dejo aquí el código fuente utilizado, por si a alguien le pica la curiosidad o he metido la pata y alguien puede corregirme:

    Private Sub TestVelocidad1()
Dim rutaArchivo As String = "C:/test/xmlAlumnos100.xml"
Dim metodoReader As System.Xml.XmlTextReader
Dim metodoLINQ As System.Xml.Linq.XDocument = System.Xml.Linq.XDocument.Load(rutaArchivo)
Dim lstAlumnos As New Collections.Generic.List(Of Alumno)
Dim lstNombres As New Collections.Generic.List(Of String)
Dim oAlumno As Alumno = Nothing
Dim notaAux As Byte = 0
Dim cantAprobados As Integer = 0
Dim cantSuspendidos As Integer = 0

Page.Trace.IsEnabled = True


'Prueba 1: Extraer todos los alumnos que se llamen Jorge.
Trace.Warn("Empieza la prueba 1")
Dim alumnos = From alum In metodoLINQ.Descendants("alumno") _
Where alum.Attribute("nombre") IsNot Nothing AndAlso alum.Attribute("nombre").Value = "Jorge" _
Select Nombre = alum.Attribute("nombre").Value, Nota = alum.Attribute("nota").Value
Trace.Warn("Fin prueba 1 con LINQ")

metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
oAlumno = New Alumno
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nombre" Then
oAlumno.Nombre = metodoReader.Value
If oAlumno.Nombre <> "Jorge" Then Exit While
ElseIf metodoReader.Name = "nota" Then
Byte.TryParse(metodoReader.Value, oAlumno.Nota)
End If
End While
If oAlumno.Nombre = "Jorge" Then lstAlumnos.Add(oAlumno)
End If
Loop
Trace.Warn("Fin prueba 1 con Reader")


'Prueba 2: Contar cuantos alumnos han aprobado.
Trace.Warn("Empieza la prueba 2")
cantAprobados = (From alum In metodoLINQ.Descendants("alumno") Where alum.Attribute("nota") IsNot Nothing AndAlso Integer.Parse(alum.Attribute("nota").Value) > 4).Count
Trace.Warn("Fin prueba 2 con LINQ")

metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
cantAprobados = 0
Do While metodoReader.Read
If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
notaAux = 0
While metodoReader.MoveToNextAttribute
If metodoReader.Name = "nota" Then
Byte.TryParse(metodoReader.Value, notaAux)
If notaAux > 4 Then cantAprobados += 1
End If
End While
End If
Loop
Trace.Warn("Fin prueba 2 con Reader")


'Prueba 3: De entre los alumnos que se llaman Jorge, ¿cuántos han suspendido?
Trace.Warn("Empieza la prueba 3")
cantSuspendidos = (From alum In metodoLINQ.Descendants("alumno") _
Where alum.Attribute("nombre") IsNot Nothing AndAlso alum.Attribute("nombre").Value = "Jorge" _
Where alum.Attribute("nota") IsNot Nothing AndAlso Integer.Parse(alum.Attribute("nota").Value) < 5).Count
        Trace.Warn("Fin prueba 3 con LINQ")

        metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
        cantSuspendidos = 0
        oAlumno = New Alumno
        Do While metodoReader.Read
            If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
                notaAux = 0 : oAlumno.Nombre = "" : oAlumno.Nota = 0
                While metodoReader.MoveToNextAttribute
                    If metodoReader.Name = "nota" Then Byte.TryParse(metodoReader.Value, oAlumno.Nota)
                    If metodoReader.Name = "nombre" Then oAlumno.Nombre = metodoReader.Value
                End While
                If oAlumno.Nombre = "Jorge" And oAlumno.Nota < 5 Then cantSuspendidos += 1
            End If
        Loop
        Trace.Warn("Fin prueba 3 con Reader")


        'Prueba 4: De entre los alumnos que han sacado más de un 8, ¿cual es el nombre más predominante?
        Dim NombreMasEmpollon As String = ""
        Trace.Warn("Empieza la prueba 4")

        Dim empollon As Generic.List(Of String) = (From n In (From alum In metodoLINQ.Descendants("alumno") _
            Where (alum.Attribute("nota") IsNot Nothing AndAlso Integer.Parse(alum.Attribute("nota").Value) > 8) _
            Group alum By nombre = alum.Attribute("nombre").Value Into cantNombres = Count() _
            Select nombre, cantNombres Order By cantNombres Descending Order By cantNombres Descending Take 1) Select n.nombre).ToList
        NombreMasEmpollon = empollon.Item(0).ToString
        Trace.Warn("Fin prueba 4 con LINQ")

        metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
        Dim klstNombres As New Generic.SortedList(Of String, Integer)
        oAlumno = New Alumno
        Do While metodoReader.Read
            If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
                oAlumno.Nombre = "" : oAlumno.Nota = 0
                While metodoReader.MoveToNextAttribute
                    If metodoReader.Name = "nota" Then Byte.TryParse(metodoReader.Value, oAlumno.Nota)
                    If metodoReader.Name = "nombre" Then oAlumno.Nombre = metodoReader.Value

                    If oAlumno.Nota > 0 And oAlumno.Nombre.Length > 0 Then
                        If oAlumno.Nota > 8 Then
                            If klstNombres.Keys.IndexOf(oAlumno.Nombre) < 0 Then
                                klstNombres.Add(oAlumno.Nombre, 1)
                            Else
                                klstNombres.Item(oAlumno.Nombre) = klstNombres.Item(oAlumno.Nombre) + 1
                            End If
                        End If
                        Exit While
                    End If
                End While
            End If
        Loop
        NombreMasEmpollon = klstNombres.Keys(0)
        For Each n As String In klstNombres.Keys
            If klstNombres(n) > klstNombres(NombreMasEmpollon) Then
                NombreMasEmpollon = n
            End If
        Next
        Trace.Warn("Fin prueba 4 con Reader")


        'Prueba 5: Extraer sólo los nombres distintos de los alumnos y ordenarlos.
        Trace.Warn("Empieza la prueba 5")
        Dim nombres As String() = (From alum In metodoLINQ.Descendants("alumno") Select n = alum.Attribute("nombre").Value Distinct Order By n.ToString).ToArray
        Trace.Warn("Fin prueba 5 con LINQ")

        metodoReader = New System.Xml.XmlTextReader(rutaArchivo)
        lstNombres.Clear()
        Do While metodoReader.Read
            If metodoReader.NodeType = System.Xml.XmlNodeType.Element AndAlso metodoReader.Name = "alumno" AndAlso metodoReader.HasAttributes Then
                While metodoReader.MoveToNextAttribute
                    If metodoReader.Name = "nombre" Then
                        If lstNombres.IndexOf(metodoReader.Value) < 0 Then
                            lstNombres.Add(metodoReader.Value)
                        End If
                        Exit While
                    End If
                End While
            End If
        Loop
        lstNombres.Sort()
        Trace.Warn("Fin prueba 5 con Reader")

    End Sub


Public Class Alumno

#Region "Variables"
    Private _nombre As String
    Private _nota As Byte
#End Region

#Region "Propiedades"
    Public Property Nombre() As String
        Get
            Return _nombre
        End Get
        Set(ByVal value As String)
            _nombre = value
        End Set
    End Property
    Public Property Nota() As Byte
        Get
            Return _nota
        End Get
        Set(ByVal value As Byte)
            _nota = value
        End Set
    End Property
#End Region

End Class

6 thoughts on “Probando el rendimiento de LINQ en ASP.Net”

  1. Buen árticulo pero simplemente… no me lo creo. Has usado algún profiler para ver que noe estás haciendo alguna opearción que sea un cuello de botella?. Hace bastante hice pruebas similares y los resultados fueron bastante diferentes a los tuyos. Ni tanta diferencia… y además más rápido xmlreader.
    Igualmente es interesante debatirlo.

    Salu2

  2. Creo que estás haciendo algo mal en tu código. Para estas comparativas usa un profiler. Yo hice hace bastante tiempo un core y después de muchas pruebas se eligió xmlreader cómo el método de parseo.
    Igualmente para XML pequeños (en general cuando el rendimiento no importa) y por elegancia linq es una pasada.
    Salu2

  3. Buenas Xaos,

    Éste artículo lo escribí hace bastantes años ya y tal vez las cosas hayan cambiado desde entonces, aunque personalmente lo dudo ya que LINQ ha seguido evolucionando bastante y se adapta mejor en mi opinión a los frameworks de la capa de datos etc. además de facilitar el manejo de los datos en colecciones que luego puedes parsear a JSON o XML en el response, ser flexible a la hora de leer los datos desde cache o fuente de datos, etc.

    Aunque el rendimiento es muy importante, también hay que contar con que la tecnología sea “friendly” para que incluso los programadores más noveles no metan bugs o incluso reduzcan el rendimiento, además de que sea algo que se adapte al resto de elementos del proyecto. Es ahí donde LINQ tiene más potencial, ya que puedes hacer maravillas con ella, no es sólo una cuestión de leer documentos XML más rápido.

    Fíjate que los códigos en LINQ son mucho más limpios, eso reduce bugs y termina mejorando la eficiencia en proyectos grandes en que los códigos espagueti tienen más probabilidades de reducir el rendimiento (por ceguera) que la tecnología en sí.

    La verdad es que es posible que hiciera algo mal en esa prueba, pero hace ya mucho de eso e igualmente LINQ es una tecnología mucho más potente que “leer un XML”.

Comments are closed.