Comparando precios con un servicio web -- # campo con performance camp codereview Relacionados El problema

Comparing prices with a webservice


1
vote

problema

Español

Trabajar en un proyecto que analiza a través de un documento de Excel para encontrar los cambios de precios de un servicio web que está en su lugar. El código funciona pero es más lento de lo esperado. Al principio, estaba procesando 28 productos un minuto, y ahora después de algún trabajo, lo tengo hasta 80 productos por minuto.

He usado JetBrains Dottrace para determinar el cuello de botella está en esta línea de código

  import tokenize import ast import sys    def handle_token(type, token, (srow, scol), (erow, ecol), line):     # Return the info about the tokens, if it's a NAME token then replace it      if tokenize.tok_name[type] == "NAME":         token = token_names.get(token, token)     return (type, token, (srow, scol), (erow, ecol), line)   def run(assignments="assignments.txt",open_from="peoples.txt"):     with open(assignments, "r") as f:         # Read the replacements into token_names         global token_names         token_names = ast.literal_eval(f.read())      with open(open_from) as source:         # Get the tokenized version of the input, replace it, and untokenize into pretty output         tokens = tokenize.generate_tokens(source.readline)         handled_tokens = (handle_token(*token) for token in tokens)          output = tokenize.untokenize(handled_tokens)      with open(open_from[:-4]+"-output.txt",'w') as outfile:         # Write to the output file         outfile.write(output)      return output   if __name__ == "__main__":     if len(sys.argv) > 1:         if len(sys.argv) > 2:             try:exec run(assignments=sys.argv[1],open_from=sys.argv[2])             except:pass         else:             try:exec run(assignments=sys.argv[1])             except:pass     else:         try:exec run()         except:pass 9  

He utilizado main.py0 Para hacer que las llamadas piensen que fue la forma más rápida.

He hecho algunas investigaciones y no sé si hay una manera de eliminar este cuello de botella o no. Estoy publicando el código a continuación para la revisión del Código General, y también con la esperanza de que haya algo simple que me falta, que puede ayudar a las llamadas API, ir más rápido o más suave.

  main.py1  

Las funciones GetTrustedPreices y UpdatePrices están debajo

  main.py2  
Original en ingles

Working on a project that parses through an excel document to find price changes off of a webservice that is in place. The code works but is slower than expected. At first it was processing 28 products a minute, and now after some work I have it up to 80 products a minute.

I've used Jetbrains dottrace to determine the bottleneck is on this line of code

 xmlstr = wc.DownloadString(@"http://awebsiteservice/prices?SearchString=" + UPC); 

I used System.Net.WebClient to make the calls thinking it was the fastest way.

