Browse Source

Improved performance of the table rendering algorithm (square to linear complexity)

MarcinZiabek 3 years ago
parent
commit
f3d4151273
2 changed files with 83 additions and 2 deletions
  1. 46 0
      QuestPDF.Examples/TableBenchmark.cs
  2. 37 2
      QuestPDF/Elements/Table/Table.cs

+ 46 - 0
QuestPDF.Examples/TableBenchmark.cs

@@ -0,0 +1,46 @@
+using System.Linq;
+using NUnit.Framework;
+using QuestPDF.Examples.Engine;
+using QuestPDF.Fluent;
+using QuestPDF.Helpers;
+
+namespace QuestPDF.Examples
+{
+    public class TableBenchmark
+    {
+        [Test]
+        public void Benchmark()
+        {
+            RenderingTest
+                .Create()
+                .ProducePdf()
+                .PageSize(PageSizes.A4)
+                .ShowResults()
+                .MaxPages(20_000)
+                .EnableCaching(true)
+                .EnableDebugging(false)
+                .Render(container =>
+                {
+                    container
+                        .Padding(10)
+                        .MinimalBox()
+                        .Border(1)
+                        .Table(table =>
+                        {
+                            const int numberOfRows = 250_000;
+                            const int numberOfColumns = 10;
+                            
+                            table.ColumnsDefinition(columns =>
+                            {
+                                foreach (var _ in Enumerable.Range(0, numberOfColumns))
+                                    columns.RelativeColumn();
+                            });
+
+                            foreach (var row in Enumerable.Range(0, numberOfRows))
+                            foreach (var column in Enumerable.Range(0, numberOfColumns))
+                                table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
+                        });
+                });
+        }
+    }
+}

+ 37 - 2
QuestPDF/Elements/Table/Table.cs

@@ -19,15 +19,22 @@ namespace QuestPDF.Elements.Table
         private int RowsCount { get; set; }
         private int CurrentRow { get; set; }
         
+        // cache that stores all cells
+        // first index: row number
+        // inner table: list of all cells that ends at the corresponding row
+        private TableCell[][] CellsCache { get; set; }
+        private int MaxRow { get; set; }
+        
         internal override void Initialize(IPageContext pageContext, ICanvas canvas)
         {
             StartingRowsCount = Cells.Select(x => x.Row).DefaultIfEmpty(0).Max();
             RowsCount = Cells.Select(x => x.Row + x.RowSpan - 1).DefaultIfEmpty(0).Max();
             Cells = Cells.OrderBy(x => x.Row).ThenBy(x => x.Column).ToList();
+            BuildCache();
 
             base.Initialize(pageContext, canvas);
         }
-        
+
         internal override IEnumerable<Element?> GetChildren()
         {
             return Cells;
@@ -38,6 +45,31 @@ namespace QuestPDF.Elements.Table
             Cells.ForEach(x => x.IsRendered = false);
             CurrentRow = 1;
         }
+
+        private void BuildCache()
+        {
+            if (CellsCache != null)
+                return;
+
+            if (Cells.Count == 0)
+            {
+                MaxRow = 0;
+                CellsCache = Array.Empty<TableCell[]>();
+                
+                return;
+            }
+            
+            var groups = Cells
+                .GroupBy(x => x.Row + x.RowSpan - 1)
+                .ToDictionary(x => x.Key, x => x.OrderBy(x => x.Column).ToArray());
+
+            MaxRow = groups.Max(x => x.Key);
+
+            CellsCache = Enumerable
+                .Range(0, MaxRow + 1)
+                .Select(x => groups.ContainsKey(x) ? groups[x] : Array.Empty<TableCell>())
+                .ToArray();
+        }
         
         internal override SpacePlan Measure(Size availableSpace)
         {
@@ -143,7 +175,10 @@ namespace QuestPDF.Elements.Table
                 var rowBottomOffsets = new DynamicDictionary<int, float>();
                 var commands = new List<TableCellRenderingCommand>();
                 
-                var cellsToTry = Cells.Where(x => x.Row + x.RowSpan - 1 >= CurrentRow);
+                var cellsToTry = Enumerable
+                    .Range(CurrentRow, MaxRow - CurrentRow + 1)
+                    .SelectMany(x => CellsCache[x]);
+                
                 var currentRow = CurrentRow;
                 var maxRenderingRow = RowsCount;