Skip to content

Instantly share code, notes, and snippets.

@bbowyersmyth
Last active September 7, 2015 09:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbowyersmyth/c1c55444fb102281e3c9 to your computer and use it in GitHub Desktop.
Save bbowyersmyth/c1c55444fb102281e3c9 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
public static void Main(string[] args)
{
// Singles
string[] singleValues = { "abcd" };
var singleValuesList = new List<string>(singleValues);
IEnumerable<string> singleValuesEnumerable = new Stack<string>(singleValues);
IEnumerable<string> singleValuesArrayAsEnumerable = singleValues;
// Multiples
string[] multipleValues = { "abcd", "efgh", "ijkl", "mnop", "qrstuvwxyz" };
var multipleValuesList = new List<string>(multipleValues);
IEnumerable<string> multipleValuesEnumerable = new Stack<string>(multipleValuesList);
IEnumerable<string> mulitpleValuesArrayAsEnumerable = multipleValues;
// Large
var largeValuesList = new List<string>(100);
for (var i = 0; i < 20; i++)
{
largeValuesList.AddRange(multipleValues);
}
var largeValues = largeValuesList.ToArray();
IEnumerable<string> largeValuesEnumerable = new Stack<string>(largeValuesList);
IEnumerable<string> largeValuesArrayAsEnumerable = largeValues;
Profile("Old string[1]", 1000000, () =>
{
string joined = Join(", ", singleValues, 0, singleValues.Length);
});
Profile("New string[1]", 1000000, () =>
{
string joined = JoinNew(", ", singleValues, 0, singleValues.Length);
});
Profile("Old List<string>(1)", 1000000, () =>
{
string joined = Join(", ", singleValuesList);
});
Profile("New List<string>(1)", 1000000, () =>
{
string joined = JoinNew(", ", singleValuesList);
});
Profile("Old IEnumerable<string>(1)", 1000000, () =>
{
string joined = Join(", ", singleValuesEnumerable);
});
Profile("New IEnumerable<string>(1)", 1000000, () =>
{
string joined = JoinNew(", ", singleValuesEnumerable);
});
Profile("Old (IEnumerable<string>)string[1]", 1000000, () =>
{
string joined = Join(", ", singleValuesArrayAsEnumerable);
});
Profile("New (IEnumerable<string>)string[1]", 1000000, () =>
{
string joined = JoinNew(", ", singleValuesArrayAsEnumerable);
});
Console.WriteLine();
Profile("Old string[5]", 1000000, () =>
{
string joined = Join(", ", multipleValues, 0, multipleValues.Length);
});
Profile("New string[5]", 1000000, () =>
{
string joined = JoinNew(", ", multipleValues, 0, multipleValues.Length);
});
Profile("Old List<string>(5)", 1000000, () =>
{
string joined = Join(", ", multipleValuesList);
});
Profile("New List<string>(5)", 1000000, () =>
{
string joined = JoinNew(", ", multipleValuesList);
});
Profile("Old IEnumerable<string>(5)", 1000000, () =>
{
string joined = Join(", ", multipleValuesEnumerable);
});
Profile("New IEnumerable<string>(5)", 1000000, () =>
{
string joined = JoinNew(", ", multipleValuesEnumerable);
});
Profile("Old (IEnumerable<string>)string[5]", 1000000, () =>
{
string joined = Join(", ", mulitpleValuesArrayAsEnumerable);
});
Profile("New (IEnumerable<string>)string[5]", 1000000, () =>
{
string joined = JoinNew(", ", mulitpleValuesArrayAsEnumerable);
});
Console.WriteLine();
Profile("Old string[100]", 1000000, () =>
{
string joined = Join(", ", largeValues, 0, largeValues.Length);
});
Profile("New string[100]", 1000000, () =>
{
string joined = JoinNew(", ", largeValues, 0, largeValues.Length);
});
Profile("Old List<string>(100)", 1000000, () =>
{
string joined = Join(", ", largeValuesList);
});
Profile("New List<string>(100)", 1000000, () =>
{
string joined = JoinNew(", ", largeValuesList);
});
Profile("Old IEnumerable<string>(100)", 1000000, () =>
{
string joined = Join(", ", largeValuesEnumerable);
});
Profile("New IEnumerable<string>(100)", 1000000, () =>
{
string joined = JoinNew(", ", largeValuesEnumerable);
});
Profile("Old (IEnumerable<string>)string[100]", 1000000, () =>
{
string joined = Join(", ", largeValuesArrayAsEnumerable);
});
Profile("New (IEnumerable<string>)string[100]", 1000000, () =>
{
string joined = JoinNew(", ", largeValuesArrayAsEnumerable);
});
//Console.Read();
return;
}
static void Profile(string description, int iterations, Action func)
{
// warm up
func();
// clean up
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++)
{
func();
}
//GC.Collect();
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
public static String Join(String separator, IEnumerable<String> values)
{
if (values == null)
throw new ArgumentNullException("values");
if (separator == null)
separator = String.Empty;
using (IEnumerator<String> en = values.GetEnumerator())
{
if (!en.MoveNext())
return String.Empty;
StringBuilder result = StringBuilderCache.Acquire();
if (en.Current != null)
{
result.Append(en.Current);
}
while (en.MoveNext())
{
result.Append(separator);
if (en.Current != null)
{
result.Append(en.Current);
}
}
return StringBuilderCache.GetStringAndRelease(result);
}
}
public unsafe static String JoinNew(String separator, IEnumerable<String> values)
{
if (values == null)
throw new ArgumentNullException("values");
IList<string> list = values as IList<string>;
if (list != null)
{
var count = list.Count;
if (count == 0)
return String.Empty;
if (count == 1)
return list[0] ?? String.Empty;
StringBuilder result = StringBuilderCache.Acquire();
result.Append(list[0]);
for (int i = 1; i < count; i++)
{
result.Append(separator);
result.Append(list[i]);
}
return StringBuilderCache.GetStringAndRelease(result);
}
else
{
using (IEnumerator<String> en = values.GetEnumerator())
{
if (!en.MoveNext())
return String.Empty;
String value = en.Current;
if (en.MoveNext()) {
StringBuilder result = StringBuilderCache.Acquire();
result.Append(value);
do
{
result.Append(separator);
result.Append(en.Current);
} while (en.MoveNext());
return StringBuilderCache.GetStringAndRelease(result);
}
return value ?? String.Empty;
}
}
}
public unsafe static String Join(String separator, String[] value, int startIndex, int count)
{
//Range check the array
if (value == null)
throw new ArgumentNullException("value");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", ("ArgumentOutOfRange_StartIndex"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", ("ArgumentOutOfRange_NegativeCount"));
if (startIndex > value.Length - count)
throw new ArgumentOutOfRangeException("startIndex", ("ArgumentOutOfRange_IndexCountBuffer"));
//Treat null as empty string.
if (separator == null)
{
separator = String.Empty;
}
//If count is 0, that skews a whole bunch of the calculations below, so just special case that.
if (count == 0)
{
return String.Empty;
}
int jointLength = 0;
//Figure out the total length of the strings in value
int endIndex = startIndex + count - 1;
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{
if (value[stringToJoinIndex] != null)
{
jointLength += value[stringToJoinIndex].Length;
}
}
//Add enough room for the separator.
jointLength += (count - 1) * separator.Length;
// Note that we may not catch all overflows with this check (since we could have wrapped around the 4gb range any number of times
// and landed back in the positive range.) The input array might be modifed from other threads,
// so we have to do an overflow check before each append below anyway. Those overflows will get caught down there.
if ((jointLength < 0) || ((jointLength + 1) < 0))
{
throw new OutOfMemoryException();
}
//If this is an empty string, just return.
if (jointLength == 0)
{
return String.Empty;
}
string jointString = new String('\0', jointLength);
fixed (char* pointerToJointString = jointString)
{
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer(pointerToJointString, jointLength);
// Append the first string first and then append each following string prefixed by the separator.
charBuffer.AppendString(value[startIndex]);
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{
charBuffer.AppendString(separator);
charBuffer.AppendString(value[stringToJoinIndex]);
}
}
return jointString;
}
public unsafe static String JoinNew(String separator, String[] value, int startIndex, int count)
{
//Range check the array
if (value == null)
throw new ArgumentNullException("value");
if (startIndex < 0)
throw new ArgumentOutOfRangeException("startIndex", ("ArgumentOutOfRange_StartIndex"));
if (count < 0)
throw new ArgumentOutOfRangeException("count", ("ArgumentOutOfRange_NegativeCount"));
if (startIndex > value.Length - count)
throw new ArgumentOutOfRangeException("startIndex", ("ArgumentOutOfRange_IndexCountBuffer"));
if (count == 0)
{
return String.Empty;
}
if (count == 1)
{
return value[startIndex] ?? String.Empty;
}
//Treat null as empty string.
if (separator == null)
{
separator = String.Empty;
}
int jointLength = 0;
//Figure out the total length of the strings in value
int endIndex = startIndex + count - 1;
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{
string currentValue = value[stringToJoinIndex];
if (currentValue != null)
{
jointLength += currentValue.Length;
}
}
//Add enough room for the separator.
jointLength += (count - 1) * separator.Length;
// Note that we may not catch all overflows with this check (since we could have wrapped around the 4gb range any number of times
// and landed back in the positive range.) The input array might be modifed from other threads,
// so we have to do an overflow check before each append below anyway. Those overflows will get caught down there.
if ((jointLength < 0) || ((jointLength + 1) < 0))
{
throw new OutOfMemoryException();
}
//If this is an empty string, just return.
if (jointLength == 0)
{
return String.Empty;
}
string jointString = new String('\0', jointLength);
fixed (char* pointerToJointString = jointString)
{
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer(pointerToJointString, jointLength);
// Append the first string first and then append each following string prefixed by the separator.
charBuffer.AppendString(value[startIndex]);
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{
charBuffer.AppendString(separator);
charBuffer.AppendString(value[stringToJoinIndex]);
}
}
return jointString;
}
}
internal static class StringBuilderCache
{
private const int MAX_BUILDER_SIZE = 260;
private const int DEFAULT_CAPACITY = 16;
[ThreadStatic]
private static StringBuilder t_cachedInstance;
public static StringBuilder Acquire(int capacity = DEFAULT_CAPACITY)
{
if (capacity <= MAX_BUILDER_SIZE)
{
StringBuilder sb = StringBuilderCache.t_cachedInstance;
if (sb != null)
{
// Avoid stringbuilder block fragmentation by getting a new StringBuilder
// when the requested size is larger than the current capacity
if (capacity <= sb.Capacity)
{
StringBuilderCache.t_cachedInstance = null;
sb.Clear();
return sb;
}
}
}
return new StringBuilder(capacity);
}
public static StringBuilder Acquire(string value)
{
StringBuilder sb = Acquire(System.Math.Max(value.Length, DEFAULT_CAPACITY));
sb.Append(value);
return sb;
}
public static void Release(StringBuilder sb)
{
if (sb.Capacity <= MAX_BUILDER_SIZE)
{
StringBuilderCache.t_cachedInstance = sb;
}
}
public static string GetStringAndRelease(StringBuilder sb)
{
string result = sb.ToString();
Release(sb);
return result;
}
}
unsafe internal struct UnSafeCharBuffer
{
[SecurityCritical]
char* m_buffer;
int m_totalSize;
int m_length;
[DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
public static extern IntPtr memcpy(byte* dest, byte* src, int count);
[System.Security.SecurityCritical] // auto-generated
public UnSafeCharBuffer(char* buffer, int bufferSize)
{
Contract.Assert(buffer != null, "buffer pointer can't be null.");
Contract.Assert(bufferSize >= 0, "buffer size can't be negative.");
m_buffer = buffer;
m_totalSize = bufferSize;
m_length = 0;
}
[System.Security.SecuritySafeCritical] // auto-generated
public void AppendString(string stringToAppend)
{
if (String.IsNullOrEmpty(stringToAppend))
{
return;
}
if ((m_totalSize - m_length) < stringToAppend.Length)
{
throw new IndexOutOfRangeException();
}
fixed (char* pointerToString = stringToAppend)
{
memcpy((byte*)(m_buffer + m_length), (byte*)pointerToString, stringToAppend.Length * sizeof(char));
}
m_length += stringToAppend.Length;
Contract.Assert(m_length <= m_totalSize, "Buffer has been overflowed!");
}
public int Length
{
get
{
return m_length;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment