Skip to content

Instantly share code, notes, and snippets.

@OptoCloud
Last active September 7, 2023 07:02
Show Gist options
  • Save OptoCloud/23a0acb9cd6c692c07dbdf5ea9651baa to your computer and use it in GitHub Desktop.
Save OptoCloud/23a0acb9cd6c692c07dbdf5ea9651baa to your computer and use it in GitHub Desktop.
Quick String Replacer (Thanks @just-ero for the help!)
using OptoCloud;
var summary = BenchmarkDotNet.Running.BenchmarkRunner.Run<StringReplaceBenchmark>();
using System.Runtime.InteropServices;
namespace OptoCloud;
public sealed class QuickStringReplace
{
private readonly record struct Replacement(int MatchStart, int MatchEnd, string New);
private readonly string _source;
private readonly List<Replacement> _replacements = new(8);
private int _diff;
public QuickStringReplace(string source)
{
_source = source;
}
public QuickStringReplace Replace(ReadOnlySpan<char> oldValue, string newValue)
{
ArgumentNullException.ThrowIfNull(newValue);
int oldLength = oldValue.Length;
if (oldLength <= 0)
{
return this;
}
ReadOnlySpan<char> source = _source.AsSpan();
int diff = newValue.Length - oldLength;
int abs = 0, rel;
while ((rel = source[abs..].IndexOf(oldValue)) != -1)
{
int pos = abs + rel;
_replacements.Add(new(pos, pos + oldLength, newValue));
_diff += diff;
abs = pos + oldLength;
}
return this;
}
private static int Compare(Replacement left, Replacement right) => left.MatchStart - right.MatchStart;
private static readonly Comparison<Replacement> Comparison = new(Compare);
public override string ToString()
{
int newLength = _source.Length + _diff;
return string.Create(newLength, this, static (span, state) =>
{
ReadOnlySpan<char> source = state._source.AsSpan();
Span<Replacement> replacements = CollectionsMarshal.AsSpan(state._replacements);
replacements.Sort(Comparison);
int offset = 0, prev = 0;
for (int i = 0; i < replacements.Length; i++)
{
Replacement r = replacements[i];
int chunk = r.MatchStart - prev;
if (chunk > 0)
{
source.Slice(prev, chunk).CopyTo(span[offset..]);
offset += chunk;
}
ReadOnlySpan<char> rNew = r.New.AsSpan();
if (!rNew.IsEmpty)
{
rNew.CopyTo(span[offset..]);
offset += rNew.Length;
}
prev = r.MatchEnd;
}
int remainder = source.Length - prev;
if (remainder > 0)
{
source[prev..].CopyTo(span[offset..]);
}
});
}
}
using BenchmarkDotNet.Attributes;
using System.Text;
namespace OptoCloud;
[MemoryDiagnoser]
public class StringReplaceBenchmark
{
private readonly string Template =
"""
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Simple Transactional Email</title>
<style>
@media only screen and (max-width: 620px) {
table.body h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table.body p,
table.body ul,
table.body ol,
table.body td,
table.body span,
table.body a {
font-size: 16px !important;
}
table.body .wrapper,
table.body .article {
padding: 10px !important;
}
table.body .content {
padding: 0 !important;
}
table.body .container {
padding: 0 !important;
width: 100% !important;
}
table.body .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table.body .btn table {
width: 100% !important;
}
table.body .btn a {
width: 100% !important;
}
table.body .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
</style>
</head>
<body style="background-color: #f6f6f6; font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;">
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">{{PreheaderText}}</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f6f6f6; width: 100%;" width="100%" bgcolor="#f6f6f6">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; max-width: 580px; padding: 10px; width: 580px; margin: 0 auto;" width="580" valign="top">
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 580px; padding: 10px;">
{{RemoveMe}}
<table role="presentation" class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background: #ffffff; border-radius: 3px; width: 100%;" width="100%">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">Hi there,</p>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">Sometimes you just want to send a simple HTML email with a simple design and clear call to action. This is it.</p>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; box-sizing: border-box; width: 100%;" width="100%">
<tbody>
<tr>
<td align="left" style="font-family: sans-serif; font-size: 14px; vertical-align: top; padding-bottom: 15px;" {{RemoveMe}}valign="top">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
<tbody>
<tr>
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top; border-radius: 5px; text-align: center; background-color: #3498db;" valign="top" align="center" bgcolor="#3498db"> <a href="{{ActionLink}}" target="_blank" style="border: solid 1px #3498db; border-radius: 5px; box-sizing: border-box; cursor: pointer; display: inline-block; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-decoration: none; text-transform: capitalize; background-color: #3498db; border-color: #3498db; color: #ffffff;">Call To Action</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">This is a really simple email template. Its sole purpose is to get the recipient to click the button with no distractions.</p>
<p style="font-family:{{RemoveMe}} sans-serif; font-size: 14px; font-weight: normal; margin: 0; margin-bottom: 15px;">Good luck! Hope it works.</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- END CENTERED WHITE CONTAINER -->
{{RemoveMe}}
<!-- START FOOTER -->
<div class="footer" style="clear: both; margin-top: 10px; text-align: center; width: 100%;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: {{RemoveMe}}12px; text-align: center;" valign="top" align="center">
<span class="apple-link" style="color: #999999; font-size: 12px; text-align: center;">{{CompanyAddress}}</span>
<br> Don't like these emails? <a href="{{UnsubscribeLink}}" style="text-decoration: underline; color: #999999; font-size: 12px; text-align: center;">Unsubscribe</a>.
</td>
</tr>
<tr>
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;" valign="top" align="center">
Powered by <a href="{{PoweredByLink}}" style="color: #999999; font-size: 12px; text-align: center; text-decoration: none;">{{PoweredByText}}</a>.
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
</div>
</td>
<td style="font-family: {{RemoveMe}}sans-serif; font-size: 14px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>
""";
private const string MatchPreheaderText = "{{PreheaderText}}";
private readonly string MatchPreheaderReplacement = "This is a really simple email template. Its sole purpose is to get the recipient to click the button with no distractions.";
private const string MatchActionLink = "{{ActionLink}}";
private readonly string MatchActionLinkReplacement = "http://www.google.com";
private const string MatchCompanyAddress = "{{CompanyAddress}}";
private readonly string MatchCompanyAddressReplacement = "123 Fake Street, SpringField, OR, 97477";
private const string MatchUnsubscribeLink = "{{UnsubscribeLink}}";
private readonly string MatchUnsubscribeLinkReplacement = "http://www.google.com";
private const string MatchPoweredByLink = "{{PoweredByLink}}";
private readonly string MatchPoweredByLinkReplacement = "http://www.google.com";
private const string MatchPoweredByText = "{{PoweredByText}}";
private readonly string MatchPoweredByTextReplacement = "Google";
private const string RemoveMeText = "{{RemoveMe}}";
private readonly string RemoveMeReplacement = "";
[Benchmark(Baseline = true)]
public string StringReplaceTest()
{
return Template
.Replace(MatchPreheaderText, MatchPreheaderReplacement)
.Replace(MatchCompanyAddress, MatchCompanyAddressReplacement)
.Replace(MatchPoweredByLink, MatchPoweredByLinkReplacement)
.Replace(MatchPoweredByText, MatchPoweredByTextReplacement)
.Replace(MatchUnsubscribeLink, MatchUnsubscribeLinkReplacement)
.Replace(MatchActionLink, MatchActionLinkReplacement)
.Replace(RemoveMeText, RemoveMeReplacement);
}
[Benchmark]
public string StringBuilderReplaceTest()
{
return new StringBuilder(Template)
.Replace(MatchPreheaderText, MatchPreheaderReplacement)
.Replace(MatchCompanyAddress, MatchCompanyAddressReplacement)
.Replace(MatchPoweredByLink, MatchPoweredByLinkReplacement)
.Replace(MatchPoweredByText, MatchPoweredByTextReplacement)
.Replace(MatchUnsubscribeLink, MatchUnsubscribeLinkReplacement)
.Replace(MatchActionLink, MatchActionLinkReplacement)
.Replace(RemoveMeText, RemoveMeReplacement)
.ToString();
}
[Benchmark]
public string QuickStringReplaceTest()
{
return new QuickStringReplace(Template)
.Replace(MatchPreheaderText, MatchPreheaderReplacement)
.Replace(MatchCompanyAddress, MatchCompanyAddressReplacement)
.Replace(MatchPoweredByLink, MatchPoweredByLinkReplacement)
.Replace(MatchPoweredByText, MatchPoweredByTextReplacement)
.Replace(MatchUnsubscribeLink, MatchUnsubscribeLinkReplacement)
.Replace(MatchActionLink, MatchActionLinkReplacement)
.Replace(RemoveMeText, RemoveMeReplacement)
.ToString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment