Pitfalls in the string pool, or just another reason to think before interning String instances in C #

As software developers, we always want the software we write to run fast. Using the optimal algorithm, parallelizing, applying various optimization techniques - we will resort to all the means known to us in order to improve the performance of the software. One of these optimization techniques is the so-called string interning. It allows you to reduce the amount of memory consumed by the process, and also significantly reduces the time spent on comparing strings. However, as elsewhere in life, it is necessary to observe the measure - you should not use internment at every step. The rest of this article will show you how you can burn yourself and create an unobvious bottleneck for your application in the form of the String.Intern method.





, , , C# . , – , , String, .





, , : , . . 100 %, . , 1 , , 4,7 ( 100 ). , , , , . , .





, String . -, ( String Pool). , . , , , . , , String. String, . , ( ). : String.Intern String.IsInterned.





, , String. , . IsInterned . , null ( ).





, , Intern. , , . , , , . , . , , .





. String.Equals:





public bool Equals(String value)
{
  if (this == null)
    throw new NullReferenceException();
 
  if (value == null)
    return false;
 
  if (Object.ReferenceEquals(this, value))
    return true;
  
  if (this.Length != value.Length)
    return false;
 
  return EqualsHelper(this, value);
}

      
      



EqualsHelper, , Object.ReferenceEquals . , Object.ReferenceEquals true ( ). , , EqualsHelper . Equals , , false ReferenceEquals .





, , Object.ReferenceEquals. . - , – . ReferenceEquals , .





, . , . .





, , , .





,

bug tracker' - , : ++ . , PVS-Studio . , , IncrediBuild. IncrediBuild , , . , , , , ( ), . .





, , PVS-Studio , IncrediBuild, , . – . , .





open source Unreal Tournament. IncrediBuild . 145 .





Unreal Tournament PVS-Studio, . CLMonitor.exe , Unreal Tournament Visual Studio. , , CLMonitor.exe, . PVS-Studio ThreadCount, CLMonitor.exe PVS-Studio.exe, ++ . PVS-Studio.exe , CLMonitor.exe.





: ThreadCount PVS-Studio, (145), , , 145 PVS-Studio.exe . IncrediBuild Build Monitor, , . - :





, : , , IncrediBuild . ...





,

, PVS-Studio.exe Build Monitor. IncrediBuild IncrediBuild. , , , : 182 8 50 IncrediBuild 145 . , 18 , 3,5 . Build Monitor. , :





, PVS-Studio.exe , - PVS-Studio.exe. . . . , , – IncrediBuild. - .





. , , . , CLMonitor.exe , . "" , CLMonitor.exe Visual Studio . Threads, 145 . , , :





....
return String.Intern(settings == null ? path
                                 : settings
                                 .TransformToRelative(path.Replace("/", "\\"),
                                                      solutionDirectory));
....
analyzedSourceFiles.Add( String.Intern(settings
                        .TransformPathToRelative(analyzedSourceFilePath, 
                                                 solutionDirectory))
                       );
....

      
      



? String.Intern. . , , CLMonitor.exe , PVS-Studio.exe. ErrorInfo, . , . , , ErrorInfo . .





, . , . - 145 String.Intern, LimitedConcurrencyLevelTaskScheduler CLMonitor.exe , PVS-Studio.exe, IncrediBuild . , , – PVS-Studio.exe ErrorInfo . , PVS-Studio.exe . , 145 .





ThreadCount , , String.Intern.





, CLMonitor.exe. : , PVS-Studio.exe ( , ).





, . Build Monitor - PVS-Studio.exe. 50 26, . , IncrediBuild 145 , 7 . , 3,5 .





String.Intern – , CoreCLR

, String.Intern, , - lock'. , String.Intern - , , , . , String.Intern reference source. , – Thread.GetDomain().GetOrInternString(str). , :





internal extern String GetOrInternString(String str);

      
      



. - . ? - CLR, .NET. , CoreCLR. , GetOrInternString :





STRINGREF *BaseDomain::GetOrInternString(STRINGREF *pString)

      
      



GetInternedString. , :





....
if (m_StringToEntryHashTable->GetValue(&StringData, &Data, dwHash))
{
  STRINGREF *pStrObj = NULL;
  pStrObj = ((StringLiteralEntry*)Data)->GetStringObject();
  _ASSERTE(!bAddIfNotFound || pStrObj);
  return pStrObj;
}
else
{
  CrstHolder gch(&(SystemDomain::GetGlobalStringLiteralMap()
                                   ->m_HashTableCrstGlobal));
  ....
  // Make sure some other thread has not already added it.
  if (!m_StringToEntryHashTable->GetValue(&StringData, &Data))
  {
    // Insert the handle to the string into the hash table.
    m_StringToEntryHashTable->InsertValue(&StringData, (LPVOID)pEntry, FALSE);
  }
  ....
}
....

      
      



else , , String ( GetValue) , false. , else. CrstHolder gch. CrstHolder :





inline CrstHolder(CrstBase * pCrst)
    : m_pCrst(pCrst)
{
    WRAPPER_NO_CONTRACT;
    AcquireLock(pCrst);
}

      
      



AcquireLock. . AcquireLock:





DEBUG_NOINLINE static void AcquireLock(CrstBase *c)
{
  WRAPPER_NO_CONTRACT;
  ANNOTATION_SPECIAL_HOLDER_CALLER_NEEDS_DYNAMIC_CONTRACT;
  c->Enter();
}

      
      



, , – Enter. , , , , : "Acquire the lock". CoreCLR . , , , , , . CrstHolder, , m_StringToEntryHashTable->InsertValue.





, else. gch , ReleaseLock:





inline ~CrstHolder()
{
  WRAPPER_NO_CONTRACT;
  ReleaseLock(m_pCrst);
}

      
      



, . , 145 ( IncrediBuild), , , 144 , . Build Monitor.





, , . " ", . , , .





.





, : Ilya Gainulin. Pitfalls in String Pool, or Another Reason to Think Twice Before Interning Instances of String Class in C#.








All Articles