Improve StringBuilder ctor(), ctor(int), and ToString() performance. #7029

Merged
merged 1 commit into from Sep 2, 2016

Projects

None yet

4 participants

@bbowyersmyth
Contributor

Squeeze a little more performance out of StringBuilder ctor, ToString + others.

  • The default ctor(), and by far the most used, goes through 9 conditions, a string pin, and a function call. None of which are needed.
    Changed implementation to be just the two things that are required.
  • The ctor(int), second most common, also does a string pin and function call. Redirect to the smaller ctor(int, int) overload.
  • Missing (uint) cast caused additional casting code to be generated.
  • Fix on &array[0] instead of array for less generated IL. Lengths are checked beforehand.
  • Moved return statements inside the fixed block so that the char* doesn't need to be nulled out.
Test Before After
ctor() 173ms 93ms
ctor(16) 149ms 109ms
ToString() 110ms 93ms

Perf test code https://gist.github.com/bbowyersmyth/2cc93ec0b2127cd98d567654b5d6467e
Additional test dotnet/corefx#11353

@bbowyersmyth bbowyersmyth Improve StringBuilder ctor(), ctor(int), and ToString() performance. …
…Consistent changes in other functions.
be7ef09
@bbowyersmyth
Contributor

Before

public override unsafe string ToString()
    // .maxstack 3
    // .locals init (
    // [0] string 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00bc ldloc.0))]',
      // [1] class System.Text.StringBuilder 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]',
      // [2] char* 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_0081 ldloc.2))]',
      // [3] string pinned 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_00bb stloc.3))]',
      // [4] char[] 'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0064 ldloc.s))]',
      // [5] int32 'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_0082 ldloc.s))]',
      // [6] int32 'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_008b ldloc.s))]',
      // [7] char& pinned V_7,
**      // [8] char[] V_8
    // )
    // 
    // IL_0000: ldarg.0      // this
    // IL_0001: call         instance int32 System.Text.StringBuilder::get_Length()
    // IL_0006: brtrue.s     IL_000e
    // IL_0008: ldsfld       string System.String::Empty
    // IL_000d: ret          
    // IL_000e: ldarg.0      // this
    // IL_000f: call         instance int32 System.Text.StringBuilder::get_Length()
    // IL_0014: call         string System.String::FastAllocateString(int32)
    // IL_0019: stloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00bc ldloc.0))]'
    // IL_001a: ldarg.0      // this
    // IL_001b: stloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_001c: ldloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00bc ldloc.0))]'
    // IL_001d: stloc.3      // 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_00bb stloc.3))]'
    // IL_001e: ldloc.3      // 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_00bb stloc.3))]'
    // IL_001f: conv.i       
    // IL_0020: stloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_0081 ldloc.2))]'
    // IL_0021: ldloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_0081 ldloc.2))]'
    // IL_0022: brfalse.s    IL_002c
    // IL_0024: ldloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_0081 ldloc.2))]'
    // IL_0025: call         int32 System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
    // IL_002a: add          
    // IL_002b: stloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_0081 ldloc.2))]'
    // IL_002c: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_002d: ldfld        int32 System.Text.StringBuilder::m_ChunkLength
    // IL_0032: ldc.i4.0     
    // IL_0033: ble.s        IL_00ad
    // IL_0035: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_0036: ldfld        char[] System.Text.StringBuilder::m_ChunkChars
    // IL_003b: stloc.s      'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0064 ldloc.s))]'
    // IL_003d: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_003e: ldfld        int32 System.Text.StringBuilder::m_ChunkOffset
    // IL_0043: stloc.s      'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_0082 ldloc.s))]'
    // IL_0045: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_0046: ldfld        int32 System.Text.StringBuilder::m_ChunkLength
    // IL_004b: stloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_008b ldloc.s))]'
    // IL_004d: ldloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_008b ldloc.s))]'
    // IL_004f: ldloc.s      'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_0082 ldloc.s))]'
    // IL_0051: add          
**    // IL_0052: conv.u8      
**    // IL_0053: ldloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00bc ldloc.0))]'
**    // IL_0054: callvirt     instance int32 System.String::get_Length()
**    // IL_0059: conv.i8      
**    // IL_005a: bgt.s        IL_0098
    // IL_005c: ldloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_008b ldloc.s))]'
    // IL_005e: ldloc.s      'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0064 ldloc.s))]'
    // IL_0060: ldlen        
    // IL_0061: conv.i4      
    // IL_0062: bgt.un.s     IL_0098
    // IL_0064: ldloc.s      'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0064 ldloc.s))]'
