Last active
September 7, 2015 09:34
-
-
Save bbowyersmyth/c1c55444fb102281e3c9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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