Skip to content

Commit

Permalink
less allocs in PhpString.ToString()
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubmisek committed Aug 15, 2024
1 parent d935e45 commit 9ec657a
Showing 1 changed file with 87 additions and 17 deletions.
104 changes: 87 additions & 17 deletions src/Peachpie.Runtime/PhpString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using Pchp.Core.Text;
using Pchp.Core.Utilities;
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -104,18 +106,25 @@ public static BlobChar FromValue(PhpValue value)

static Exception InvalidValueException(PhpValue value) => new NotSupportedException(value.TypeCode.ToString());

public static string ToString(BlobChar[] chars, Encoding enc)
{
var builder = ObjectPools.GetStringBuilder();

GetChars(chars, enc, builder);

return ObjectPools.GetStringAndReturn(builder);
}

/// <summary>
/// Copies characters to a new array of <see cref="char"/>s.
/// Appends encoded characters to a <see cref="StringBuilder"/>.
/// Single-byte chars are encoded to Unicode chars.
/// </summary>
public static Span<char> ToCharArray(BlobChar[] chars, Encoding enc)
public static int GetChars(BlobChar[] chars, Encoding enc, StringBuilder builder)
{
// TODO: more decent code

var result = new ValueList<char>(chars.Length);

Debug.Assert(chars != null);

int count = 0;

for (int i = 0; i < chars.Length; i++)
{
if (chars[i].IsByte)
Expand All @@ -127,25 +136,28 @@ public static Span<char> ToCharArray(BlobChar[] chars, Encoding enc)
}

// encode bytes (i..j] to char array
var maxchars = enc.GetMaxCharCount(j - i);
var tmp = new char[maxchars];
var src = new byte[j - i];
var nbytes = j - i;
var src = ArrayPool<byte>.Shared.Rent(nbytes);

for (int b = 0; b < src.Length; b++, i++)
for (int b = 0; b < nbytes; b++, i++)
{
src[b] = (byte)chars[i]._b;
}

var charscount = enc.GetChars(src, 0, src.Length, tmp, 0);
result.AddRange(tmp, 0, charscount);
count += enc.GetChars(src.AsSpan(0, nbytes), builder);

//
ArrayPool<byte>.Shared.Return(src);
}
else
{
result.Add(chars[i]._ch);
builder.Append(chars[i]._ch);
count++;
}
}

return result.AsSpan();
//
return count;
}

/// <summary>
Expand Down Expand Up @@ -817,7 +829,7 @@ static void WriteChunk(Context ctx, BlobChar[] chars)
var enc = ctx.StringEncoding;

Span<char> ch = stackalloc char[1];
var bytes = new byte[ReferenceEquals(enc, Encoding.UTF8) ? 8 : enc.GetMaxByteCount(1)];
var bytes = new byte[ReferenceEquals(enc, Encoding.UTF8) ? 8 : enc.GetMaxByteCount(1)];

//int size = 0;

Expand Down Expand Up @@ -1099,6 +1111,35 @@ public string ToString(Encoding encoding)
}
}

public void GetChars(Encoding encoding, StringBuilder builder)
{
Debug.Assert(encoding != null);
Debug.Assert(builder != null);

if (_string != null)
{
builder.Append(_string);
}
else
{
var chunks = _chunks;
if (chunks != null)
{
if (chunks is object[] objs)
{
foreach (var obj in objs.AsSpan())
{
ChunkToString(encoding, builder, chunk: obj);
}
}
else
{
ChunkToString(encoding, builder, chunk: chunks);
}
}
}
}

static string ChunkToString(Encoding encoding, ReadOnlySpan<object> chunks)
{
if (chunks.Length == 1)
Expand All @@ -1115,7 +1156,7 @@ static string ChunkToString(Encoding encoding, ReadOnlySpan<object> chunks)

foreach (var chunk in chunks)
{
builder.Append(ChunkToString(encoding, chunk));
ChunkToString(encoding, builder, chunk);
}

return ObjectPools.GetStringAndReturn(builder);
Expand All @@ -1130,11 +1171,40 @@ static string ChunkToString(Encoding encoding, object chunk)
byte[] barr => encoding.GetString(barr),
Blob b => b.ToString(encoding),
char[] carr => new string(carr),
BlobChar[] barr => BlobChar.ToCharArray(barr, encoding).ToString(),
BlobChar[] barr => BlobChar.ToString(barr, encoding),
_ => throw InvalidChunkException(chunk),
};
}

static void ChunkToString(Encoding encoding, StringBuilder builder, object chunk)
{
switch (chunk)
{
case string str:
builder.Append(str);
break;

case byte[] barr:
encoding.GetChars(barr, builder);
break;

case Blob b:
b.GetChars(encoding, builder);
break;

case char[] carr:
builder.Append(carr);
break;

case BlobChar[] barr:
BlobChar.GetChars(barr, encoding, builder);
break;

default:
throw InvalidChunkException(chunk);
};
}

#endregion

#region ToBytes
Expand Down

0 comments on commit 9ec657a

Please sign in to comment.