From 10cdb48eb2e3a94f7755e27a3403e2e09e35ca6a Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Thu, 15 Dec 2022 11:49:31 +0100 Subject: [PATCH 1/4] Small performance improvements --- Composite/Core/Localization/LocalizationParser.cs | 4 ++-- Composite/Data/DataServiceScopeManager.cs | 4 ++-- .../InternalUrlConverters/PageInternalUrlConverter.cs | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Composite/Core/Localization/LocalizationParser.cs b/Composite/Core/Localization/LocalizationParser.cs index 920607e5df..12c7847e2c 100644 --- a/Composite/Core/Localization/LocalizationParser.cs +++ b/Composite/Core/Localization/LocalizationParser.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -16,7 +16,7 @@ namespace Composite.Core.Localization [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static class LocalizationParser { - private static readonly Regex _attributRegex = new Regex(@"\$\((?[^:]+):(?[^\)]+)\)"); + private static readonly Regex _attributRegex = new Regex(@"\$\((?[^:]+):(?[^\)]+)\)", RegexOptions.Compiled); /// diff --git a/Composite/Data/DataServiceScopeManager.cs b/Composite/Data/DataServiceScopeManager.cs index d74f6d59b5..0d8701cc57 100644 --- a/Composite/Data/DataServiceScopeManager.cs +++ b/Composite/Data/DataServiceScopeManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Remoting.Messaging; @@ -37,7 +37,7 @@ internal static void EnableServices() internal static object GetService(Type t) { - if (DisableServicesFlag.HasValue && DisableServicesFlag.Value) + if (DisableServicesFlag ?? false) return null; foreach(var serviceList in DataServiceScopeStack) diff --git a/Composite/Plugins/Routing/InternalUrlConverters/PageInternalUrlConverter.cs b/Composite/Plugins/Routing/InternalUrlConverters/PageInternalUrlConverter.cs index 2cbeb377f4..f781502f78 100644 --- a/Composite/Plugins/Routing/InternalUrlConverters/PageInternalUrlConverter.cs +++ b/Composite/Plugins/Routing/InternalUrlConverters/PageInternalUrlConverter.cs @@ -26,7 +26,7 @@ public string ToPublicUrl(string internalPageUrl, UrlSpace urlSpace) try { - anchor = new UrlBuilder(internalPageUrl).Anchor; + anchor = GetAnchor(internalPageUrl); pageUrlData = PageUrls.UrlProvider.ParseInternalUrl(internalPageUrl); } catch @@ -61,6 +61,14 @@ public string ToPublicUrl(string internalPageUrl, UrlSpace urlSpace) return publicPageUrl; } + private static string GetAnchor(string url) + { + int anchorIndex = url.IndexOf('#'); + if (anchorIndex <= -1) return null; + + return anchorIndex == url.Length - 1 ? string.Empty : url.Substring(anchorIndex + 1); + } + /// public IDataReference ToDataReference(string internalUrl) { From 5e04217f8c7b6a604cb096021e92394e9e44eb9c Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Fri, 16 Dec 2022 11:53:22 +0100 Subject: [PATCH 2/4] Optimizing performance of UrlBuilder --- Composite/Core/UrlBuilder.cs | 82 ++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/Composite/Core/UrlBuilder.cs b/Composite/Core/UrlBuilder.cs index 5ecfe6ff70..b6b24cf9eb 100644 --- a/Composite/Core/UrlBuilder.cs +++ b/Composite/Core/UrlBuilder.cs @@ -2,8 +2,11 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; using System.Web; +using System.Web.Util; using Composite.Core.Extensions; using Composite.Core.Routing.Pages; @@ -85,28 +88,91 @@ public UrlBuilder(string url) internal static class DefaultHttpEncoder { - public static string UrlEncode(string urlPart) + private static readonly Func _urlPathEncodeFunc; + private static readonly Func _urlDecodeFunc; + private static readonly Func _urlEncodeFunc; + + static DefaultHttpEncoder() { + HttpEncoder defaultHttpEncoder; using (new NoHttpContext()) { - return HttpUtility.UrlEncode(urlPart); + defaultHttpEncoder = HttpEncoder.Current; + } + + var instanceExpression = Expression.Constant(defaultHttpEncoder); + + // Compiling: str => _defaultHttpEncoder.UrlPathEncode(str) + { + var stringParam = Expression.Parameter(typeof(string)); + var methodInfo = typeof(HttpEncoder) + .GetMethod("UrlPathEncode", BindingFlags.Instance | BindingFlags.NonPublic, null, new []{typeof(string)}, null); + Verify.IsNotNull(methodInfo, "Failed to get method 'UrlPathEncode' from " + typeof(HttpEncoder).FullName); + var methodCallExpression = Expression.Call(instanceExpression, methodInfo, stringParam); + _urlPathEncodeFunc = Expression.Lambda>(methodCallExpression, stringParam).Compile(); + } + + // Compiling: (str, encoding) => _defaultHttpEncoder.UrlDecode(str, encoding); + { + var stringParam = Expression.Parameter(typeof(string)); + var encodingParam = Expression.Parameter(typeof(Encoding)); + var methodInfo = typeof(HttpEncoder) + .GetMethod("UrlDecode", BindingFlags.Instance | BindingFlags.NonPublic, null, new []{ typeof(string), typeof(Encoding) }, null); + Verify.IsNotNull(methodInfo, "Failed to get method 'UrlDecode' from " + typeof(HttpEncoder).FullName); + + var methodCallExpression = Expression.Call(instanceExpression, methodInfo, stringParam, encodingParam); + _urlDecodeFunc = Expression.Lambda>( + methodCallExpression, stringParam, encodingParam).Compile(); + } + + // Compiling: (bytes, offset, length) => _defaultHttpEncoder.UrlEncode(str, bytes, offset, length); + { + var bytesParam = Expression.Parameter(typeof(byte[])); + var offsetParam = Expression.Parameter(typeof(int)); + var lengthParam = Expression.Parameter(typeof(int)); + var methodInfo = typeof(HttpEncoder) + .GetMethod("UrlEncode", BindingFlags.Instance | BindingFlags.NonPublic, null, + new[] { typeof(byte[]), typeof(int), typeof(int) }, null); + Verify.IsNotNull(methodInfo, "Failed to get method 'UrlDecode' from " + typeof(HttpEncoder).FullName); + + var methodCallExpression = Expression.Call(instanceExpression, methodInfo, bytesParam, offsetParam, lengthParam); + _urlEncodeFunc = Expression.Lambda>( + methodCallExpression, bytesParam, offsetParam, lengthParam).Compile(); } } - public static string UrlPathEncode(string urlPart) + private static byte[] UrlEncodeToBytes(string str, Encoding e) { - using (new NoHttpContext()) + if (str == null) { - return HttpUtility.UrlPathEncode(urlPart); + return null; } + byte[] bytes = e.GetBytes(str); + + return _urlEncodeFunc(bytes, 0, bytes.Length); } - public static string UrlDecode(string urlPart) + + public static string UrlEncode(string urlPart) { - using (new NoHttpContext()) + if (urlPart == null) { - return HttpUtility.UrlDecode(urlPart); + return null; } + + return Encoding.ASCII.GetString(UrlEncodeToBytes(urlPart, Encoding.UTF8)); + } + + + + public static string UrlPathEncode(string urlPart) + { + return _urlPathEncodeFunc(urlPart); + } + + public static string UrlDecode(string urlPart) + { + return _urlDecodeFunc(urlPart, Encoding.UTF8); } private class NoHttpContext : IDisposable From 78ee5336096b05047ed9048bb1d9e4bd4734a010 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 19 Dec 2022 12:07:29 +0100 Subject: [PATCH 3/4] Optimizing XhtmlPrettifier --- Composite/Core/Xml/XhtmlPrettifier.cs | 43 ++++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/Composite/Core/Xml/XhtmlPrettifier.cs b/Composite/Core/Xml/XhtmlPrettifier.cs index 0a7422b813..601f4961c0 100644 --- a/Composite/Core/Xml/XhtmlPrettifier.cs +++ b/Composite/Core/Xml/XhtmlPrettifier.cs @@ -16,11 +16,10 @@ namespace Composite.Core.Xml [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] public static class XhtmlPrettifier { - private static string _ampersandWord = "C1AMPERSAND"; + private static readonly string _ampersandWord = "C1AMPERSAND"; private static readonly Regex _encodeCDataRegex = new Regex(@"))*)\]\]>", RegexOptions.Compiled); private static readonly Regex _decodeCDataRegex = new Regex("C1CDATAREPLACE(?[0-9]*)", RegexOptions.Compiled); private static readonly Regex _encodeRegex = new Regex(@"&(?[^\;]+;)", RegexOptions.Compiled); - private static readonly Regex _decodeRegex = new Regex(@"C1AMPERSAND(?[^\;]+;)", RegexOptions.Compiled); private static readonly char[] IncorrectEscapeSequenceCharacters = { '\'', '\"', '<', '>', ' ' }; @@ -168,7 +167,7 @@ private static void NodeTreeToString(IEnumerable nodes, StringBuilder s || node.ParentNode == null || node.ParentNode.NamespaceByPrefix(attribute.Name) != node.NamespaceURI) { - stringBuilder.Append(" ").Append(attribute.Name).Append("=\"").Append(EncodeAttributeString(attribute.Value)).Append("\""); + stringBuilder.Append(" ").Append(attribute.Name).Append("=\"").AppendEncodeAttributeString(attribute.Value).Append("\""); } } @@ -236,7 +235,7 @@ private static void NodeTreeToString(IEnumerable nodes, StringBuilder s stringBuilder.Append(" "); } - stringBuilder.Append(EncodeElementString(value)); + stringBuilder.AppendEncodeElementString(value); if (addSpaceToEnd) { @@ -281,7 +280,7 @@ private static void NodeTreeToString(IEnumerable nodes, StringBuilder s stringBuilder.AppendLine().AddIndent(node.Level, indentString); } - stringBuilder.Append(""); + stringBuilder.Append(""); if (node.ParentNode != null && !node.ParentNode.IsBlockElement()) { stringBuilder.AppendLine().AddIndent(node.Level - 1, indentString); @@ -329,26 +328,48 @@ private static StringBuilder AddIndent(this StringBuilder sb, int level, string - private static string EncodeAttributeString(string value) + private static StringBuilder AppendEncodeAttributeString(this StringBuilder sb, string value) { value = value.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """); - return RemoveC1EncodedAmpersands(value); + return sb.AppendRemoveC1EncodedAmpersands(value); } - private static string EncodeElementString(string value) + private static StringBuilder AppendEncodeElementString(this StringBuilder sb, string value) { value = value.Replace("&", "&").Replace("<", "<").Replace(">", ">"); - return RemoveC1EncodedAmpersands(value); + return sb.AppendRemoveC1EncodedAmpersands(value); } - private static string RemoveC1EncodedAmpersands(string value) + private static StringBuilder AppendRemoveC1EncodedAmpersands(this StringBuilder sb, string value) { - return _decodeRegex.Replace(value, match => "&" + match.Groups["tag"].Value); + var pointer = 0; + var ampersandWordPosition = value.IndexOf(_ampersandWord, StringComparison.Ordinal); + while (ampersandWordPosition > -1) + { + if (ampersandWordPosition > pointer) + { + sb.Append(value, pointer, ampersandWordPosition - pointer); + } + + sb.Append('&'); + + pointer = ampersandWordPosition + _ampersandWord.Length; + if (pointer == value.Length) break; + + ampersandWordPosition = value.IndexOf(_ampersandWord, pointer, StringComparison.Ordinal); + } + + if (pointer < value.Length) + { + sb.Append(value, pointer, value.Length - pointer); + } + + return sb; } From acc0393ac2e463b6019ca3c3068c905dd6a66601 Mon Sep 17 00:00:00 2001 From: Dmitry Dzygin Date: Mon, 19 Dec 2022 12:11:29 +0100 Subject: [PATCH 4/4] Optimizing XElementToAspNetExtensions - removing implicitly called XName constructor --- .../Renderings/Page/XElementToAspNetExtensions.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs index 05cd4d694f..60501a913e 100644 --- a/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs +++ b/Composite/Core/WebClient/Renderings/Page/XElementToAspNetExtensions.cs @@ -175,7 +175,7 @@ private static XElement CopyWithoutNamespace(XElement source, XNamespace namespa private static bool IsHtmlControlElement(XElement element) { var name = element.Name; - string xNamespace = element.Name.Namespace.NamespaceName; + string xNamespace = name.Namespace.NamespaceName; return (xNamespace == Namespaces.Xhtml.NamespaceName || xNamespace == string.Empty) && !VoidElements.Contains(name.LocalName); @@ -232,7 +232,9 @@ public static void CopyAttributes(this XElement source, HtmlControl target, bool { foreach (var attribute in source.Attributes()) { - if (attribute.Name.LocalName == "id") + string localName = attribute.Name.LocalName; + + if (localName == "id") { target.ID = attribute.Value; continue; @@ -243,16 +245,15 @@ public static void CopyAttributes(this XElement source, HtmlControl target, bool string namespaceName = attribute.Value; if (namespaceName != "http://www.w3.org/1999/xhtml" - && !namespaceName.StartsWith("http://www.composite.net/ns")) + && !namespaceName.StartsWith("http://www.composite.net/ns", StringComparison.Ordinal)) { - target.Attributes.Add($"xmlns:{attribute.Name.LocalName}", attribute.Value); + target.Attributes.Add($"xmlns:{localName}", attribute.Value); } continue; } - string localName = attribute.Name.LocalName; - if (localName != XName_Xmlns + if (localName != XName_Xmlns.LocalName || (copyXmlnsAttribute && (source.Parent == null || source.Name.Namespace != source.Parent.Name.Namespace))) {