Article
· Mars 28 54m de lecture

Embedded Python VS ObjectScript - Test de performance à l'aide des fichiers XML

Depuis l'introduction d'Embedded Python, il y a toujours eu un doute sur ses performances par rapport à ObjectScript et J'en ai discuté à plusieurs reprises avec @Guillaume Rongier , eh bien, profitant du fait que je faisais une petite application pour capturer les données des concours publics en Espagne et pouvoir effectuer des recherches en utilisant les capacités de VectorSearch, j'ai vu l'opportunité de réaliser un petit test.

Données pour le test

Les informations relatives aux concours publics sont fournies mensuellement dans des fichiers XML à partir de cette URL  et le format typique des informations d'un concours est le suivant:

 

Spoiler

Comme vous le voyez, chaque concours a des dimensions considérables et dans chaque fichier, nous pouvons trouver environ 450 concours. Cette dimension ne permet pas d'utiliser une classe d'ObjectScript pour le mappage (on pourrait... mais je ne suis pas prêt à le faire).  

Codes pour les tests

Mon idée est de capturer uniquement les champs pertinents pour les recherches ultérieures. Pour ce faire, j'ai créé la classe suivante qui nous servira à stocker les informations capturées:

Class Inquisidor.Object.Licitacion Extends (%Persistent, %XML.Adaptor) [ DdlAllowed ]
{

Property IdLicitacion As %String(MAXLEN = 200);
Property Titulo As %String(MAXLEN = 2000);
Property URL As %String(MAXLEN = 1000);
Property Resumen As %String(MAXLEN = 2000);
Property TituloVectorizado As %Vector(DATATYPE = "DECIMAL", LEN = 384);
Property Contratante As %String(MAXLEN = 2000);
Property URLContratante As %String(MAXLEN = 2000);
Property ValorEstimado As %Numeric(STORAGEDEFAULT = "columnar");
Property ImporteTotal As %Numeric(STORAGEDEFAULT = "columnar");
Property ImporteTotalSinImpuestos As %Numeric(STORAGEDEFAULT = "columnar");
Property FechaAdjudicacion As %Date;
Property Estado As %String;
Property Ganador As %String(MAXLEN = 200);
Property ImporteGanador As %Numeric(STORAGEDEFAULT = "columnar");
Property ImporteGanadorSinImpuestos As %Numeric(STORAGEDEFAULT = "columnar");
Property Clasificacion As %String(MAXLEN = 10);
Property Localizacion As %String(MAXLEN = 200);
Index IndexContratante On Contratante;
Index IndexGanador On Ganador;
Index IndexClasificacion On Clasificacion;
Index IndexLocalizacion On Localizacion;
Index IndexIdLicitation On IdLicitacion [ PrimaryKey ];
}

Pour capturer les données à l'aide d'Embedded Python, j'ai utilisé la bibliothèque  xml.etree.ElementTree  qui nous permet d'extraire les valeurs nœud par nœud. Voici la méthode Python que j'ai utilisée pour le mappage du XML:

