Skip to content

Commit

Permalink
Improve performance and reduce memory use and reduce array allocation…
Browse files Browse the repository at this point in the history
…s by reusing char[] (#9166)

* Reduce array allocations by reusing char[]

* don't hide .Equals()

* Reduce memory use by reducing array allocations

* Revert "Reduce memory use by reducing array allocations"

This reverts commit faf6b60.

* reuse char[] for string.split() to avoid params [] allocation
  • Loading branch information
nzdev authored Jan 22, 2021
1 parent 335c6b1 commit 0bd4dce
Show file tree
Hide file tree
Showing 97 changed files with 310 additions and 167 deletions.
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Composing/TypeFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ private static string[] AssembliesAcceptingLoadExceptions
var s = ConfigurationManager.AppSettings[Constants.AppSettings.AssembliesAcceptingLoadExceptions];
return _assembliesAcceptingLoadExceptions = string.IsNullOrWhiteSpace(s)
? Array.Empty<string>()
: s.Split(',').Select(x => x.Trim()).ToArray();
: s.Split(Constants.CharArrays.Comma).Select(x => x.Trim()).ToArray();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Configuration/GlobalSettingsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal static string GetUmbracoMvcAreaNoCache(this IGlobalSettings globalSetti
var path = globalSettings.Path;
if (path.StartsWith(SystemDirectories.Root)) // beware of TrimStart, see U4-2518
path = path.Substring(SystemDirectories.Root.Length);
return path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim().ToLower();
return path.TrimStart(Constants.CharArrays.Tilde).TrimStart(Constants.CharArrays.ForwardSlash).Replace('/', '-').Trim().ToLower();
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static bool IsImageFile(this IContentSection contentConfig, string extens
{
if (contentConfig == null) throw new ArgumentNullException(nameof(contentConfig));
if (extension == null) return false;
extension = extension.TrimStart('.');
extension = extension.TrimStart(Constants.CharArrays.Period);
return contentConfig.ImageFileTypes.InvariantContains(extension);
}

Expand Down
138 changes: 138 additions & 0 deletions src/Umbraco.Core/Constants-CharArrays.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Umbraco.Core
{
public static partial class Constants
{
/// <summary>
/// Char Arrays to avoid allocations
/// </summary>
public static class CharArrays
{
/// <summary>
/// Char array containing only /
/// </summary>
public static readonly char[] ForwardSlash = new char[] { '/' };

/// <summary>
/// Char array containing only \
/// </summary>
public static readonly char[] Backslash = new char[] { '\\' };

/// <summary>
/// Char array containing only '
/// </summary>
public static readonly char[] SingleQuote = new char[] { '\'' };

/// <summary>
/// Char array containing only "
/// </summary>
public static readonly char[] DoubleQuote = new char[] { '\"' };


/// <summary>
/// Char array containing ' "
/// </summary>
public static readonly char[] DoubleQuoteSingleQuote = new char[] { '\"', '\'' };

/// <summary>
/// Char array containing only _
/// </summary>
public static readonly char[] Underscore = new char[] { '_' };

/// <summary>
/// Char array containing \n \r
/// </summary>
public static readonly char[] LineFeedCarriageReturn = new char[] { '\n', '\r' };


/// <summary>
/// Char array containing \n
/// </summary>
public static readonly char[] LineFeed = new char[] { '\n' };

/// <summary>
/// Char array containing only ,
/// </summary>
public static readonly char[] Comma = new char[] { ',' };

/// <summary>
/// Char array containing only &
/// </summary>
public static readonly char[] Ampersand = new char[] { '&' };

/// <summary>
/// Char array containing only \0
/// </summary>
public static readonly char[] NullTerminator = new char[] { '\0' };

/// <summary>
/// Char array containing only .
/// </summary>
public static readonly char[] Period = new char[] { '.' };

/// <summary>
/// Char array containing only ~
/// </summary>
public static readonly char[] Tilde = new char[] { '~' };
/// <summary>
/// Char array containing ~ /
/// </summary>
public static readonly char[] TildeForwardSlash = new char[] { '~', '/' };

/// <summary>
/// Char array containing only ?
/// </summary>
public static readonly char[] QuestionMark = new char[] { '?' };

/// <summary>
/// Char array containing ? &
/// </summary>
public static readonly char[] QuestionMarkAmpersand = new char[] { '?', '&' };

/// <summary>
/// Char array containing XML 1.1 whitespace chars
/// </summary>
public static readonly char[] XmlWhitespaceChars = new char[] { ' ', '\t', '\r', '\n' };

This comment has been minimized.

Copy link
@bjarnef

bjarnef Jan 22, 2021

Contributor

Maybe just naming it XmlWhitespaces?


/// <summary>
/// Char array containing only the Space char
/// </summary>
public static readonly char[] Space = new char[] { ' ' };

/// <summary>
/// Char array containing only ;
/// </summary>
public static readonly char[] Semicolon = new char[] { ';' };

/// <summary>
/// Char array containing a comma and a space
/// </summary>
public static readonly char[] CommaSpace = new char[] { ',', ' ' };

/// <summary>
/// Char array containing _ -
/// </summary>
public static readonly char[] UnderscoreDash = new char[] { '_', '-' };

/// <summary>
/// Char array containing =
/// </summary>
public static readonly char[] EqualsChar = new char[] { '=' };

This comment has been minimized.

Copy link
@bjarnef

bjarnef Jan 22, 2021

Contributor

Maybe just Equals would be better since none of the other constants include Char in naming?


/// <summary>
/// Char array containing >
/// </summary>
public static readonly char[] GreaterThan = new char[] { '>' };

/// <summary>
/// Char array containing |
/// </summary>
public static readonly char[] VerticalTab = new char[] { '|' };
}
}
}
2 changes: 1 addition & 1 deletion src/Umbraco.Core/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ public static string ToQueryString(this IDictionary<string, object> d)
{
builder.Append(String.Format("{0}={1}&", HttpUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : HttpUtility.UrlEncode(i.Value.ToString())));
}
return builder.ToString().TrimEnd('&');
return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand);
}

/// <summary>The get entry ignore case.</summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/GuidUdi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public GuidUdi(Uri uriValue)
: base(uriValue)
{
Guid guid;
if (Guid.TryParse(uriValue.AbsolutePath.TrimStart('/'), out guid) == false)
if (Guid.TryParse(uriValue.AbsolutePath.TrimStart(Constants.CharArrays.ForwardSlash), out guid) == false)
throw new FormatException("URI \"" + uriValue + "\" is not a GUID entity ID.");

Guid = guid;
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/HttpContextExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static string GetCurrentRequestIpAddress(this HttpContextBase httpContext
if (string.IsNullOrEmpty(ipAddress))
return request.UserHostAddress;

var addresses = ipAddress.Split(',');
var addresses = ipAddress.Split(Constants.CharArrays.Comma);
if (addresses.Length != 0)
return addresses[0];

Expand Down
10 changes: 5 additions & 5 deletions src/Umbraco.Core/IO/IOHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static string FindFile(string virtualPath)
retval = virtualPath.Replace("~", SystemDirectories.Root);

if (virtualPath.StartsWith("/") && virtualPath.StartsWith(SystemDirectories.Root) == false)
retval = SystemDirectories.Root + "/" + virtualPath.TrimStart('/');
retval = SystemDirectories.Root + "/" + virtualPath.TrimStart(Constants.CharArrays.ForwardSlash);

return retval;
}
Expand Down Expand Up @@ -98,11 +98,11 @@ public static string MapPath(string path, bool useHttpContext)
if (String.IsNullOrEmpty(path) == false && (path.StartsWith("~") || path.StartsWith(SystemDirectories.Root)))
return HostingEnvironment.MapPath(path);
else
return HostingEnvironment.MapPath("~/" + path.TrimStart('/'));
return HostingEnvironment.MapPath("~/" + path.TrimStart(Constants.CharArrays.ForwardSlash));
}

var root = GetRootDirectorySafe();
var newPath = path.TrimStart('~', '/').Replace('/', IOHelper.DirSepChar);
var newPath = path.TrimStart(Constants.CharArrays.TildeForwardSlash).Replace('/', IOHelper.DirSepChar);
var retval = root + IOHelper.DirSepChar.ToString(CultureInfo.InvariantCulture) + newPath;

return retval;
Expand All @@ -121,7 +121,7 @@ internal static string ReturnPath(string settingsKey, string standardPath, bool
if (string.IsNullOrEmpty(retval))
retval = standardPath;

return retval.TrimEnd('/');
return retval.TrimEnd(Constants.CharArrays.ForwardSlash);
}

internal static string ReturnPath(string settingsKey, string standardPath)
Expand Down Expand Up @@ -188,7 +188,7 @@ internal static bool VerifyEditPath(string filePath, IEnumerable<string> validDi
internal static bool VerifyFileExtension(string filePath, IEnumerable<string> validFileExtensions)
{
var ext = Path.GetExtension(filePath);
return ext != null && validFileExtensions.Contains(ext.TrimStart('.'));
return ext != null && validFileExtensions.Contains(ext.TrimStart(Constants.CharArrays.Period));
}

public static bool PathStartsWith(string path, string root, char separator)
Expand Down
10 changes: 5 additions & 5 deletions src/Umbraco.Core/IO/PhysicalFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public PhysicalFileSystem(string virtualRoot)

_rootPath = EnsureDirectorySeparatorChar(IOHelper.MapPath(virtualRoot)).TrimEnd(Path.DirectorySeparatorChar);
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
_rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd('/');
_rootUrl = EnsureUrlSeparatorChar(IOHelper.ResolveUrl(virtualRoot)).TrimEnd(Constants.CharArrays.ForwardSlash);
}

public PhysicalFileSystem(string rootPath, string rootUrl)
Expand All @@ -54,7 +54,7 @@ public PhysicalFileSystem(string rootPath, string rootUrl)

_rootPath = EnsureDirectorySeparatorChar(rootPath).TrimEnd(Path.DirectorySeparatorChar);
_rootPathFwd = EnsureUrlSeparatorChar(_rootPath);
_rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd('/');
_rootUrl = EnsureUrlSeparatorChar(rootUrl).TrimEnd(Constants.CharArrays.ForwardSlash);
}