**    // IL_0066: dup          
**    // IL_0067: stloc.s      V_8
**    // IL_0069: brfalse.s    IL_0071
**    // IL_006b: ldloc.s      V_8
**    // IL_006d: ldlen        
**    // IL_006e: conv.i4      
**    // IL_006f: brtrue.s     IL_0077
**    // IL_0071: ldc.i4.0     
**    // IL_0072: conv.u       
**    // IL_0073: stloc.s      V_7
**    // IL_0075: br.s         IL_0081
**    // IL_0077: ldloc.s      V_8
    // IL_0079: ldc.i4.0     
    // IL_007a: ldelema      System.Char
    // IL_007f: stloc.s      V_7
    // IL_0081: ldloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_0081 ldloc.2))]'
    // IL_0082: ldloc.s      'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_0082 ldloc.s))]'
    // IL_0084: conv.i       
    // IL_0085: ldc.i4.2     
    // IL_0086: mul          
    // IL_0087: add          
    // IL_0088: ldloc.s      V_7
    // IL_008a: conv.i       
    // IL_008b: ldloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_008b ldloc.s))]'
    // IL_008d: call         void System.String::wstrcpy(char*, char*, int32)
    // IL_0092: ldc.i4.0     
    // IL_0093: conv.u       
    // IL_0094: stloc.s      V_7
    // IL_0096: br.s         IL_00ad
    // IL_0098: ldstr        "chunkLength"
    // IL_009d: ldstr        "ArgumentOutOfRange_Index"
    // IL_00a2: call         string System.Environment::GetResourceString(string)
    // IL_00a7: newobj       instance void System.ArgumentOutOfRangeException::.ctor(string, string)
    // IL_00ac: throw        
    // IL_00ad: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_00ae: ldfld        class System.Text.StringBuilder System.Text.StringBuilder::m_ChunkPrevious
    // IL_00b3: stloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
    // IL_00b4: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_00b4 ldloc.1))]'
**    // IL_00b5: brtrue       IL_002c
**    // IL_00ba: ldnull       
**    // IL_00bb: stloc.3      // 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_00bb stloc.3))]'
    // IL_00bc: ldloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00bc ldloc.0))]'
    // IL_00bd: ret          
    // 

After

public override unsafe string ToString()
    // .maxstack 3
    // .locals init (
    // [0] string 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00a2 ldloc.0))]',
      // [1] class System.Text.StringBuilder 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]',
      // [2] char* 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_006c ldloc.2))]',
      // [3] string pinned 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_001e ldloc.3))]',
      // [4] char[] 'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0062 ldloc.s))]',
      // [5] int32 'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_006d ldloc.s))]',
      // [6] int32 'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_0076 ldloc.s))]',
      // [7] char& pinned V_7

    // )
    // 
    // IL_0000: ldarg.0      // this
    // IL_0001: call         instance int32 System.Text.StringBuilder::get_Length()
    // IL_0006: brtrue.s     IL_000e
    // IL_0008: ldsfld       string System.String::Empty
    // IL_000d: ret          
    // IL_000e: ldarg.0      // this
    // IL_000f: call         instance int32 System.Text.StringBuilder::get_Length()
    // IL_0014: call         string System.String::FastAllocateString(int32)
    // IL_0019: stloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00a2 ldloc.0))]'
    // IL_001a: ldarg.0      // this
    // IL_001b: stloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_001c: ldloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00a2 ldloc.0))]'
    // IL_001d: stloc.3      // 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_001e ldloc.3))]'
    // IL_001e: ldloc.3      // 'str2 [Range(Instruction(IL_001d stloc.3)-Instruction(IL_001e ldloc.3))]'
    // IL_001f: conv.i       
    // IL_0020: stloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_006c ldloc.2))]'
    // IL_0021: ldloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_006c ldloc.2))]'
    // IL_0022: brfalse.s    IL_002c
    // IL_0024: ldloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_006c ldloc.2))]'
    // IL_0025: call         int32 System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData()
    // IL_002a: add          
    // IL_002b: stloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_006c ldloc.2))]'
    // IL_002c: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_002d: ldfld        int32 System.Text.StringBuilder::m_ChunkLength
    // IL_0032: ldc.i4.0     
    // IL_0033: ble.s        IL_0098
    // IL_0035: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_0036: ldfld        char[] System.Text.StringBuilder::m_ChunkChars
    // IL_003b: stloc.s      'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0062 ldloc.s))]'
    // IL_003d: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_003e: ldfld        int32 System.Text.StringBuilder::m_ChunkOffset
    // IL_0043: stloc.s      'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_006d ldloc.s))]'
    // IL_0045: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_0046: ldfld        int32 System.Text.StringBuilder::m_ChunkLength
    // IL_004b: stloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_0076 ldloc.s))]'
    // IL_004d: ldloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_0076 ldloc.s))]'
    // IL_004f: ldloc.s      'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_006d ldloc.s))]'
    // IL_0051: add          

    // IL_0052: ldloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00a2 ldloc.0))]'
    // IL_0053: callvirt     instance int32 System.String::get_Length()

    // IL_0058: bgt.un.s     IL_0083
    // IL_005a: ldloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_0076 ldloc.s))]'
    // IL_005c: ldloc.s      'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0062 ldloc.s))]'
    // IL_005e: ldlen        
    // IL_005f: conv.i4      
    // IL_0060: bgt.un.s     IL_0083
    // IL_0062: ldloc.s      'chArray [Range(Instruction(IL_003b stloc.s)-Instruction(IL_0062 ldloc.s))]'












    // IL_0064: ldc.i4.0     
    // IL_0065: ldelema      System.Char
    // IL_006a: stloc.s      V_7
    // IL_006c: ldloc.2      // 'chPtr [Range(Instruction(IL_0020 stloc.2)-Instruction(IL_006c ldloc.2))]'
    // IL_006d: ldloc.s      'num [Range(Instruction(IL_0043 stloc.s)-Instruction(IL_006d ldloc.s))]'
    // IL_006f: conv.i       
    // IL_0070: ldc.i4.2     
    // IL_0071: mul          
    // IL_0072: add          
    // IL_0073: ldloc.s      V_7
    // IL_0075: conv.i       
    // IL_0076: ldloc.s      'charCount [Range(Instruction(IL_004b stloc.s)-Instruction(IL_0076 ldloc.s))]'
    // IL_0078: call         void System.String::wstrcpy(char*, char*, int32)
    // IL_007d: ldc.i4.0     
    // IL_007e: conv.u       
    // IL_007f: stloc.s      V_7
    // IL_0081: br.s         IL_0098
    // IL_0083: ldstr        "chunkLength"
    // IL_0088: ldstr        "ArgumentOutOfRange_Index"
    // IL_008d: call         string System.Environment::GetResourceString(string)
    // IL_0092: newobj       instance void System.ArgumentOutOfRangeException::.ctor(string, string)
    // IL_0097: throw        
    // IL_0098: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_0099: ldfld        class System.Text.StringBuilder System.Text.StringBuilder::m_ChunkPrevious
    // IL_009e: stloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_009f: ldloc.1      // 'stringBuilder [Range(Instruction(IL_001b stloc.1)-Instruction(IL_009f ldloc.1))]'
    // IL_00a0: brtrue.s     IL_002c


    // IL_00a2: ldloc.0      // 'str1 [Range(Instruction(IL_0019 stloc.0)-Instruction(IL_00a2 ldloc.0))]'
    // IL_00a3: ret          
    // 
