Compare commits

...

1 Commits

Author SHA1 Message Date
MarcinZiabek 4743768e23 Implemented simple object cache, and updated some fluent api invocations 2022-09-12 17:09:07 +02:00
17 changed files with 238 additions and 64 deletions
+2 -2
View File
@@ -65,9 +65,9 @@ namespace QuestPDF.Examples.Engine
return this; return this;
} }
public RenderingTest ShowResults() public RenderingTest ShowResults(bool value = true)
{ {
ShowResult = true; ShowResult = value;
return this; return this;
} }
+76 -30
View File
@@ -1,8 +1,13 @@
using System.Linq; using System;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using QuestPDF.Drawing;
using QuestPDF.Elements;
using QuestPDF.Examples.Engine; using QuestPDF.Examples.Engine;
using QuestPDF.Fluent; using QuestPDF.Fluent;
using QuestPDF.Helpers; using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
namespace QuestPDF.Examples namespace QuestPDF.Examples
{ {
@@ -11,36 +16,77 @@ namespace QuestPDF.Examples
[Test] [Test]
public void Benchmark() public void Benchmark()
{ {
RenderingTest GenerateAndCollect();
.Create()
.ProducePdf()
.PageSize(PageSizes.A4)
.ShowResults()
.MaxPages(10_000)
.EnableCaching(true)
.EnableDebugging(false)
.Render(container =>
{
container
.Padding(10)
.MinimalBox()
.Border(1)
.Table(table =>
{
const int numberOfRows = 100_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)) var stopwatch = new Stopwatch();
foreach (var column in Enumerable.Range(0, numberOfColumns)) stopwatch.Start();
table.Cell().Background(Placeholders.BackgroundColor()).Padding(5).Text($"{row}_{column}");
}); foreach (var _ in Enumerable.Range(0, 10000))
}); {
GenerateAndCollect();
}
stopwatch.Stop();
Console.WriteLine($"Execution time: {stopwatch.Elapsed:g}");
void GenerateAndCollect()
{
var container = new Container();
container
.Padding(10)
.MinimalBox()
.Border(1)
.Column(column =>
{
const int numberOfRows = 100;
const int numberOfColumns = 10;
for (var y = 0; y < numberOfRows; y++)
{
column.Item().Row(row =>
{
for (var x = 0; x < numberOfColumns; x++)
{
row.RelativeItem()
.Background(Colors.Red.Lighten5)
.Padding(3)
.Background(Colors.Red.Lighten4)
.Padding(3)
.Background(Colors.Red.Lighten3)
.Padding(3)
.Background(Colors.Red.Lighten2)
.Padding(3)
.Background(Colors.Red.Lighten1)
.Padding(3)
.Background(Colors.Red.Medium)
.Padding(3)
.Background(Colors.Red.Darken1)
.Padding(3)
.Background(Colors.Red.Darken2)
.Padding(3)
.Background(Colors.Red.Darken3)
.Padding(3)
.Background(Colors.Red.Darken4)
.Height(3);
}
});
}
});
ElementCacheManager.Collect(container);
}
} }
} }
} }
+2
View File
@@ -74,6 +74,8 @@ namespace QuestPDF.Drawing
var pageContext = new PageContext(); var pageContext = new PageContext();
RenderPass(pageContext, new FreeCanvas(), content, debuggingState); RenderPass(pageContext, new FreeCanvas(), content, debuggingState);
RenderPass(pageContext, canvas, content, debuggingState); RenderPass(pageContext, canvas, content, debuggingState);
ElementCacheManager.Collect(content);
} }
internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState) internal static void RenderPass<TCanvas>(PageContext pageContext, TCanvas canvas, Container content, DebuggingState? debuggingState)
+76
View File
@@ -0,0 +1,76 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using QuestPDF.Infrastructure;
namespace QuestPDF.Drawing
{
internal class CircularBuffer
{
private const int BufferSize = 11_000;
private object[] Buffer = new object[BufferSize];
private int WriteIndex { get; set; } = 0;
private int ReadIndex { get; set; } = 0;
public T Get<T>() where T : class, new()
{
lock (this)
{
if (ReadIndex == WriteIndex)
return new T();
var index = ReadIndex;
ReadIndex = (ReadIndex + 1) % BufferSize;
var result = Buffer[index] as T;
Buffer[index] = null;
return result;
}
}
public void Store(object value)
{
lock (this)
{
Buffer[WriteIndex] = value;
WriteIndex = (WriteIndex + 1) % BufferSize;
}
}
}
// performance analysis:
// without: 115s
// ConcurrentQueue: 28s
// ConcurrentBag: 38s
// CircularBuffer: 30s
internal static class ElementCacheManager
{
private static ConcurrentDictionary<Type, ConcurrentQueue<object>> Cache { get; } = new();
public static T Get<T>() where T : class, new()
{
var buffer = Cache.GetOrAdd(typeof(T), _=> new ConcurrentQueue<object>());
return buffer.TryDequeue(out var result) ? result as T : new T();
}
public static void Store<T>(T element)
{
var buffer = Cache.GetOrAdd(element.GetType(), _=> new ConcurrentQueue<object>());
buffer.Enqueue(element);
}
public static void Collect(Element element)
{
foreach (var child in element.GetChildren())
Collect(child);
if (element is ICollectable collectable)
{
collectable.Collect();
Store(element);
}
}
}
}
+10
View File
@@ -41,5 +41,15 @@ namespace QuestPDF.Elements
{ {
return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})"; return $"Border: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left}) Color({Color})";
} }
public override void Collect()
{
base.Collect();
Left = 0;
Right = 0;
Bottom = 0;
Top = 0;
}
} }
} }
+6 -1
View File
@@ -19,7 +19,7 @@ namespace QuestPDF.Elements
public Position Offset { get; set; } public Position Offset { get; set; }
} }
internal class Column : Element, ICacheable, IStateResettable internal class Column : Element, ICacheable, IStateResettable, ICollectable
{ {
internal List<ColumnItem> Items { get; } = new(); internal List<ColumnItem> Items { get; } = new();
internal float Spacing { get; set; } internal float Spacing { get; set; }
@@ -124,5 +124,10 @@ namespace QuestPDF.Elements
return commands; return commands;
} }
public void Collect()
{
Items.Clear();
}
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ namespace QuestPDF.Elements
{ {
internal class Container : ContainerElement internal class Container : ContainerElement
{ {
internal Container() public Container()
{ {
} }
+11 -1
View File
@@ -4,7 +4,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements namespace QuestPDF.Elements
{ {
internal class Padding : ContainerElement, ICacheable internal class Padding : ContainerElement, ICacheable, ICollectable
{ {
public float Top { get; set; } public float Top { get; set; }
public float Right { get; set; } public float Right { get; set; }
@@ -62,5 +62,15 @@ namespace QuestPDF.Elements
{ {
return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})"; return $"Padding: Top({Top}) Right({Right}) Bottom({Bottom}) Left({Left})";
} }
public override void Collect()
{
base.Collect();
Left = 0;
Right = 0;
Bottom = 0;
Top = 0;
}
} }
} }
+6 -1
View File
@@ -31,7 +31,7 @@ namespace QuestPDF.Elements
public Position Offset { get; set; } public Position Offset { get; set; }
} }
internal class Row : Element, ICacheable, IStateResettable internal class Row : Element, ICacheable, IStateResettable, ICollectable
{ {
internal List<RowItem> Items { get; } = new(); internal List<RowItem> Items { get; } = new();
internal float Spacing { get; set; } internal float Spacing { get; set; }
@@ -156,5 +156,10 @@ namespace QuestPDF.Elements
return renderingCommands; return renderingCommands;
} }
public void Collect()
{
Items.Clear();
}
} }
} }
+6 -1
View File
@@ -8,7 +8,7 @@ using QuestPDF.Infrastructure;
namespace QuestPDF.Elements.Text namespace QuestPDF.Elements.Text
{ {
internal class TextBlock : Element, IStateResettable internal class TextBlock : Element, IStateResettable, ICollectable
{ {
public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left; public HorizontalAlignment Alignment { get; set; } = HorizontalAlignment.Left;
public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>(); public List<ITextBlockItem> Items { get; set; } = new List<ITextBlockItem>();
@@ -38,6 +38,11 @@ namespace QuestPDF.Elements.Text
RenderingQueue.Enqueue(item); RenderingQueue.Enqueue(item);
} }
} }
public void Collect()
{
Items.Clear();
}
internal override SpacePlan Measure(Size availableSpace) internal override SpacePlan Measure(Size availableSpace)
{ {
+2 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -8,7 +9,7 @@ namespace QuestPDF.Fluent
{ {
private static IContainer Border(this IContainer element, Action<Border> handler) private static IContainer Border(this IContainer element, Action<Border> handler)
{ {
var border = element as Border ?? new Border(); var border = element as Border ?? ElementCacheManager.Get<Border>();
handler(border); handler(border);
return element.Element(border); return element.Element(border);
+8 -10
View File
@@ -1,12 +1,14 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
using Container = System.ComponentModel.Container;
namespace QuestPDF.Fluent namespace QuestPDF.Fluent
{ {
public class ColumnDescriptor public class ColumnDescriptor
{ {
internal Column Column { get; } = new(); internal Column Column { get; set; }
public void Spacing(float value, Unit unit = Unit.Point) public void Spacing(float value, Unit unit = Unit.Point)
{ {
@@ -15,14 +17,9 @@ namespace QuestPDF.Fluent
public IContainer Item() public IContainer Item()
{ {
var container = new Container(); var columnItem = ElementCacheManager.Get<ColumnItem>();
Column.Items.Add(columnItem);
Column.Items.Add(new ColumnItem return columnItem;
{
Child = container
});
return container;
} }
} }
@@ -36,7 +33,8 @@ namespace QuestPDF.Fluent
public static void Column(this IContainer element, Action<ColumnDescriptor> handler) public static void Column(this IContainer element, Action<ColumnDescriptor> handler)
{ {
var descriptor = new ColumnDescriptor(); var descriptor = ElementCacheManager.Get<ColumnDescriptor>();
descriptor.Column = ElementCacheManager.Get<Column>();
handler(descriptor); handler(descriptor);
element.Element(descriptor.Column); element.Element(descriptor.Column);
} }
+6 -5
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Drawing.Exceptions; using QuestPDF.Drawing.Exceptions;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -52,10 +53,10 @@ namespace QuestPDF.Fluent
public static IContainer Background(this IContainer element, string color) public static IContainer Background(this IContainer element, string color)
{ {
return element.Element(new Background var background = ElementCacheManager.Get<Background>();
{ background.Color = color;
Color = color
}); return element.Element(background);
} }
public static void Placeholder(this IContainer element, string? text = null) public static void Placeholder(this IContainer element, string? text = null)
@@ -162,7 +163,7 @@ namespace QuestPDF.Fluent
public static IContainer MinimalBox(this IContainer element) public static IContainer MinimalBox(this IContainer element)
{ {
return element.Element(new MinimalBox()); return element.Element(ElementCacheManager.Get<MinimalBox>());
} }
public static IContainer Unconstrained(this IContainer element) public static IContainer Unconstrained(this IContainer element)
+2 -1
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -8,7 +9,7 @@ namespace QuestPDF.Fluent
{ {
private static IContainer Padding(this IContainer element, Action<Padding> handler) private static IContainer Padding(this IContainer element, Action<Padding> handler)
{ {
var padding = element as Padding ?? new Padding(); var padding = element as Padding ?? ElementCacheManager.Get<Padding>();
handler(padding); handler(padding);
return element.Element(padding); return element.Element(padding);
+11 -9
View File
@@ -1,4 +1,5 @@
using System; using System;
using QuestPDF.Drawing;
using QuestPDF.Elements; using QuestPDF.Elements;
using QuestPDF.Infrastructure; using QuestPDF.Infrastructure;
@@ -6,7 +7,7 @@ namespace QuestPDF.Fluent
{ {
public class RowDescriptor public class RowDescriptor
{ {
internal Row Row { get; } = new(); internal Row Row { get; set; }
public void Spacing(float value) public void Spacing(float value)
{ {
@@ -15,14 +16,12 @@ namespace QuestPDF.Fluent
private IContainer Item(RowItemType type, float size = 0) private IContainer Item(RowItemType type, float size = 0)
{ {
var element = new RowItem var rowItem = ElementCacheManager.Get<RowItem>();
{ rowItem.Type = type;
Type = type, rowItem.Size = size;
Size = size
};
Row.Items.Add(element); Row.Items.Add(rowItem);
return element; return rowItem;
} }
[Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")] [Obsolete("This element has been renamed since version 2022.2. Please use the RelativeItem method.")]
@@ -57,9 +56,12 @@ namespace QuestPDF.Fluent
{ {
public static void Row(this IContainer element, Action<RowDescriptor> handler) public static void Row(this IContainer element, Action<RowDescriptor> handler)
{ {
var descriptor = new RowDescriptor(); var descriptor = ElementCacheManager.Get<RowDescriptor>();
descriptor.Row = ElementCacheManager.Get<Row>();
handler(descriptor); handler(descriptor);
element.Element(descriptor.Row); element.Element(descriptor.Row);
ElementCacheManager.Store(descriptor);
} }
} }
} }
+6 -1
View File
@@ -5,7 +5,7 @@ using QuestPDF.Elements;
namespace QuestPDF.Infrastructure namespace QuestPDF.Infrastructure
{ {
internal abstract class ContainerElement : Element, IContainer internal abstract class ContainerElement : Element, IContainer, ICollectable
{ {
internal Element? Child { get; set; } = Empty.Instance; internal Element? Child { get; set; } = Empty.Instance;
@@ -34,5 +34,10 @@ namespace QuestPDF.Infrastructure
{ {
Child?.Draw(availableSpace); Child?.Draw(availableSpace);
} }
public virtual void Collect()
{
Child = default;
}
} }
} }
+7
View File
@@ -0,0 +1,7 @@
namespace QuestPDF.Infrastructure
{
public interface ICollectable
{
void Collect();
}
}