From edc6317228dbd395c02c82b0566ec4ad2837af17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20M=C3=AD=C5=A1ek?= Date: Mon, 5 Aug 2024 14:42:43 +0200 Subject: [PATCH] avoids alloc in version_compare() --- src/Peachpie.Library/Miscellaneous.cs | 84 ++++++++++++------- src/Peachpie.Runtime/Utilities/ListPool.cs | 30 +++++++ src/Peachpie.Runtime/Utilities/ObjectPools.cs | 2 + 3 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 src/Peachpie.Runtime/Utilities/ListPool.cs diff --git a/src/Peachpie.Library/Miscellaneous.cs b/src/Peachpie.Library/Miscellaneous.cs index df6e26ae45..c40ff8d214 100644 --- a/src/Peachpie.Library/Miscellaneous.cs +++ b/src/Peachpie.Library/Miscellaneous.cs @@ -519,20 +519,21 @@ static int CompareParts(string part1, string part2) /// Parses a version and splits it into an array of parts. /// /// The version to be parsed (can be a null reference). - /// An array of parts. + /// Version parts will be added there. + /// If we collected version parts. /// /// Non-alphanumeric characters are eliminated. /// The version is split in between a digit following a non-digit and by /// characters '.', '-', '+', '_'. /// - static string[] VersionToArray(string version) + static bool VersionToArray(string version, List result) { if (string.IsNullOrEmpty(version)) { - return Array.Empty(); + return false; } - var sb = new StringBuilder(version.Length); + var sb = ObjectPools.GetStringBuilder(); char last = '\0'; for (int i = 0; i < version.Length; i++) @@ -566,12 +567,25 @@ static string[] VersionToArray(string version) } } - if (last == '.') + // split into {result} + int from = 0; + for (int i = 0; i < sb.Length; i++) { - sb.Length--; + if (sb[i] == '.') + { + result.Add(sb.ToString(from, i - from)); + from = i + 1; + } } - - return sb.ToString().Split('.'); + + if (from < sb.Length) + { + result.Add(sb.ToString(from, sb.Length - from)); + } + + // + ObjectPools.Return(sb); + return true; } #endregion @@ -593,31 +607,45 @@ static string[] VersionToArray(string version) /// public static int version_compare(string version1, string version2) { - string[] v1 = VersionToArray(version1); - string[] v2 = VersionToArray(version2); - int result; + var v1 = ListPool.Pool.Get(); + var v2 = ListPool.Pool.Get(); - for (int i = 0; i < Math.Max(v1.Length, v2.Length); i++) + try { - string item1 = (i < v1.Length) ? v1[i] : " "; - string item2 = (i < v2.Length) ? v2[i] : " "; - if (item1.Length == 0) item1 = "0"; - if (item2.Length == 0) item2 = "0"; + VersionToArray(version1, v1); + VersionToArray(version2, v2); - if (char.IsDigit(item1[0]) && char.IsDigit(item2[0])) - { - result = Comparison.Compare(Pchp.Core.Convert.StringToLongInteger(item1), Pchp.Core.Convert.StringToLongInteger(item2)); - } - else - { - result = CompareParts(char.IsDigit(item1[0]) ? "#" : item1, char.IsDigit(item2[0]) ? "#" : item2); - } + int result; - if (result != 0) + for (int i = 0; i < Math.Max(v1.Count, v2.Count); i++) { - return result; + string item1 = (i < v1.Count) ? v1[i] : " "; + string item2 = (i < v2.Count) ? v2[i] : " "; + + if (item1.Length == 0) item1 = "0"; + if (item2.Length == 0) item2 = "0"; + + if (char.IsDigit(item1[0]) && char.IsDigit(item2[0])) + { + result = Comparison.Compare(Pchp.Core.Convert.StringToLongInteger(item1), Pchp.Core.Convert.StringToLongInteger(item2)); + } + else + { + result = CompareParts(char.IsDigit(item1[0]) ? "#" : item1, char.IsDigit(item2[0]) ? "#" : item2); + } + + if (result != 0) + { + return result; + } } + + } + finally + { + ListPool.Pool.Return(v1); + ListPool.Pool.Return(v2); } return 0; @@ -626,11 +654,11 @@ public static int version_compare(string version1, string version2) /// /// Compares two "PHP-standardized" version number strings. /// - public static bool version_compare(string version1, string version2, string op) + public static bool version_compare(string version1, string version2, string @operator) { var compare = version_compare(version1, version2); - switch (op) + switch (@operator) { case "<": case "lt": return compare < 0; diff --git a/src/Peachpie.Runtime/Utilities/ListPool.cs b/src/Peachpie.Runtime/Utilities/ListPool.cs new file mode 100644 index 0000000000..a26dd0a451 --- /dev/null +++ b/src/Peachpie.Runtime/Utilities/ListPool.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.ObjectPool; + +namespace Pchp.Core.Utilities +{ + public class ListPool + { + sealed class ListPoolPolicy : PooledObjectPolicy> + { + public override List Create() => new List(); + + public override bool Return(List obj) + { + if (obj.Count > 1024*1024) + { + return false; + } + + obj.Clear(); + return true; + } + } + + public static readonly ObjectPool> Pool = new DefaultObjectPool>(new ListPoolPolicy()); + } +} diff --git a/src/Peachpie.Runtime/Utilities/ObjectPools.cs b/src/Peachpie.Runtime/Utilities/ObjectPools.cs index 611070f805..89e9ca2528 100644 --- a/src/Peachpie.Runtime/Utilities/ObjectPools.cs +++ b/src/Peachpie.Runtime/Utilities/ObjectPools.cs @@ -16,6 +16,8 @@ public static class ObjectPools /// public static ObjectPool StringBuilderPool { get; } = new DefaultObjectPoolProvider().Create(new StringBuilderPooledObjectPolicy()); + public static ObjectPool> GetListPool() => ListPool.Pool; + /// Gets pooled instance of . public static StringBuilder GetStringBuilder() => StringBuilderPool.Get();