Skip to content

Commit

Permalink
avoids alloc in version_compare()
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubmisek committed Aug 5, 2024
1 parent 9292615 commit edc6317
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 28 deletions.
84 changes: 56 additions & 28 deletions src/Peachpie.Library/Miscellaneous.cs
Original file line number Diff line number Diff line change
Expand Up @@ -519,20 +519,21 @@ static int CompareParts(string part1, string part2)
/// Parses a version and splits it into an array of parts.
/// </summary>
/// <param name="version">The version to be parsed (can be a <B>null</B> reference).</param>
/// <returns>An array of parts.</returns>
/// <param name="result">Version parts will be added there.</param>
/// <returns>If we collected version parts.</returns>
/// <remarks>
/// Non-alphanumeric characters are eliminated.
/// The version is split in between a digit following a non-digit and by
/// characters '.', '-', '+', '_'.
/// </remarks>
static string[] VersionToArray(string version)
static bool VersionToArray(string version, List<string> result)
{
if (string.IsNullOrEmpty(version))
{
return Array.Empty<string>();
return false;
}

var sb = new StringBuilder(version.Length);
var sb = ObjectPools.GetStringBuilder();
char last = '\0';

for (int i = 0; i < version.Length; i++)
Expand Down Expand Up @@ -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
Expand All @@ -593,31 +607,45 @@ static string[] VersionToArray(string version)
/// </summary>
public static int version_compare(string version1, string version2)
{
string[] v1 = VersionToArray(version1);
string[] v2 = VersionToArray(version2);
int result;
var v1 = ListPool<string>.Pool.Get();
var v2 = ListPool<string>.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<string>.Pool.Return(v1);
ListPool<string>.Pool.Return(v2);
}

return 0;
Expand All @@ -626,11 +654,11 @@ public static int version_compare(string version1, string version2)
/// <summary>
/// Compares two "PHP-standardized" version number strings.
/// </summary>
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;
Expand Down
30 changes: 30 additions & 0 deletions src/Peachpie.Runtime/Utilities/ListPool.cs
Original file line number Diff line number Diff line change
@@ -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<T>
{
sealed class ListPoolPolicy : PooledObjectPolicy<List<T>>
{
public override List<T> Create() => new List<T>();

public override bool Return(List<T> obj)
{
if (obj.Count > 1024*1024)
{
return false;
}

obj.Clear();
return true;
}
}

public static readonly ObjectPool<List<T>> Pool = new DefaultObjectPool<List<T>>(new ListPoolPolicy());
}
}
2 changes: 2 additions & 0 deletions src/Peachpie.Runtime/Utilities/ObjectPools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public static class ObjectPools
/// </summary>
public static ObjectPool<StringBuilder> StringBuilderPool { get; } = new DefaultObjectPoolProvider().Create(new StringBuilderPooledObjectPolicy());

public static ObjectPool<List<T>> GetListPool<T>() => ListPool<T>.Pool;

/// <summary>Gets pooled instance of <see cref="StringBuilder"/>.</summary>
public static StringBuilder GetStringBuilder() => StringBuilderPool.Get();

Expand Down

0 comments on commit edc6317

Please sign in to comment.