/// <summary>
Expand Down Expand Up @@ -259,12 +259,12 @@ public string GetRelativePath(string fullPathOrUrl)
// if it starts with the root URL, strip it and trim the starting slash to make it relative
// eg "/Media/1234/img.jpg" => "1234/img.jpg"
if (IOHelper.PathStartsWith(path, _rootUrl, '/'))
return path.Substring(_rootUrl.Length).TrimStart('/');
return path.Substring(_rootUrl.Length).TrimStart(Constants.CharArrays.ForwardSlash);

// if it starts with the root path, strip it and trim the starting slash to make it relative
// eg "c:/websites/test/root/Media/1234/img.jpg" => "1234/img.jpg"
if (IOHelper.PathStartsWith(path, _rootPathFwd, '/'))
return path.Substring(_rootPathFwd.Length).TrimStart('/');
return path.Substring(_rootPathFwd.Length).TrimStart(Constants.CharArrays.ForwardSlash);

// unchanged - what else?
return path;
Expand Down Expand Up @@ -326,7 +326,7 @@ public string GetFullPath(string path)
/// <remarks>All separators are forward-slashes.</remarks>
public string GetUrl(string path)
{
path = EnsureUrlSeparatorChar(path).Trim('/');
path = EnsureUrlSeparatorChar(path).Trim(Constants.CharArrays.ForwardSlash);
return _rootUrl + "/" + path;
}

Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Core/IO/ShadowFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public void AddFile(string path, Stream stream, bool overrideIfExists)
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));