@jamesqo jamesqo commented on the diff Sep 1, 2016
src/mscorlib/src/System/Text/StringBuilder.cs
@@ -347,9 +350,9 @@ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext contex
int chunkLength = chunk.m_ChunkLength;
// Check that we will not overrun our boundaries.
- if ((uint)(chunkLength + chunkOffset) <= ret.Length && (uint)chunkLength <= (uint)sourceArray.Length)
+ if ((uint)(chunkLength + chunkOffset) <= (uint)ret.Length && (uint)chunkLength <= (uint)sourceArray.Length)
@jamesqo
jamesqo Sep 1, 2016 Contributor

Nice catch. I noticed that the rhs uint cast was missing before but did not bother submitting a PR for it.

@jamesqo
Contributor
jamesqo commented Sep 1, 2016

Interesting findings, I would not have known that using &array[0] and return inside the fixed statement would give different results. fixed statements are strange.

@jkotas
Member
jkotas commented Sep 1, 2016

Nice improvements. LGTM. Thanks!

@jkotas jkotas merged commit dc0bdc8 into dotnet:master Sep 2, 2016

10 checks passed

CentOS7.1 x64 Debug Build and Test Build finished.
Details
FreeBSD x64 Checked Build Build finished.
Details
Linux ARM Emulator Cross Debug Build Build finished.
Details
Linux ARM Emulator Cross Release Build Build finished.
Details
OSX x64 Checked Build and Test Build finished.
Details
Ubuntu x64 Checked Build and Test Build finished.
Details
Windows_NT x64 Debug Build and Test Build finished.
Details
Windows_NT x64 Release Priority 1 Build and Test Build finished.
Details
Windows_NT x86 legacy_backend Checked Build and Test Build finished.
Details
Windows_NT x86 ryujit Checked Build and Test Build finished.
Details
@jkotas
Member
jkotas commented Sep 2, 2016

Could you please port the changes to CoreRT as well when you get a chance? Thanks!

@bbowyersmyth bbowyersmyth deleted the bbowyersmyth:StringBuilderPerf branch Sep 2, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment