I've made a very basic wrapper over a String that offers mutability in place (context: mono/XNA, budget is about 11ms per frame, 0B (ZERO, NIL, НОЛЬ, NADA) allocations hard requirement). Before that I've used a wrapper over StringBuilder that essentially would do the same - get raw value of the string it operates on.
public class MutableString
{
// Setup fields
private int m_Pos;
private string m_valueStr;
private readonly bool dontThrow = false;
// Publics
public int Capacity { get { return m_valueStr.Length; } }
public int Length { get { return m_Pos; } }
public MutableString(int size) : this(size, '\0', false) { }
public MutableString(int size, bool ignoreOverflow) : this(size, '\0', ignoreOverflow) { }
public MutableString(int size, char fillChar) : this(size, fillChar, false) { }
public MutableString(int size, char fillChar, bool ignoreOverflow)
{
if (size < 1)
throw new ArgumentException("Size cannot be 0 or less");
m_valueStr = new string(fillChar, size);
dontThrow = ignoreOverflow;
}
public override string ToString()
{
if (m_Pos == 0)
return string.Empty;
var free = m_valueStr.Length - m_Pos;
if (free > 0)
repeatChar('\0', free);
m_Pos = 0;
return m_valueStr;
}
//
// LOGIC
//
#region CHARS
public void Append(char[] value, int indx, int count)
{
if (value == null)
return;
var len = value.Length;
if (len == 0 || count < 1 || indx < 0 || (count > len - indx))
return;
if (len > 1)
AppendInternal(value, indx, count);
else
Append(value[0]);
}
public void Append(char[] value)
{
if (value == null)
return;
var len = value.Length;
if (len > 1)
AppendInternal(value, 0, len);
else
Append(value[0]);
}
public void Append(char value)
{
if (m_Pos >= m_valueStr.Length)
{
if (dontThrow)
return;
else
throw new ArgumentException("Not enough free space to accomodate element!");
}
singleChar(value);
m_Pos++;
}
private void AppendInternal(char[] value, int indx, int count)
{
var free = m_valueStr.Length - m_Pos;
if (count > free)
{
if (dontThrow)
return;
else
throw new ArgumentException(string.Format("Not enough free space to accomodate {0} elements!", count));
}
charCopy(value, indx, count);
m_Pos = m_Pos + count;
}
#endregion
#region STRINGS
private void AppendInternal(string value, int indx, int count)
{
var free = m_valueStr.Length - m_Pos;
if (count > free)
{
if (dontThrow == true)
return;
else
throw new ArgumentOutOfRangeException(string.Format("Not enough free space to accomodate {0} elements!", count));
}
stringCopy(value, indx, count);
m_Pos = m_Pos + count;
}
public void Append(string value, int indx, int count)
{
if (value == null)
return;
var len = value.Length;
if (count < 1 || indx < 0 || (count > len - indx))
return;
if (len > 1)
AppendInternal(value, indx, count);
else
Append(value[0]);
}
public void Append(string value)
{
if (value == null)
return;
var len = value.Length;
if (len > 1)
AppendInternal(value, 0, len);
else
Append(value[0]);
}
#endregion
// Copy logic
private unsafe void stringCopy(string value, int indx, int charCount)
{
fixed (char* ptrDest = m_valueStr)
{
fixed (char* ptrSrc = value)
{
wstrcpy(ptrDest + m_Pos, ptrSrc + indx, charCount);
}
}
}
private unsafe void charCopy(char[] value, int indx, int charCount)
{
fixed (char* ptrDest = m_valueStr)
{
fixed (char* ptrSrc = value)
{
wstrcpy(ptrDest + m_Pos, ptrSrc + indx, charCount);
}
}
}
private unsafe void singleChar(char value)
{
fixed (char* ptrDest = m_valueStr)
{
ptrDest[m_Pos] = value;
}
}
private unsafe void repeatChar(char value, int count)
{
var fin = m_Pos + count;
fixed (char* ptrDest = m_valueStr)
{
while (m_Pos < fin)
{
ptrDest[m_Pos] = value;
m_Pos++;
}
}
}
private unsafe void rawCopy(char* dest, char* src, int charCount)
{
for (int i = 0; i < charCount; i++)
dest[i] = src[i];
}
private unsafe static void wstrcpy(char* dmem, char* smem, int charCount)
{
if (((int)dmem & 2) != 0)
{
*dmem = *smem;
dmem++;
smem++;
charCount--;
}
while (charCount >= 8)
{
*(uint*)dmem = *(uint*)smem;
*(uint*)(dmem + 2) = *(uint*)(smem + 2);
*(uint*)(dmem + 4) = *(uint*)(smem + 4);
*(uint*)(dmem + 6) = *(uint*)(smem + 6);
dmem += 8;
smem += 8;
charCount -= 8;
}
if ((charCount & 4) != 0)
{
*(uint*)dmem = *(uint*)smem;
*(uint*)(dmem + 2) = *(uint*)(smem + 2);
dmem += 4;
smem += 4;
}
if ((charCount & 2) != 0)
{
*(uint*)dmem = *(uint*)smem;
dmem += 2;
smem += 2;
}
if ((charCount & 1) != 0)
{
*dmem = *smem;
}
}
}
The issue is that my performance over SB wrapper is way slower for some reason. on my Core i7-3770K I get like +0.600-0.700ms performance drop for 10 000 operations run. And it makes zero sense code wise, I have less call overhead and less branching, code-wise.
Is there anything I'm missing or can do? Also, unrelated general feedback is welcomed.
UPD: forgot to say aiming .net3.5 for max compatibility with mono
UPD2: got it, had to swap while
to for
in repeatChar()
looks like JIT wasnt able to work out some magic with while.
Span<char>
orMemory<char>
out of scope? With them you could reduce the direct usage ofunsafe
code. From performance perspective it seems a valid option. You could also take advantage ofArrayPool<char>
to reduce memory allocation if it matters for you from performance perspective. \$\endgroup\$