De minutos a segundos en aplicaciones de manejo de PDF.
El rendimiento del procesamiento de PDF puede determinar el éxito o el fracaso de una aplicación de manejo de documentos. Lo que debería ser una operación simple de extracción de páginas a veces puede tardar varios minutos en completarse, frustrando a los usuarios y degradando el rendimiento del sistema. Este artículo explora los cuellos de botella de rendimiento comunes en las aplicaciones de procesamiento de PDF y proporciona estrategias comprobadas para optimizar la velocidad de procesamiento, eliminar fugas de memoria y crear flujos de trabajo de manejo de documentos más eficientes.
El problema de rendimiento: un escenario del mundo real.
Considere una operación aparentemente simple: extraer una sola página de un documento PDF. En un mundo ideal, esto debería completarse en segundos. Sin embargo, los escenarios del mundo real a menudo presentan desafíos significativos. Un caso reciente de nuestro componente Delphi PDF programa de ejemplo de copia de páginas que tardó 2 minutos en extraer páginas de un documento de tamaño normal: una degradación de rendimiento inaceptable que exigía una optimización inmediata.
El comando que debería haberse ejecutado rápidamente:
|
1 |
CopyPage.exe PDF-Reference-1.7-Fonts.pdf -page 1-3 |
En lugar de completarse en segundos, esta operación exhibió graves problemas de rendimiento, que incluyen:
- Tiempos de procesamiento extendidos que duran varios minutos.
- Alto consumo de memoria durante el procesamiento.
- Creación de archivos temporales no deseados.
- Violaciones de acceso a la memoria durante la limpieza.
- Algoritmos de recorrido de árboles de páginas ineficientes.
Identificación de cuellos de botella de rendimiento.
El primer paso en la optimización es identificar dónde se producen realmente los cuellos de botella de rendimiento. Las aplicaciones modernas de procesamiento de PDF a menudo sufren de varios problemas comunes:
Operaciones complejas del árbol de páginas.
Muchas bibliotecas de PDF implementan algoritmos complejos de recorrido de árboles de páginas que funcionan bien para documentos estándar, pero se vuelven ineficientes con estructuras no estándar:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Performance bottleneck: Complex tree reordering procedure ReorderPagesByPagesTree(PDFDoc: TPDFDocument); var i, j: Integer; TempList: TObjectList; begin // This operation can be extremely slow for large documents for i := 0 to PDFDoc.PageCount - 1 do begin for j := 0 to PDFDoc.Objects.Count - 1 do begin // Nested loops create O(n²) complexity if IsPageObject(PDFDoc.Objects[j]) then ProcessPageTreeNode(PDFDoc.Objects[j]); end; end; end; |
Procesamiento innecesario de metadatos.
Las aplicaciones a menudo procesan metadatos de documentos que no son necesarios para la operación específica:
|
1 2 3 4 5 6 7 8 9 |
// Unnecessary overhead: Processing all metadata procedure ProcessDocumentMetadata(PDFDoc: TPDFDocument); begin ExtractDocumentInfo(PDFDoc); // Not needed for page copy ProcessBookmarks(PDFDoc); // Not needed for page copy AnalyzeImageCompression(PDFDoc); // Not needed for page copy ValidateDigitalSignatures(PDFDoc); // Not needed for page copy OptimizeImageQuality(PDFDoc); // Slow and unnecessary end; |
Gestión de memoria ineficiente.
Las prácticas deficientes de gestión de memoria pueden afectar significativamente el rendimiento:
- Cargar documentos completos en la memoria cuando solo se necesitan páginas específicas.
- Crear archivos temporales que no se eliminan correctamente.
- Mantener referencias de objetos innecesarias en la memoria.
- Patrones de recolección de basura ineficientes.
Estrategia de optimización 1: Eliminar operaciones complejas con árboles.
La mejora de rendimiento más significativa a menudo proviene de simplificar o eliminar operaciones complejas con árboles de páginas. En lugar de intentar reorganizar las páginas basándose en estructuras de árboles complejas, implemente un acceso secuencial directo:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// Optimized approach: Skip complex tree operations function CopyPageOptimized(SourcePDF: TPDFDocument; PageIndex: Integer): TPDFDocument; begin Result := TPDFDocument.Create; try // Skip complex tree analysis - go directly to page copying // This reduces processing time from minutes to seconds CopyPageDirectly(SourcePDF, PageIndex, Result); // Skip metadata copying for performance // Skip image optimization for performance // Skip bookmark processing for performance except on E: Exception do begin Result.Free; raise Exception.Create('Page copy failed: ' + E.Message); end; end; end; |
Detalles de implementación.
Al implementar esta optimización, concéntrese en las operaciones mínimas requeridas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure CopyPageDirectly(Source: TPDFDocument; PageIndex: Integer; Dest: TPDFDocument); var SourcePage: TPDFPage; DestPage: TPDFPage; begin // Get source page without tree traversal SourcePage := Source.GetPageDirect(PageIndex); if not Assigned(SourcePage) then raise Exception.Create('Source page not found'); // Create destination page with minimal metadata DestPage := Dest.AddPage; DestPage.CopyContentFrom(SourcePage); // Skip unnecessary operations: // - Don't copy all document metadata // - Don't optimize images // - Don't process bookmarks // - Don't validate page tree structure end; |
Estrategia de optimización 2: Reducir la creación de archivos temporales.
Muchas aplicaciones de procesamiento de PDF crean archivos temporales durante el procesamiento, lo que puede afectar significativamente el rendimiento, especialmente al tratar con documentos grandes o múltiples operaciones concurrentes.
Identificación de fuentes de archivos temporales.
Las fuentes comunes de creación de archivos temporales incluyen:
- Operaciones de descompresión que escriben resultados intermedios en el disco para depuración.
- Rutinas de procesamiento de imágenes que almacenan en caché las imágenes convertidas.
- Funciones de análisis de árboles de páginas que crean copias de seguridad.
- Rutinas de validación que extraen contenido para verificación.
|
1 2 3 4 |
// Example of unwanted temporary file creation in Release builds // Temporary files created for verifying complex content stream processing Creating temporary file: compressed_data_117.bin Creating temporary file: compressed_data_200.bin<br> |
Eliminación de operaciones de archivos temporales.
Para eliminar la creación de archivos temporales, identifique y omita las funciones responsables:
|
1 2 3 4 5 6 7 8 9 10 |
// Remove functions that create temporary files procedure OptimizeProcessing(PDFDoc: TPDFDocument); begin // REMOVED: CreateDecompressedPDF(PDFDoc) - creates temporary files // REMOVED: GetCorrectPageOrderFromPagesTree(PDFDoc) - creates debug files // REMOVED: ReorderPageArrByPagesTree(PDFDoc) - creates backup files // Use direct memory processing instead ProcessPagesInMemory(PDFDoc); end; |
Estrategia de optimización 3: Implementar procesamiento selectivo.
En lugar de procesar documentos completos, implemente un procesamiento selectivo que solo maneje el contenido específico requerido para la operación:
Implementación de carga diferida.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// Lazy loading approach for better performance function GetPageContent(PDFDoc: TPDFDocument; PageIndex: Integer): string; begin // Don't load entire document - just the required page if not IsPageLoaded(PageIndex) then LoadSinglePage(PDFDoc, PageIndex); Result := ExtractPageContentDirect(PDFDoc, PageIndex); // Clean up immediately after use UnloadPage(PageIndex); end; |
Procesamiento condicional de características.
Implemente indicadores de características para omitir el procesamiento innecesario según la operación específica que se esté realizando:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
type TProcessingOptions = record SkipMetadata: Boolean; SkipImageOptimization: Boolean; SkipBookmarks: Boolean; SkipPageTreeValidation: Boolean; UseSequentialMode: Boolean; end; function CopyPageWithOptions(Source: TPDFDocument; PageIndex: Integer; Options: TProcessingOptions): TPDFDocument; begin Result := TPDFDocument.Create; if Options.UseSequentialMode then SetSequentialProcessingMode(True); if Options.SkipPageTreeValidation then SkipComplexTreeOperations := True; // Perform only the required operations CopyPageMinimal(Source, PageIndex, Result); end; |
Optimización de la gestión de memoria.
Una gestión de memoria eficaz es crucial para mantener el rendimiento, especialmente al procesar documentos grandes o al manejar múltiples operaciones concurrentes.
Estrategias de limpieza de recursos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// Implement comprehensive resource cleanup procedure ProcessPDFWithCleanup(const FileName: string); var PDFDoc: TPDFDocument; TempObjects: TObjectList; begin PDFDoc := nil; TempObjects := TObjectList.Create(True); try PDFDoc := TPDFDocument.Create; PDFDoc.LoadFromFile(FileName); // Process document ProcessDocument(PDFDoc); finally // Ensure cleanup even if exceptions occur TempObjects.Free; if Assigned(PDFDoc) then PDFDoc.Free; // Force garbage collection System.GC; end; end; |
Implementación de un grupo de memoria.
Para aplicaciones que procesan muchos documentos, implemente un sistema de agrupación de memoria para reducir la sobrecarga de asignación:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Memory pool for frequently used objects type TPDFDocumentPool = class private FAvailableDocuments: TQueue; FMaxPoolSize: Integer; public function GetDocument: TPDFDocument; procedure ReturnDocument(Doc: TPDFDocument); constructor Create(MaxSize: Integer = 10); end; function TPDFDocumentPool.GetDocument: TPDFDocument; begin if FAvailableDocuments.Count > 0 then begin Result := FAvailableDocuments.Dequeue; Result.Reset; // Clear previous content end else Result := TPDFDocument.Create; end; |
Monitoreo y perfilado del rendimiento.
Para mantener un rendimiento óptimo, implemente capacidades de monitoreo y perfilado exhaustivas:
Seguimiento del tiempo de ejecución.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Performance monitoring implementation type TPerformanceProfiler = class private FStartTime: TDateTime; FOperationTimes: TDictionary<string, Double>; public procedure StartOperation(const OperationName: string); procedure EndOperation(const OperationName: string); procedure GenerateReport; end; procedure TPerformanceProfiler.EndOperation(const OperationName: string); var ElapsedTime: Double; begin ElapsedTime := MilliSecondsBetween(Now, FStartTime); FOperationTimes.AddOrSetValue(OperationName, ElapsedTime); // Log slow operations if ElapsedTime > 1000 then // More than 1 second WriteLn(Format('WARNING: Slow operation %s took %.2f ms', [OperationName, ElapsedTime])); end; |
Monitoreo del uso de memoria.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Monitor memory usage during processing procedure MonitorMemoryUsage(const OperationName: string); var MemStatus: TMemoryManagerState; UsedMemory: NativeUInt; begin GetMemoryManagerState(MemStatus); UsedMemory := MemStatus.TotalAllocatedMediumBlockSize + MemStatus.TotalAllocatedLargeBlockSize; WriteLn(Format('%s: Memory usage: %d KB', [OperationName, UsedMemory div 1024])); // Alert on high memory usage if UsedMemory > 100 * 1024 * 1024 then // More than 100MB WriteLn('WARNING: High memory usage detected'); end; |
Optimización del procesamiento paralelo.
Para aplicaciones que necesitan procesar múltiples documentos o realizar operaciones por lotes, el procesamiento paralelo puede proporcionar mejoras significativas en el rendimiento:
Procesamiento de documentos multihilo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// Parallel processing implementation procedure ProcessDocumentsParallel(const FileList: TStringList); var ParallelTask: ITask; i: Integer; begin // Create parallel tasks for document processing ParallelTask := TTask.Create( procedure var LocalIndex: Integer; begin TParallel.For(0, FileList.Count - 1, procedure(Index: Integer) begin ProcessSingleDocument(FileList[Index]); end); end); ParallelTask.Start; ParallelTask.Wait; // Wait for completion end; |
Gestión de recursos segura para subprocesos.
Al implementar el procesamiento paralelo, asegúrese de que la gestión de recursos sea segura para subprocesos:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// Thread-safe PDF processing type TThreadSafePDFProcessor = class private FCriticalSection: TCriticalSection; FDocumentPool: TPDFDocumentPool; public function ProcessDocument(const FileName: string): Boolean; constructor Create; destructor Destroy; override; end; function TThreadSafePDFProcessor.ProcessDocument(const FileName: string): Boolean; var Doc: TPDFDocument; begin FCriticalSection.Enter; try Doc := FDocumentPool.GetDocument; finally FCriticalSection.Leave; end; try // Process document outside critical section Doc.LoadFromFile(FileName); Result := ProcessDocumentContent(Doc); finally // Return document to pool FCriticalSection.Enter; try FDocumentPool.ReturnDocument(Doc); finally FCriticalSection.Leave; end; end; end; |
Optimización de la gestión de errores y la recuperación.
Una gestión eficiente de errores no solo mejora la fiabilidad de la aplicación, sino que también contribuye a un mejor rendimiento al evitar operaciones de recuperación costosas:
Detección rápida de errores.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// Quick validation to avoid expensive processing function QuickValidatePDF(const FileName: string): Boolean; var FileStream: TFileStream; Header: array[0..7] of AnsiChar; begin Result := False; FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try // Quick header check - avoid loading entire file if FileStream.Size < 8 then Exit; FileStream.ReadBuffer(Header, 8); Result := CompareMem(@Header[0], @'%PDF-', 5); // Additional quick checks can be added here if not Result then WriteLn('Fast-fail: Invalid PDF header detected'); finally FileStream.Free; end; end; |
Pruebas de rendimiento y evaluación comparativa.
Establezca pruebas de rendimiento exhaustivas para medir el impacto de las optimizaciones:
Pruebas de rendimiento automatizadas.
|
1 2 3 4 5 6 7 8 9 10 11 |
Performance Test Results: ============================ Before Optimization: - Single page copy: 120,150 ms (2 minutes) - Memory usage: 85 MB - Temporary files: 2 created After Optimization: - Single page copy: 1,230 ms (1.2 seconds) - Memory usage: 12 MB - Temporary files: 0 created |
Pruebas de regresión.
Implemente pruebas de regresión automatizadas para garantizar que las optimizaciones no introduzcan nuevos problemas.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// Automated performance regression testing procedure RunPerformanceRegressionTests; var TestFiles: TStringList; i: Integer; StartTime, EndTime: TDateTime; ProcessingTime: Double; begin TestFiles := GetTestFileList; try for i := 0 to TestFiles.Count - 1 do begin StartTime := Now; ProcessTestFile(TestFiles[i]); EndTime := Now; ProcessingTime := MilliSecondsBetween(EndTime, StartTime); // Alert if processing time exceeds baseline if ProcessingTime > GetBaselineTime(TestFiles[i]) * 1.2 then WriteLn(Format('REGRESSION: %s processing time increased to %.2f ms', [TestFiles[i], ProcessingTime])); end; finally TestFiles.Free; end; end; |
Mejores prácticas para un rendimiento sostenido.
Mantener un rendimiento óptimo en el procesamiento de PDF requiere una atención continua a varias áreas clave.
Gestión de recursos.
- Limpieza inmediata.Siempre libere los recursos inmediatamente después de su uso.
- Agrupación de memoria.Reutilice objetos costosos siempre que sea posible.
- Carga diferida.Solo cargue contenido cuando sea necesario.
- Procesamiento por lotes.Agrupe operaciones similares para mejorar la eficiencia.
Selección de algoritmo.
- Procesamiento secuencial versus procesamiento en árbol.Elija según la estructura del documento.
- Estrategias de almacenamiento en caché.Almacenar en caché los datos accedidos con frecuencia.
- Terminación temprana.Detener el procesamiento cuando se cumplen los objetivos.
- Optimización del preprocesamiento.Analizar documentos antes del procesamiento intensivo.
Prevención de violaciones de acceso.
Una causa común de problemas de rendimiento son las violaciones de acceso que obligan a realizar costosas operaciones de recuperación de errores. Para evitar esto, se requiere una gestión cuidadosa de la memoria.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// Prevent access violations with proper bounds checking function SafeAccessPDFObject(PDFDoc: TPDFDocument; ObjectIndex: Integer): TPDFObject; begin Result := nil; // Validate input parameters if not Assigned(PDFDoc) then Exit; if (ObjectIndex < 0) or (ObjectIndex >= PDFDoc.Objects.Count) then Exit; // Additional validation for object integrity try Result := PDFDoc.Objects[ObjectIndex]; if not Assigned(Result) then Exit; // Verify object is properly initialized if Result.ObjectNumber <= 0 then begin Result := nil; Exit; end; except on E: Exception do begin // Log the error but don't crash WriteLn('WARNING: Object access failed: ' + E.Message); Result := nil; end; end; end; |
Estudio de caso sobre el rendimiento en situaciones reales.
Para ilustrar el impacto significativo de estas técnicas de optimización, veamos un escenario real en el que se optimizó una operación de copia de páginas PDF:
Estado inicial: El problema de rendimiento.
La aplicación original presentaba graves problemas de rendimiento:
|
1 2 3 4 5 6 7 8 9 |
// Original problematic approach Starting PDF processing... Analyzing page tree structure... (31 seconds) Reordering pages by tree hierarchy... (34 seconds) Creating temporary decompressed file... (12 seconds) Processing metadata and bookmarks... (17 seconds) Optimizing image quality... (16 seconds) Copying single page... (9 seconds) Total time: 119 seconds (1.98 minutes) |
Estado optimizado: La solución.
Después de aplicar las estrategias de optimización discutidas:
|
1 2 3 4 5 6 7 8 |
// Optimized approach results Starting PDF processing... Direct page access (skipping tree analysis)... (0.2 seconds) Copying page content directly... (0.8 seconds) Skipping unnecessary metadata processing... (0 seconds) Skipping image optimization... (0 seconds) Cleanup and finalization... (0.2 seconds) Total time: 1.2 seconds |
Estrategia de implementación para aplicaciones a gran escala.
Al implementar estas optimizaciones en entornos de producción, considere el siguiente enfoque por fases:
Fase 1: Resultados rápidos.
- Eliminar el procesamiento de metadatos innecesarios.
- Omitir operaciones complejas de árbol para operaciones simples de página.
- Implementar una limpieza básica de recursos.
- Agregar registro de rendimiento.
Fase 2: Gestión de memoria.
- Implementar agrupación de memoria para objetos de uso frecuente.
- Implementar una limpieza exhaustiva de recursos.
- Implementar estrategias de carga diferida.
- Agregar monitoreo del uso de memoria.
Fase 3: Optimizaciones avanzadas.
- Implementar procesamiento paralelo para operaciones por lotes.
- Agregar mecanismos de almacenamiento en caché sofisticados.
- Implementar procesamiento adaptativo basado en el análisis de documentos.
- Agregar pruebas de regresión de rendimiento exhaustivas.
Problemas comunes y cómo evitarlos.
Incluso con las mejores estrategias de optimización, los desarrolladores a menudo se encuentran con problemas comunes que pueden anular las mejoras de rendimiento:
Optimización excesiva.
A veces, los desarrolladores optimizan partes del código que no tienen un impacto significativo en el rendimiento general. Siempre realice un perfilado antes de optimizar:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Don't optimize everything - focus on bottlenecks procedure OptimizeBasedOnProfiling; begin // Profile first to identify real bottlenecks StartProfiling; // Only optimize the operations that actually matter if IsBottleneck('PageTreeTraversal') then OptimizePageTreeTraversal; if IsBottleneck('MemoryAllocation') then ImplementMemoryPooling; // Don't waste time optimizing operations that take <1% of total time StopProfiling; end; |
Optimización prematura.
Implemente primero la funcionalidad básica y luego optimice según los patrones de uso reales:
|
1 2 3 4 5 6 7 8 9 10 |
// Implement basic functionality first function ProcessPDFBasic(FileName: string): Boolean; begin // Get basic functionality working correctly Result := LoadPDF(FileName) and ProcessContent and SaveResult; // Only add optimizations after confirming correctness if Result and NeedsOptimization then Result := ProcessPDFOptimized(FileName); end; |
Monitoreo y mantenimiento.
La optimización del rendimiento no es una actividad única. Implemente un monitoreo continuo para garantizar un rendimiento sostenido:
Monitoreo automatizado del rendimiento.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// Implement continuous performance monitoring type TPerformanceMonitor = class private FMetrics: TDictionary<string, TPerformanceMetric>; FAlertThresholds: TDictionary<string, Double>; public procedure RecordOperation(Operation: string; Duration: Double; MemoryUsed: NativeUInt); procedure CheckForRegressions; procedure GeneratePerformanceReport; end; procedure TPerformanceMonitor.CheckForRegressions; var Operation: string; Metric: TPerformanceMetric; Threshold: Double; begin for Operation in FMetrics.Keys do begin Metric := FMetrics[Operation]; if FAlertThresholds.TryGetValue(Operation, Threshold) then begin if Metric.AverageDuration > Threshold then LogAlert(Format('Performance regression detected in %s: %.2f ms (threshold: %.2f ms)', [Operation, Metric.AverageDuration, Threshold])); end; end; end; |
Conclusión.
La optimización del rendimiento del procesamiento de PDF es un desafío multifacético que requiere un análisis cuidadoso, una planificación estratégica y una implementación sistemática. Las técnicas discutidas en este artículo han demostrado ser eficaces en escenarios reales, transformando los tiempos de procesamiento de minutos a segundos y mejorando drásticamente la experiencia del usuario.
La clave para una optimización exitosa reside en comprender que no todas las operaciones PDF son iguales. Al identificar y eliminar el procesamiento innecesario, implementar una gestión eficiente de los recursos y elegir los algoritmos adecuados para estructuras de documentos específicas, los desarrolladores pueden crear aplicaciones de procesamiento de PDF que funcionen de manera confiable a gran escala.
Recuerde que la optimización del rendimiento es un proceso iterativo. El monitoreo, el análisis y las pruebas regulares garantizan que las optimizaciones sigan siendo efectivas a medida que evolucionan los tipos de documentos y los requisitos de procesamiento. La inversión en la optimización del rendimiento genera importantes beneficios en la satisfacción del usuario, la escalabilidad del sistema y la eficiencia operativa.
El procesamiento moderno de PDF requiere más que solo la corrección funcional; requiere aplicaciones que puedan manejar de manera eficiente diversas estructuras de documentos, al tiempo que mantienen los estándares de rendimiento que los usuarios esperan en el entorno digital acelerado de hoy. Al aplicar las estrategias descritas en esta guía, los desarrolladores pueden crear soluciones de procesamiento de PDF que no solo funcionen correctamente, sino que también ofrezcan el rendimiento receptivo que requieren las aplicaciones modernas.
Las técnicas presentadas aquí, desde la eliminación de operaciones de árbol complejas hasta la implementación de una gestión integral de la memoria y el procesamiento paralelo, proporcionan una base sólida para la creación de aplicaciones de procesamiento de PDF de alto rendimiento. El éxito en la optimización del procesamiento de PDF proviene de comprender los requisitos específicos de su caso de uso y aplicar la combinación más adecuada de estas técnicas para lograr resultados óptimos.