Method ReadXML(xmlPath As %String) As %String [ Language = python ]
{
    import xml.etree.ElementTree as ET
    import iris
    import pandas as pd

    try :
        tree = ET.parse(xmlPath)
        root = tree.getroot()
        for entry in root.iter("{http://www.w3.org/2005/Atom}entry"):
            licitacion = {"titulo": "", "resumen": "", "idlicitacion": "", "url": "", "contratante": "", "urlcontratante": "", "estado": "", "valorestimado": "", "importetotal": "", "importetotalsinimpuestos": "", "clasificacion": "", "localizacion": "", "fechaadjudicacion": "", "ganador": "", "importeganadorsinimpuestos": "", "importeganador": ""}
            for tags in entry:
                if tags.tag == "{http://www.w3.org/2005/Atom}title":
                    licitacion["titulo"] = tags.text
                if tags.tag == "{http://www.w3.org/2005/Atom}summary":
                    licitacion["resumen"] = tags.text
                if tags.tag == "{http://www.w3.org/2005/Atom}id":
                    licitacion["idlicitacion"] = tags.text
                if tags.tag == "{http://www.w3.org/2005/Atom}link":
                    licitacion["url"] = tags.attrib["href"]
                if tags.tag == "{urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2}ContractFolderStatus":
                    for detailTags in tags:
                        if detailTags.tag == "{urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2}LocatedContractingParty":
                            for infoContractor in detailTags:
                                if infoContractor.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}Party":
                                    for contractorDetails in infoContractor:
                                        if contractorDetails.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}PartyName" :
                                            for name in contractorDetails:
                                                licitacion["contratante"] = name.text
                                        elif contractorDetails.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}WebsiteURI":
                                            licitacion["urlcontratante"] = contractorDetails.text
                        elif detailTags.tag == "{urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2}ContractFolderStatusCode":
                            licitacion["estado"] = detailTags.text
                        elif detailTags.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}ProcurementProject":
                            for infoProcurement in detailTags:
                                if infoProcurement.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}BudgetAmount":
                                    for detailBudget in infoProcurement:
                                        if detailBudget.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}EstimatedOverallContractAmount":
                                            licitacion["valorestimado"] = detailBudget.text
                                        elif detailBudget.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}TotalAmount":
                                            licitacion["importetotal"] = detailBudget.text
                                        elif detailBudget.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}TaxExclusiveAmount":
                                            licitacion["importetotalsinimpuestos"] = detailBudget.text
                                elif infoProcurement.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}RequiredCommodityClassification":
                                    for detailClassification in infoProcurement:
                                        if detailClassification.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}ItemClassificationCode":
                                            licitacion["clasificacion"] = detailClassification.text
                                elif infoProcurement.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}RealizedLocation":
                                    for detailLocalization in infoProcurement:
                                        if detailLocalization.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}CountrySubentity":
                                            licitacion["localizacion"] = detailLocalization.text
                        elif detailTags.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}TenderResult":
                            for infoResult in detailTags:
                                if infoResult.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}AwardDate":
                                    licitacion["fechaadjudicacion"] = infoResult.text
                                elif infoResult.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}WinningParty":
                                    for detailWinner in infoResult:
                                        if detailWinner.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}PartyName":
                                            for detailName in detailWinner:
                                                if detailName.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}Name":
                                                    licitacion["ganador"] = detailName.text
                                elif infoResult.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}AwardedTenderedProject":
                                    for detailTender in infoResult:
                                        if detailTender.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}LegalMonetaryTotal":
                                            for detailWinnerAmount in detailTender:
                                                if detailWinnerAmount.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}TaxExclusiveAmount":
                                                    licitacion["importeganadorsinimpuestos"] = detailWinnerAmount.text
                                                elif detailWinnerAmount.tag == "{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}PayableAmount":
                                                    licitacion["importeganador"] = detailWinnerAmount.text
            iris.cls("Ens.Util.Log").LogInfo("Inquisidor.BP.XMLToLicitacion", "VectorizePatient", "Terminado mapeo "+licitacion["titulo"])
            if licitacion.get("importeganador") is not None and licitacion.get("importeganador") is not "":
                iris.cls("Ens.Util.Log").LogInfo("Inquisidor.BP.XMLToLicitacion", "VectorizePatient", "Lanzando insert "+licitacion["titulo"])
                stmt = iris.sql.prepare("INSERT INTO INQUISIDOR_Object.Licitacion (Titulo, Resumen, IdLicitacion, URL, Contratante, URLContratante, Estado, ValorEstimado, ImporteTotal, ImporteTotalSinImpuestos, Clasificacion, Localizacion, FechaAdjudicacion, Ganador, ImporteGanadorSinImpuestos, ImporteGanador) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,TO_DATE(?,'YYYY-MM-DD'),?,?,?)")
                try:
                    rs = stmt.execute(licitacion["titulo"], licitacion["resumen"], licitacion["idlicitacion"], licitacion["url"], licitacion["contratante"], licitacion["urlcontratante"], licitacion["estado"], licitacion["valorestimado"], licitacion["importetotal"], licitacion["importetotalsinimpuestos"], licitacion["clasificacion"], licitacion["localizacion"], licitacion["fechaadjudicacion"], licitacion["ganador"], licitacion["importeganadorsinimpuestos"], licitacion["importeganador"])
                except Exception as err:
                    iris.cls("Ens.Util.Log").LogInfo("Inquisidor.BP.XMLToLicitacion", "VectorizePatient", repr(err))
        return "Success"
    except Exception as err:
        iris.cls("Ens.Util.Log").LogInfo("Inquisidor.BP.XMLToLicitacion", "VectorizePatient", repr(err))
        return "Error"
}

Une fois le mappage terminé, nous procédons à un simple insert avec l'enregistrement.

Pour le mappage en utilisant ObjectScript, j'ai utilisé la fonctionnalité %XML.TextReader , voyons la méthode:

