Optimization of string immutability in CLRHttp://www.blogcn.com/user8/flier_lu/index.html? Id = 1269085 & run =. 06fe977
Since Programming Language Since then, how to deal with strings has been a constant debate. From C/C ++, strings are represented by character arrays, allowing users to fully control their lifecycles. to Delphi/Vb, strings are automatically maintained using reference counts through built-in support by the compiler; go to Java/C # and manage the lifecycle through immutable strings and garbage collection. Different strategies have different tendencies and have their own shortcomings and advantages. Here I don't want to comment on the advantages and disadvantages of multiple strategies, but I just want to make a little in-depth discussion on the implementation of C.
CLR selects an immutable string policy similar to Java to simplify lifecycle maintenance and multi-thread synchronization. However, it also pays a certain cost for efficiency and space, therefore, it has to be optimized through compiler-level customization.
Chris brumme and Yun Jin discussed the reasons for ensuring immutability on their blog, and pointed out that through pinvoke and unsafe Code The danger of directly modifying the string content.
Interning strings & immutability
Dangerous pinvokes-string Modification
To improve efficiency and save space, CLR actually maintains an immutable string table. The string allocated in the heap can be added to the table through the string. Intern function. The string. isinterned function is used to determine whether the table is in the table. If you are in a table, you can directly compare strings by referencing them, greatly improving the string comparison Efficiency. The example on msdn is as follows:
| Reference:
// Sample for string. Intern (string)
Using System;
Using System. text;
Class Sample {
Public Static Void Main () {
String S1 = " Mytest " ;
String S2 = New Stringbuilder (). append ( " My " ). Append ( " Test " ). Tostring ();
String S3 = String. Intern (S2 );
Console. writeline ( " S1 = '{0 }' " , S1 );
Console. writeline ( " S2 = '{0 }' " , S2 );
Console. writeline ( " S3 = '{0 }' " , S3 );
Console. writeline ( " Is S2 the same reference as S1? : {0} " , (Object) S2 = (Object) S1 );
Console. writeline ( " Is S3 the same reference as S1? : {0} " , (Object) S3 = (Object) S1 );
}
}
/**/ /*
This example produces the following results:
S1 = 'mytest'
S2 = 'mytest'
S3 = 'mytest'
Is S2 the same reference as S1? : False
Is S3 the same reference as S1? : True
*/
|
If you are familiar with the CLR metadata file structure, you may immediately think that the metadata table actually contains the # string stream and # US stream, respectivelyProgramAnd user string. For example, the above "mytest" string will be directly loaded into the stream, and the string table dynamically maintained by CLR is expanded on this basis.
Dynamically created string. For example, if the string constructed by stringbuilder in the preceding example is put in the heap by default, only the string is explicitly called by the user. the intern function is added to the static string table. Check the rotor code and you will find that string. Intern actually calls the getorinternstring function of the appdomain where the current thread is located, and further calls the getinternedstring function of the string ing table of this appdomain.
| Reference:
String. Intern (string Str) (bclsystemstring. CS: 1194 )
Thread. getdomain (). getorinternstring (STR)
Appdomain. getorinternstring (string Str) (bclsystemappdomain. CS: 1558 )
Internalcall
Basedomain: getorinternstring (stringref * Pstring) (vmappdomain. cpp: 856 )
M_pstringliteralmap -> Getinternedstring (pstring ,)
Appdomainstringliteralmap: getinternedstring () (vmstringliteralmap. cpp: 196 )
|
In the getorinternstring function, the hashcode is calculated based on the content of the string. Then, the hashcode is used to search for the string ing table (m_stringtoentryhashtable) of the current appdomain; if not, search for the global string ing table (systemdomain: getglobalstringliteralmap () of CLR. If not, the parameter determines whether to add the string to the global string ing table based on the hashcode index. (The getinternedstring function determines whether to add the string based on the baddifnotfound parameter.) If the current appdomain may be uninstalled, this string is also added to the local string ing table of the current appdomain using the hashcode index. The pseudocode is as follows:
| Reference:
Stringref * Appdomainstringliteralmap: getinternedstring (stringref * Pstring, bool baddifnotfound, bool bappdomainwontunload) { Stringliteralentry * Data; DWORD dwhash = M_stringtoentryhashtable -> Gethash (string data );
If (M_stringtoentryhashtable -> Getvalue ( & Stringdata, & Data, dwhash )) { ReturnData->Getstringobject (); } Else { Stringliteralentry * Pentry = Systemdomain: getglobalstringliteralmap () -> Getinternedstring (pstring, dwhash, baddifnotfound );
If (Pentry) { If ( ! Bappdomainwontunload) { M_stringtoentryhashtable->Insertvalue (&Stringdata, (lpvoid) pentry, false ); } } Else { ReturnPentry->Getstringobject (); } } }
|
another function, String. isinterned, actually calls exactly the same path, except that it is not automatically added when getinternedstring is not found in the string ing table (baddifnotfound = false ).
we can draw some conclusions:
1. The scope of intern string is the entire CLR, although each appdomain has an independent priority cache mechanism. This not only ensures query efficiency, but also ensures the consistency of strings in the shared Assembly loaded at different levels (such as CLR/appdomain.
2. The content in intern string directly determines its hashcode, and then its storage and index in the string table. Direct content modification may cause unknown problems. If you modify the content directly and then use string. isinterned, a completely different index item is returned.
3. Intern string can be directly compared through its reference. Because it is implicitly (solidified in the # string or # US stream of metadata) or displayed (call string. intern) when the string intern is used, strings with the same content will be located at the same entry of the string index table, and the same object reference will be returned.