var parts = normPath.Split('/');
var parts = normPath.Split(Constants.CharArrays.ForwardSlash);
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
Expand Down Expand Up @@ -297,7 +297,7 @@ public void AddFile(string path, string physicalPath, bool overrideIfExists = tr
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));

var parts = normPath.Split('/');
var parts = normPath.Split(Constants.CharArrays.ForwardSlash);
for (var i = 0; i < parts.Length - 1; i++)
{
var dirPath = string.Join("/", parts.Take(i + 1));
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ internal bool IsConnectionStringConfigured(ConnectionStringSettings databaseSett
var sqlCeDatabaseExists = false;
if (dbIsSqlCe)
{
var parts = databaseSettings.ConnectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var parts = databaseSettings.ConnectionString.Split(Constants.CharArrays.Semicolon, StringSplitOptions.RemoveEmptyEntries);
var dataSourcePart = parts.FirstOrDefault(x => x.InvariantStartsWith("Data Source="));
if (dataSourcePart != null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ internal List<DataTypeDto> GetDataTypes(string editorAlias, bool strict = true)

protected int[] ConvertStringValues(string val)
{
var splitVals = val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var splitVals = val.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);

var intVals = splitVals
.Select(x => int.TryParse(x, out var i) ? i : int.MinValue)
Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Core/Models/PathValidationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static void ValidatePathWithException(this NodeDto entity)
if (entity.Path.IsNullOrWhiteSpace())
throw new InvalidDataException($"The content item {entity.NodeId} has an empty path: {entity.Path} with parentID: {entity.ParentId}");

var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
//a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
Expand Down Expand Up @@ -53,7 +53,7 @@ public static bool ValidatePath(this IUmbracoEntity entity)
if (entity.Path.IsNullOrWhiteSpace())
return false;

var pathParts = entity.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var pathParts = entity.Path.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries);
if (pathParts.Length < 2)
{
//a path cannot be less than 2 parts, at a minimum it must be root (-1) and it's own id
Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Core/Packaging/PackageDataInstallation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ private Dictionary<string, int> CreateContentTypeFolderStructure(IEnumerable<XEl
&& ((string)infoElement.Element("Master")).IsNullOrWhiteSpace())
{
var alias = documentType.Element("Info").Element("Alias").Value;
var folders = foldersAttribute.Value.Split('/');
var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash);
var rootFolder = HttpUtility.UrlDecode(folders[0]);
//level 1 = root level folders, there can only be one with the same name
var current = _contentTypeService.GetContainers(rootFolder, 1).FirstOrDefault();
Expand Down Expand Up @@ -942,7 +942,7 @@ private Dictionary<string, int> CreateDataTypeFolderStructure(IEnumerable<XEleme
if (foldersAttribute != null)
{
var name = datatypeElement.Attribute("Name").Value;
var folders = foldersAttribute.Value.Split('/');
var folders = foldersAttribute.Value.Split(Constants.CharArrays.ForwardSlash);
var rootFolder = HttpUtility.UrlDecode(folders[0]);
//there will only be a single result by name for level 1 (root) containers
var current = _dataTypeService.GetContainers(rootFolder, 1).FirstOrDefault();
Expand Down
14 changes: 7 additions & 7 deletions src/Umbraco.Core/Packaging/PackageDefinitionXmlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ public PackageDefinition ToPackageDefinition(XElement xml)
Actions = xml.Element("actions")?.ToString(SaveOptions.None) ?? "<actions></actions>", //take the entire outer xml value
ContentNodeId = xml.Element("content")?.AttributeValue<string>("nodeId") ?? string.Empty,
ContentLoadChildNodes = xml.Element("content")?.AttributeValue<bool>("loadChildNodes") ?? false,
Macros = xml.Element("macros")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Templates = xml.Element("templates")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Stylesheets = xml.Element("stylesheets")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DocumentTypes = xml.Element("documentTypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Languages = xml.Element("languages")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DataTypes = xml.Element("datatypes")?.Value.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Macros = xml.Element("macros")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Templates = xml.Element("templates")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Stylesheets = xml.Element("stylesheets")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DocumentTypes = xml.Element("documentTypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Languages = xml.Element("languages")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DictionaryItems = xml.Element("dictionaryitems")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
DataTypes = xml.Element("datatypes")?.Value.Split(Constants.CharArrays.Comma, StringSplitOptions.RemoveEmptyEntries).ToList() ?? new List<string>(),
Files = xml.Element("files")?.Elements("file").Select(x => x.Value).ToList() ?? new List<string>()
};

Expand Down
Loading

0 comments on commit 0bd4dce

Please sign in to comment.