Method OnRequest(pRequest As Ens.StreamContainer, Output pResponse As Ens.Response) As %Status
{
    set filename = pRequest.OriginalFilename

    set status=##class(%XML.TextReader).ParseFile(filename,.textreader)
    //check status
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
    set tStatement = ##class(%SQL.Statement).%New()
    //iterate through document, node by node
    while textreader.Read()
    {        

        if ((textreader.NodeType = "element") && (textreader.Depth = 2) && (textreader.Path = "/feed/entry")) {
            if ($DATA(licitacion))
            {                
                if (licitacion.ImporteGanador '= ""){
                    //set sc = licitacion.%Save()
                    set myquery = "INSERT INTO INQUISIDOR_Object.LicitacionOS (Titulo, Resumen, IdLicitacion, URL, Contratante, URLContratante, Estado, ValorEstimado, ImporteTotal, ImporteTotalSinImpuestos, Clasificacion, Localizacion, FechaAdjudicacion, Ganador, ImporteGanadorSinImpuestos, ImporteGanador) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
                    set qStatus = tStatement.%Prepare(myquery)
                    if qStatus '= 1 {
                        write "%Prepare failed:" do $System.Status.DisplayError(qStatus)
                        quit
                    }
                    set rset = tStatement.%Execute(licitacion.Titulo, licitacion.Resumen, licitacion.IdLicitacion, licitacion.URL, licitacion.Contratante, licitacion.URLContratante, licitacion.Estado, licitacion.ValorEstimado, licitacion.ImporteTotal, licitacion.ImporteTotalSinImpuestos, licitacion.Clasificacion, licitacion.Localizacion, licitacion.FechaAdjudicacion, licitacion.Ganador, licitacion.ImporteGanadorSinImpuestos, licitacion.ImporteGanador)
                }                
            }
            set licitacion = ##class(Inquisidor.Object.LicitacionOS).%New()
        }        

        if (textreader.Path = "/feed/entry/title"){
            if (textreader.Value '= ""){
                set licitacion.Titulo = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/summary"){
            if (textreader.Value '= ""){
                set licitacion.Resumen = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/id"){
            if (textreader.Value '= ""){
                set licitacion.IdLicitacion = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/link"){            
            if (textreader.MoveToAttributeName("href")) {
                set licitacion.URL = textreader.Value                
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cbc-place-ext:ContractFolderStatusCode"){
            if (textreader.Value '= ""){
                set licitacion.Estado = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac-place-ext:LocatedContractingParty/cac:Party/cac:PartyName"){
            if (textreader.Value '= ""){
                set licitacion.Contratante = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac-place-ext:LocatedContractingParty/cac:Party/cbc:WebsiteURI"){
            if (textreader.Value '= ""){
                set licitacion.URLContratante = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:BudgetAmount/cbc:EstimatedOverallContractAmount"){
            if (textreader.Value '= ""){
                set licitacion.ValorEstimado = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:BudgetAmount/cbc:TotalAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteTotal = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:BudgetAmount/cbc:TaxExclusiveAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteTotalSinImpuestos = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:RequiredCommodityClassification/cbc:ItemClassificationCode"){
            if (textreader.Value '= ""){
                set licitacion.Clasificacion = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:RealizedLocation/cbc:CountrySubentity"){
            if (textreader.Value '= ""){
                set licitacion.Localizacion = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cbc:AwardDate"){
            if (textreader.Value '= ""){
                set licitacion.FechaAdjudicacion = $System.SQL.Functions.TODATE(textreader.Value,"YYYY-MM-DD")
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cac:WinningParty/cac:PartyName/cbc:Name"){
            if (textreader.Value '= ""){
                set licitacion.Ganador = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cac:AwardedTenderedProject/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteGanadorSinImpuestos = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cac:AwardedTenderedProject/cac:LegalMonetaryTotal/cbc:PayableAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteGanador = textreader.Value
            }
        }       
    }    
    // set resultEmbeddings = ..GenerateEmbeddings()
    Quit $$$OK
}

Les deux codes n'enregistreront dans la base de données que les concours qui ont déjà été résolus (pour lesquels le montant total gagnant a été communiqué).

Configuration de la production

Avec nos méthodes mises en œuvre dans les processus métier correspondants, il ne nous reste plus qu'à configurer la production pour notre test, ce qui nous permettra d'alimenter les deux méthodes. Il suffit d'ajouter deux services métier qui se limiteront à capturer les fichiers avec les informations XML et à les transmettre aux processus métier.

Pour éviter toute interférence lors de la capture et de la transmission des informations aux processus métier, nous allons créer deux services métier. La production se présentera comme suit:

Pour le test, nous allons introduire les concours publics du mois de février, soit un total de 91 fichiers avec 1,30 Go de données. Voyons comment se comportent les deux codes.

À vos marques...

Prêts...

C'est parti!

Résultats du mappage XML en ObjectScript

Commençons par le temps nécessaire au code ObjectScript pour mapper les 91 fichiers:

Le premier fichier a été démarré à 21:11:15, voyons quand le dernier fichier a été mappé:

Si nous regardons les détails du dernier message, nous pouvons voir la date finale du traitement:

L'heure finale est 21:17:43, ce qui donne un temps de traitement de 6 minutes et 28 secondes.

Résultats du mappage XML en Embedded Python

Répétez la même opération avec le processus qui utilise Python:

Début à 21:11:15 comme dans le cas précédent, voyons quand l'opération s'est terminée:

Voyons le message en détail pour connaître la fin exacte:

L'heure finale était 21:12:03, ce qui nous amène à un total de 48 secondes.

Eh bien, le gagnant est connu ! Dans ce round, Embedded Python a battu ObjectScript, du moins en ce qui concerne l'analyse XML. Si vous avez des suggestions ou des améliorations à apporter au code des deux méthodes, je vous encourage à les mettre dans les commentaires et je répéterai les tests pour vérifier les améliorations possibles.

Ce que nous pouvons dire, c'est qu'en ce qui concerne la supériorité évidente de la performance d'ObjectScript par rapport à Python... le mythe est brisé!

myth-busted – Mike Raffety, DTM, PID

Discussion (0)1
Connectez-vous ou inscrivez-vous pour continuer