Skip to content

Commit 16e2a3f

Browse files
authored
perf: Optimize pdf generation performance by reducing file I/O (#10461)
perf: optimize pdf generation performance by skipping file i/o
1 parent ca3ad90 commit 16e2a3f

File tree

1 file changed

+27
-10
lines changed

1 file changed

+27
-10
lines changed

src/Docfx.App/PdfBuilder.cs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers;
45
using System.Collections.Concurrent;
56
using System.Diagnostics;
67
using System.Reflection;
@@ -32,6 +33,8 @@ namespace Docfx.Pdf;
3233

3334
static class PdfBuilder
3435
{
36+
private static readonly SearchValues<char> InvalidPathChars = SearchValues.Create(Path.GetInvalidPathChars());
37+
3538
class Outline
3639
{
3740
public string name { get; init; } = "";
@@ -94,7 +97,8 @@ public static async Task CreatePdf(string outputFolder)
9497

9598
using var pageLimiter = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount);
9699
var pagePool = new ConcurrentBag<IPage>();
97-
var headerFooterCache = new ConcurrentDictionary<(string, string), Task<byte[]>>();
100+
var headerFooterTemplateCache = new ConcurrentDictionary<string, string>();
101+
var headerFooterPageCache = new ConcurrentDictionary<(string, string), Task<byte[]>>();
98102

99103
await AnsiConsole.Progress().StartAsync(async progress =>
100104
{
@@ -187,7 +191,7 @@ Task<byte[]> PrintHeaderFooter(Outline toc, int pageNumber, int totalPages, Page
187191
var headerTemplate = ExpandTemplate(GetHeaderFooter(toc.pdfHeaderTemplate), pageNumber, totalPages);
188192
var footerTemplate = ExpandTemplate(GetHeaderFooter(toc.pdfFooterTemplate) ?? DefaultFooterTemplate, pageNumber, totalPages);
189193

190-
return headerFooterCache.GetOrAdd((headerTemplate, footerTemplate), _ => PrintHeaderFooterCore());
194+
return headerFooterPageCache.GetOrAdd((headerTemplate, footerTemplate), _ => PrintHeaderFooterCore());
191195

192196
async Task<byte[]> PrintHeaderFooterCore()
193197
{
@@ -241,16 +245,29 @@ static string ExpandTemplate(string? pdfTemplate, int pageNumber, int totalPages
241245
if (string.IsNullOrEmpty(template))
242246
return template;
243247

244-
try
245-
{
246-
var path = Path.Combine(outputFolder, template);
247-
return File.Exists(path) ? File.ReadAllText(path) : template;
248-
}
249-
catch
250-
{
248+
// Check path chars. If it's contains HTML chars. Skip access to file content to optimmize performance
249+
if (template.AsSpan().ContainsAny(InvalidPathChars))
251250
return template;
252-
}
251+
252+
return headerFooterTemplateCache.GetOrAdd(template, (_) =>
253+
{
254+
// Note: This valueFactory might be called multiple times.
255+
try
256+
{
257+
var path = Path.GetFullPath(Path.Combine(outputFolder, template));
258+
if (!File.Exists(path))
259+
return template;
260+
261+
var templateContent = File.ReadAllText(path);
262+
return templateContent;
263+
}
264+
catch
265+
{
266+
return template;
267+
}
268+
});
253269
}
270+
254271
}
255272
}
256273

0 commit comments

Comments
 (0)