I've done some research and do not know if there is a way to eliminate this bottleneck or not. I am posting the code below for general code review, and also in hopes that there is something simple I am missing that can help the API calls go faster or smoother.

 private static void PriceDifferences(ExcelWorksheet ws, int maxval)     {         bool looping = true;         int i = 2;         var wc = new WebClient();         XmlDocument xml = new XmlDocument();         string xmlstr;         Console.WriteLine("Press X to save results and stop process");         do         {             if (Console.KeyAvailable)             {                 if (Console.ReadKey(true).Key == ConsoleKey.X)                 {                     looping = false;                 }             }              if (ws.Cells[i, 8].Value != null)             {                                                                                                            if (DateTime.ParseExact(ws.Cells[i, 8].Value.ToString(), "M/dd/yyyy", null) >= DateTime.Now.AddDays(-5).Date)   //If analysis ran in last 5 days skip                 {                     i++;                     continue;                 }             }              var UPC = ws.Cells[i, 1].Value;             var cPrice = ws.Cells[i, 2].Value;              ws.Cells[i, 8].Value = DateTime.Now.Date.ToShortDateString();              //Console.WriteLine("UPC is {0}, Current Price is {1}, and Lowest Price known is {2}", UPC, cPrice, lPrice);             //Console.WriteLine("Checking for price differences");              try             {                 xmlstr = wc.DownloadString(@"http://awebsiteservice/prices?SearchString=" + UPC);             }             catch             {                 continue;             }              xml.LoadXml(xmlstr);              XmlNodeList refList = xml.GetElementsByTagName("Store");             List<string> pricelist = new List<string>();              GetTrustedPrices(refList, pricelist);             UpdatePrices(ws, pricelist, i, cPrice);              i++;             Console.WriteLine(i);          }         while (looping && i < maxval);     } 

The functions GetTrustedPrices and UpdatePrices are below

private static void GetTrustedPrices(XmlNodeList refList, List<string> pricelist)     {         foreach (XmlNode node in refList)         {             if (node.ChildNodes[2].InnerText == "Trusted")             {                 pricelist.Add(node.ChildNodes[4].ChildNodes[2].InnerText);             }         }     }  private static void UpdatePrices(ExcelWorksheet ws, List<string> pricelist, int i, object cPrice)     {         List<double> result = pricelist.Select(x => double.Parse(x)).ToList();          if (result.Count <= 0) return;         result = result.Where(itm => itm >= 1).ToList();         if (result.Count <= 0) return;          var lowest = result.Min();         if (!((double) cPrice > lowest)) return;         ws.Cells[i, 6].Value = lowest;         ws.Cells[i, 7].Value = lowest - (double) cPrice;     } 
     
     
     

Lista de respuestas

2
 
vote
vote
La mejor respuesta
 

Bueno, tienes mucho en marcha, así que lo llevaremos un paso a la vez. Mejorar el rendimiento que necesitará para usar el TPL. Esto debería permitir que las cosas comiencen a procesarse en paralelo. Palabra de advertencia No probé todo este código, ya que no tengo la hoja de Excel o el sitio web, por lo que podría haber errores menores en él

Primero, usted tiene mucho que está pasando por un método, deberíamos romperlos a cada uno, hace una cosa específica. También para facilitar las cosas, vamos a crear una clase para instanciar, para que podamos mantener algún estado.

Vamos a crear un par de pequeñas clases, los anidaría en la clase principal, para sostener algún estado por pasar.

El primero es mantener los ExcelData, nos preocupamos por

  private class ExcelDataSink {     public int ExcelPosition { get; set; }     public string UPC { get; set; }     public double CurrentPrice { get; set; } }   

Esto será mejor para cualquiera que viene después de usted. Como ellos tuve que asumir que Cprice es el precio actual. Aquí es autoexplicativo lo que es.

Siguiente es una clase para mantener los datos de precios. Recibimos el sitio web más la información que necesitamos para actualizar el documento de Excel.

  private class PricingDataSink {     public int ExcelPosition { get; set; }     public IList<double> Prices { get; set; }     public double CurrentPrice { get; set; } }   

Ya que vamos a intentar procesar en paralelo, querremos ponerle un limitador de tarifa. No queremos inundar un sitio web con más de 80 solicitar un segundo. Voy a hacer una constante en la que pueda probar y jugar para ver qué funciona mejor para su situación. También va a cambiar a Httpclient en lugar del código web y el código duro también la URL en una constante.

  public class Pricing {     private readonly HttpClient _httpClient = new HttpClient();     private const string URL = @"http://awebsiteservice/prices?SearchString=";     private const int InFlight= 5; // How many request do we want to process at once   

Necesitamos leer del archivo Excel y obtener datos de uso. Eso debería ser su propio método. Excel es un solo rosco, por lo que no se intenta paralelo a este código

  /// <summary> /// check if the price hasn't been updated in over 5 days and if not reads the current price and upc /// </summary> /// <param name="worksheet">Wrk Sheet to use</param> /// <param name="maxValue">row to end with in Excel Worksheet</param> /// <returns></returns> private IEnumerable<ExcelDataSink> GetExcelData(ExcelWorksheet worksheet, int maxValue) {     for (var i = 2; i < maxValue; i++)     {         if (worksheet.Cells[1, 8].Value == null ||             DateTime.ParseExact(worksheet.Cells[i, 8].Value.ToString(), "M/dd/yyyy", null) <             DateTime.Now.AddDays(-5).Date) //If analysis ran in last 5 days skip         {             yield return new ExcelDataSink()             {                 ExcelPosition = i,                 CurrentPrice = double.Parse(worksheet.Cells[i, 2].Value),                 UPC = worksheet.Cells[i, 1].Value             };         }     } }   

Voy a abandonar la consola como la forma estándar de TPL de cancelar es con una cancelationtoken más Esta clase solo debe preocuparse por lo que debe hacer y no sobre la visualización de la entrada del teclado de progreso o leyendo. Si necesita mostrar progreso, recomendaría aceptar la IProgress Interfaz y usando eso para informar.

Ahora los bloques de flujo de datos de TPL son buenos para hacer una malla para este tipo de cosas, así que voy a usar eso. Hay otras opciones, solo encuentro esto más fácil de entender.

Voy a usar el BufferBlock Para poner los datos que leemos de Excel en. Puede pensar en esto como la cola que posee los datos. Para cargar los datos en el bloque, crearemos otro método y marcaremos el búfer completo cuando terminemos leyendo de Excel

  /// <summary> /// Loads data from Excel into the target Block /// </summary> /// <param name="sourceBlock"></param> /// <param name="workSheet"></param> /// <param name="maxValue"></param> /// <param name="token"></param> /// <returns></returns> private async Task LoadData(ITargetBlock<ExcelDataSink> sourceBlock, ExcelWorksheet workSheet, int maxValue,     CancellationToken token) {     foreach (var sink in GetExcelData(workSheet, maxValue))     {         await sourceBlock.SendAsync(sink, token);     }      sourceBlock.Complete(); }    

Ahora necesitamos cambiar el ExcelDataSink al PRICINGDATASINK y hacer la llamada web

  /// <summary> /// Retreives new pricing from website  /// </summary> /// <param name="excelDataSink"></param> /// <returns></returns> private async Task<PricingDataSink> GetWebPricing(ExcelDataSink excelDataSink) {     var content = await _httpClient.GetStringAsync(URL + excelDataSink.UPC);      var xml = new XmlDocument();     xml.LoadXml(content);      return new PricingDataSink()     {         CurrentPrice = excelDataSink.CurrentPrice,         ExcelPosition = excelDataSink.ExcelPosition,         Prices = GetTrustedPrices(xml).ToList()     }; }  private IEnumerable<double> GetTrustedPrices(XmlDocument xmlDocument) {     return xmlDocument.GetElementsByTagName("Store").Cast<XmlNode>()         .Where(node => node.ChildNodes[2].InnerText == "Trusted")         .Select(node => XmlConvert.ToDouble(node.ChildNodes[4].ChildNodes[2].InnerText)); }   

Ahora necesitamos actualizar los precios en el archivo de Excel.

  /// <summary> /// Update the Excel file with the new lower price /// </summary> /// <param name="pricingSink"></param> /// <param name="worksheet"></param> private static void UpdatePrices(PricingDataSink pricingSink, ExcelWorksheet worksheet) {     var lowest = pricingSink.Prices.Where(p => p >= 1).DefaultIfEmpty().Min();     if (lowest >= 1 && lowest < pricingSink.CurrentPrice)     {         worksheet.Cells[pricingSink.ExcelPosition, 6].Value = lowest;         worksheet.Cells[pricingSink.ExcelPosition, 7].Value = lowest - pricingSink.CurrentPrice;         worksheet.Cells[pricingSink.ExcelPosition, 8].Value = DateTime.Now.Date.ToShortDateString();     } }   

Ahora solo necesitamos alisar todo. Voy a hacer un método llamado ActualComprice para el punto de entrada principal.

  public async Task UpdatePrice(ExcelWorksheet workSheet, int maxValue, CancellationToken token) {     // This acts like our queue     var buffer = new BufferBlock<ExcelDataSink>(new DataflowBlockOptions()     {         BoundedCapacity = InFlight, // put a constraint to not dump all data in at once         CancellationToken = token     });      // marking our consumer of the queue to process one items at a time     var executionOptions = new ExecutionDataflowBlockOptions()     {         BoundedCapacity = 1,         CancellationToken = token     };      // List of task running     var inFlight = new List<Task>();     var linkOption = new DataflowLinkOptions()     {         PropagateCompletion = true     };      // create a consumer for the number we want inflight     for (var i = 0; i < InFlight; i++)     {         // Call our code to transform the Excel into the pricing data         var transform = new TransformBlock<ExcelDataSink, PricingDataSink>(             async e => await GetWebPricing(e), executionOptions);          // Call the code to update the sheet         var updateExcel = new ActionBlock<PricingDataSink>(p => UpdatePrices(p, workSheet), executionOptions);          // wire the mesh together         transform.LinkTo(updateExcel, linkOption);         buffer.LinkTo(transform, linkOption);         inFlight.Add(updateExcel.Completion);     }      // Start the load data task     inFlight.Add(LoadData(buffer, workSheet, maxValue, token));      // Wait for everything to be completed      await Task.WhenAll(inFlight); }   

Lo recomendaría altamente, le recomendaría leer en TPL y los bloques de flujo de datos TPL para entender lo que está sucediendo todo. Para ejecutar el código, deberá crear una cancelación de cancelación y, si desea cancelarla, cancele la fuente del token.

 

Well you got a lot going on so we will take it one step at a time. The improve the performance you will need to use the TPL. This should allow things to start to process in parallel. Word of warning I didn't test all this code as I don't have the Excel sheet or website so there might be minor bugs in it

first you have a lot going on for one method we should break them out to each one does a specific thing. Also to make things easier we going to create a class to instantiate so we can keep some state.

Lets create a couple small classes, I'd nest them in the main class, to hold some state for passing around.

First one is to hold the ExcelData we care about

private class ExcelDataSink {     public int ExcelPosition { get; set; }     public string UPC { get; set; }     public double CurrentPrice { get; set; } } 

This will be better for anyone coming after you. Like them I had to assume cPrice is the current price. Here it's self explanatory what it is.

Next is a class to hold the Pricing Data we get back from the WebSite plus the info we need to update the Excel Document.

private class PricingDataSink {     public int ExcelPosition { get; set; }     public IList<double> Prices { get; set; }     public double CurrentPrice { get; set; } } 

Since we are going to try processing in parallel we will want to put a rate limiter on it. We don't want to flood a website with over 80 request a second. I'm going to make a constant that you can test with and tinker to see what works best for your situation. Also going to switch to the HttpClient instead of the WebClient and hard code the URL in a constant as well.

public class Pricing {     private readonly HttpClient _httpClient = new HttpClient();     private const string URL = @"http://awebsiteservice/prices?SearchString=";     private const int InFlight= 5; // How many request do we want to process at once 

We need to read from the Excel file and get use data. That should be it's own method. Excel is single threaded so no point trying to parallel this code

/// <summary> /// check if the price hasn't been updated in over 5 days and if not reads the current price and upc /// </summary> /// <param name="worksheet">Wrk Sheet to use</param> /// <param name="maxValue">row to end with in Excel Worksheet</param> /// <returns></returns> private IEnumerable<ExcelDataSink> GetExcelData(ExcelWorksheet worksheet, int maxValue) {     for (var i = 2; i < maxValue; i++)     {         if (worksheet.Cells[1, 8].Value == null ||             DateTime.ParseExact(worksheet.Cells[i, 8].Value.ToString(), "M/dd/yyyy", null) <             DateTime.Now.AddDays(-5).Date) //If analysis ran in last 5 days skip         {             yield return new ExcelDataSink()             {                 ExcelPosition = i,                 CurrentPrice = double.Parse(worksheet.Cells[i, 2].Value),                 UPC = worksheet.Cells[i, 1].Value             };         }     } } 

Going to drop out the console stuff as the TPL standard way to cancel is with a CancellationToken Plus this class should only care about what it needs to do and not about displaying progress or reading keyboard input. If you need to show progress I would recommending accepting the IProgress Interface and using that for reporting.

Now the TPL DataFlow Blocks are good at making a mesh for this kind of thing so I'm going to use that. There are other options I just find this easier to understand.

Going to use the BufferBlock to put the data we read from Excel in. You can kind of think of this as the queue that holds the data. To load the data into the Block we will create another method and mark the buffer complete when we are done reading from Excel

/// <summary> /// Loads data from Excel into the target Block /// </summary> /// <param name="sourceBlock"></param> /// <param name="workSheet"></param> /// <param name="maxValue"></param> /// <param name="token"></param> /// <returns></returns> private async Task LoadData(ITargetBlock<ExcelDataSink> sourceBlock, ExcelWorksheet workSheet, int maxValue,     CancellationToken token) {     foreach (var sink in GetExcelData(workSheet, maxValue))     {         await sourceBlock.SendAsync(sink, token);     }      sourceBlock.Complete(); }  

Now we need to change the ExcelDataSink to the PricingDataSink and make the web call

/// <summary> /// Retreives new pricing from website  /// </summary> /// <param name="excelDataSink"></param> /// <returns></returns> private async Task<PricingDataSink> GetWebPricing(ExcelDataSink excelDataSink) {     var content = await _httpClient.GetStringAsync(URL + excelDataSink.UPC);      var xml = new XmlDocument();     xml.LoadXml(content);      return new PricingDataSink()     {         CurrentPrice = excelDataSink.CurrentPrice,         ExcelPosition = excelDataSink.ExcelPosition,         Prices = GetTrustedPrices(xml).ToList()     }; }  private IEnumerable<double> GetTrustedPrices(XmlDocument xmlDocument) {     return xmlDocument.GetElementsByTagName("Store").Cast<XmlNode>()         .Where(node => node.ChildNodes[2].InnerText == "Trusted")         .Select(node => XmlConvert.ToDouble(node.ChildNodes[4].ChildNodes[2].InnerText)); } 

Now we need to update the prices in the Excel File.

/// <summary> /// Update the Excel file with the new lower price /// </summary> /// <param name="pricingSink"></param> /// <param name="worksheet"></param> private static void UpdatePrices(PricingDataSink pricingSink, ExcelWorksheet worksheet) {     var lowest = pricingSink.Prices.Where(p => p >= 1).DefaultIfEmpty().Min();     if (lowest >= 1 && lowest < pricingSink.CurrentPrice)     {         worksheet.Cells[pricingSink.ExcelPosition, 6].Value = lowest;         worksheet.Cells[pricingSink.ExcelPosition, 7].Value = lowest - pricingSink.CurrentPrice;         worksheet.Cells[pricingSink.ExcelPosition, 8].Value = DateTime.Now.Date.ToShortDateString();     } } 

Now we just need to wire everything up. Going to make a method called UpdatePrice for the main entry point.

public async Task UpdatePrice(ExcelWorksheet workSheet, int maxValue, CancellationToken token) {     // This acts like our queue     var buffer = new BufferBlock<ExcelDataSink>(new DataflowBlockOptions()     {         BoundedCapacity = InFlight, // put a constraint to not dump all data in at once         CancellationToken = token     });      // marking our consumer of the queue to process one items at a time     var executionOptions = new ExecutionDataflowBlockOptions()     {         BoundedCapacity = 1,         CancellationToken = token     };      // List of task running     var inFlight = new List<Task>();     var linkOption = new DataflowLinkOptions()     {         PropagateCompletion = true     };      // create a consumer for the number we want inflight     for (var i = 0; i < InFlight; i++)     {         // Call our code to transform the Excel into the pricing data         var transform = new TransformBlock<ExcelDataSink, PricingDataSink>(             async e => await GetWebPricing(e), executionOptions);          // Call the code to update the sheet         var updateExcel = new ActionBlock<PricingDataSink>(p => UpdatePrices(p, workSheet), executionOptions);          // wire the mesh together         transform.LinkTo(updateExcel, linkOption);         buffer.LinkTo(transform, linkOption);         inFlight.Add(updateExcel.Completion);     }      // Start the load data task     inFlight.Add(LoadData(buffer, workSheet, maxValue, token));      // Wait for everything to be completed      await Task.WhenAll(inFlight); } 

I would highly recommend reading up on TPL and the TPL DataFlow blocks to understand what is all happening. To run the code you will need to create a CancellationTokenSource and if you want to cancel it you cancel that the token source.

 
 

Relacionados problema

6  Palindrome más largo en una matriz  ( Longest palindrome in an array ) 
Soy nuevo en la programación, y creo que este código podría mejorarse. ¿Alguna sugerencia? 'done'0 ...

5  Encuentre el próximo número Prime - Control de flujo de los bucles anidados 'para `  ( Find the next prime number flow control of nested for loops ) 
Este código funciona perfectamente, pero me molesta. Tener un bucle etiquetado y anidado Bucle, con un Enumerable<T>.Empty()0 Declaración, y un 9988777665...

2  IMACROS BOT para realizar refrescos  ( Imacros bot for performing refreshes ) 
Estoy tratando de simplificar este código. Parece que todo funciona como debería; Sin embargo, cuando en el bucle de actualización de Imacro, parece un poco i...

1  Compruebe si dos cadenas son permutación entre sí  ( Check if two strings are permutation of each other ) 
private String sort(String word) { char[] content = word.toCharArray(); Arrays.sort(content); return new String(content); } private boolea...

1  Integración de oscilador de fase perturbada  ( Perturbed phase oscillator integration ) 
Estoy integrando un sistema de osciladores de fase perturbados. Defino el sistema de ecuación y también la matriz jacobiana. Tengo que remodelar el vector dim...

35  Demasiados bucles en la aplicación de dibujo  ( Too many loops in drawing app ) 
Tengo un método que tiene muchos bucles: #ifndef __RUNES_STRUCTURES_H #define __RUNES_STRUCTURES_H /* Runes structures. */ struct Game { char board[2...

3  Generador de imágenes de Mandelbrot con iteración paralela  ( Mandelbrot image generator with parallel iteration ) 
Actualmente estoy tratando de optimizar esta clase que tengo para la generación fractal. La ecuación está destinada a ser conectable; He usado z => z*z + c ...

8  Simple GCD Utility en Java  ( Simple gcd utility in java ) 
i anteriormente discutido El rendimiento se refiere a diferentes algoritmos GCD. Escribí una simple clase de Java que implementa el algoritmo binario GCD. E...

4  Simulación simple de red neural en C ++ (Ronda 2)  ( Simple neural network simulation in c round 2 ) 
Intro Ayer He publicado esta pregunta . Desde entonces, he actualizado mi código para incorporar estas sugerencias . También he eliminado la dependencia d...

5  Memoria / Performance of Merge Sort Code  ( Memory performance of merge sort code ) 
Escribí un código de tipo de combinación para un poco de bocadillo nocturno. Lo he puesto trabajando, pero solo estaba mirando a aprender si me faltaba algo e...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos