📜 ⬆️ ⬇️

Interesting notes on C # and CLR (v2.0)



It's time to lay out the second part of the notes on .NET.

The configuration file takes revenge.
* .CONFIG - can you really ask any application?

Tyts
I launch the well-known utility from Mark R. “Procmon.exe”, then my test window application and immediately close, stop collecting events. I filter in the received log my application by name (Include). This is what it shows:
')
1)   config: 22:09:36.0364337 WindowsFormsApplication1.exe 7388 QueryOpen S:\WindowsFormsApplication1.exe.config NAME NOT FOUND 2)   INI: 22:09:36.0366595 WindowsFormsApplication1.exe 7388 QueryOpen S:\WindowsFormsApplication1.INI NAME NOT FOUND 3)   Local: 22:09:36.0537481 WindowsFormsApplication1.exe 7388 QueryOpen S:\WindowsFormsApplication1.exe.Local NAME NOT FOUND 


Accidentally discovered that PowerGUI uses the configuration file for PowerShell scripts that are compiled into EXE (you can even password-protect or immediately make the service).
The files themselves: Untitled.exe and Untitled.exe.config.

 <?xml version="1.0" encoding="utf-8" ?> <configuration> <startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" /> <supportedRuntime version="v2.0.50727" /> </startup> <runtime> <loadFromRemoteSources enabled="true"/> </runtime> </configuration> 

.INI - can tell the JIT compiler that the build does not need to be optimized. So in Release, you can optimize MSIL, and JIT is already managed through this file, without using two different builds.

 [.NET Framework Debugging Control] GenerateTrackinglnfo = 1 AllowOptimize = 0 

.Local - Dynamic-Link Library Redirection

Joke from john robbins
There is another place where the process looks.
HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows NT \ Current Version \ Image File Execution Options \

Create a registry key MyApp.EXE and inside it a new string value Debugger, in which we specify the full path to the debugger (if it were), we write calc.exe.

Now, when you try to run MyApp.EXE, the calculator will actually run.

1) A bit of C # history
What was added in different versions
C # 2.0
Generalizations, null-tolerant types, anonymous methods and delegate improvements, iterative yield blocks. Partial types, static classes, properties with different access modifiers, namespace aliases (locally using WinForms = System.Windows.Forms; globally -FirstAlias ​​:: Demo and SecondAlias ​​:: Demo), pragma directives, fixed-size buffers in unsafe code ( fixed byte data [20]).

C # 3.0
LINQ, automatic properties, implicit typing of arrays and local variables, initializers of objects and collections in the place of declaration, anonymous types. Lambda expressions and expression trees, extending methods, partial methods.

C # 4.0
Named arguments, optional parameters, generic variant, type dynamic.

C # 5.0
Async / Await, change in foreach loop, attributes information about the calling component.


2) The minimum size of an instance of a reference type in memory.
For x86 and x64
Create an empty class:
 class MyClass { } 

I compile to 32 bits, find out the size in Windbg:
 0:005> !do 023849bc Name: ConsoleApplication1.MyClass MethodTable: 006c39d4 EEClass: 006c1904 Size: 12(0xc) bytes -   . File: E:\...\ConsoleApplication1.exe Fields: None 

I compile to 64 bits:
 0:003> !do 0000007c8d8465b8 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5c4320 EEClass: 00007ffa2b6d2548 Size: 24(0x18) bytes File: E:\...\ConsoleApplication1.exe Fields: None 

Less will not be because the first 4 or 8 bytes - the word of the object header. It is used for synchronization, storage of service information of the garbage collector, finalization, storage of the hash code. Some bits of this field determine what information is stored in it at any given time.
The second 4 or 8 bytes - a link to the method table.
The third 4 or 8 bytes for data and alignment, even if they have nothing.
Total minimum size of an instance of the reference type for x86 - 12 bytes, x64 - 24 bytes.


3) Non-static fields and class instance methods in memory (x64).
Now add one field and auto-property
 class MyClass { private string _field1 = "Some string 1"; public string Field2 { get; set; } } 

IL see two fields:
 .field private string '<Field2>k__BackingField' .field private string _field1 

And two methods:
 .method public hidebysig specialname instance string get_Field2() cil managed .method public hidebysig specialname instance void set_Field2(string 'value') cil managed 

Let's see who got where:
 0:003> !do 0000005400006600 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5c4378 EEClass: 00007ffa2b6d2548 Size: 32(0x20) bytes File: E:\...\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00007ffa89d60e08 4000002 8 System.String 0 instance 0000005400006620 _field1 00007ffa89d60e08 4000003 10 System.String 0 instance 00000054000035a0 <Field2>k__BackingField 


The fields got directly to the instance, and affected its minimum size (32 because from 17 to 24 bits the first link occupied (previously they were empty), and 25-32 the second (to preserve the order of their sequence is an attribute). But the methods directly in there is no copy, only a link to them, and accordingly they did not affect its size.

Let's look at the table of methods:
 0:003> !dumpmt -md 00007ffa2b5c4378 EEClass: 00007ffa2b6d2548 Module: 00007ffa2b5c2fc8 Name: ConsoleApplication1.MyClass mdToken: 0000000002000003 File: E:\...\ConsoleApplication1.exe BaseSize: 0x20 ComponentSize: 0x0 Slots in VTable: 7 Number of IFaces in IFaceMap: 0 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 00007ffa89ae6300 00007ffa896980e8 PreJIT System.Object.ToString() 00007ffa89b2e760 00007ffa896980f0 PreJIT System.Object.Equals(System.Object) 00007ffa89b31ad0 00007ffa89698118 PreJIT System.Object.GetHashCode() 00007ffa89b2eb50 00007ffa89698130 PreJIT System.Object.Finalize() 00007ffa2b6e0390 00007ffa2b5c4358 JIT ConsoleApplication1.MyClass..ctor() 00007ffa2b5cc130 00007ffa2b5c4338 NONE ConsoleApplication1.MyClass.get_Field2() 00007ffa2b5cc138 00007ffa2b5c4348 NONE ConsoleApplication1.MyClass.set_Field2(System.String) 

And here they are, both have not yet been JIT-compiled, except for the constructor and the inherited instance methods from System.Object which Ngen themselves when installing .NET.

In conclusion of this clause, let's see the full size of the instance with the size of the objects that its fields indicate:
 MyClass mcClass = new MyClass(); mcClass.Field2 = "Some string 2"; 0:003> !objsize 0000005400006600 sizeof(0000005400006600) = 144 (0x90) bytes (ConsoleApplication1.MyClass) 

Check it out by looking at the size of the fields:
 0:003> !objsize 0000005400006620 sizeof(0000005400006620) = 56 (0x38) bytes (System.String) 0:003> !objsize 00000054000035a0 sizeof(00000054000035a0) = 56 (0x38) bytes (System.String) 

Total: 56 + 56 + 32 = 144.


4) Static field and method (x64).
Tyts
 class MyClass { private string _name = "Some string"; public static string _STR = "I'm STATIC"; public static void ImStaticMethod() { } } MyClass mcClass = new MyClass(); Console.WriteLine(MyClass._STR); 

The minimum size of the instance (static field is not taken into account):
 0:003> !do 00000033ba2c65f8 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5b4370 EEClass: 00007ffa2b6c2550 Size: 24(0x18) bytes File: E:\...\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00007ffa89d60e08 4000002 8 System.String 0 instance 00000033ba2c6610 _name 00007ffa89d60e08 4000003 10 System.String 0 static 00000033ba2c35a0 _STR 

List of methods:
 0:003> !dumpmt -md 00007ffa2b5b4370 EEClass: 00007ffa2b6c2550 Module: 00007ffa2b5b2fc8 Name: ConsoleApplication1.MyClass mdToken: 0000000002000003 File: E:\...\ConsoleApplication1.exe BaseSize: 0x18 ComponentSize: 0x0 Slots in VTable: 7 Number of IFaces in IFaceMap: 0 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 00007ffa89ae6300 00007ffa896980e8 PreJIT System.Object.ToString() 00007ffa89b2e760 00007ffa896980f0 PreJIT System.Object.Equals(System.Object) 00007ffa89b31ad0 00007ffa89698118 PreJIT System.Object.GetHashCode() 00007ffa89b2eb50 00007ffa89698130 PreJIT System.Object.Finalize() 00007ffa2b6d0110 00007ffa2b5b4350 JIT ConsoleApplication1.MyClass..cctor() 00007ffa2b6d03f0 00007ffa2b5b4348 JIT ConsoleApplication1.MyClass..ctor() 00007ffa2b5bc130 00007ffa2b5b4338 NONE ConsoleApplication1.MyClass.ImStaticMethod() 

ConsoleApplication1.MyClass..cctor () - the static constructor was executed only because I turned to a static field. It is also called a type constructor, and when it is exactly called is not known. It is created automatically in the presence of static fields. If you do not need to make any actions in it, then it is better not to prescribe it explicitly, since this places optimization with the help of the beforefieldinit flag in the metadata. For more information msdn.microsoft.com/ru-ru/library/dd335949.aspx .

Check sizes:
 0:003> !objsize 00000033ba2c65f8 sizeof(00000033ba2c65f8) = 72 (0x48) bytes (ConsoleApplication1.MyClass) 0:003> !objsize 00000033ba2c6610 sizeof(00000033ba2c6610) = 48 (0x30) bytes (System.String) 

Total: 24 + 48 = 72.
A static field, like methods, is not stored as a copy in each instance.


5) Find the parent and who keeps the copy from garbage collection.
For heap
Data and addresses from 3 points.
 0:003> !dumpclass 00007ffa2b6c2550 Class Name: ConsoleApplication1.MyClass mdToken: 0000000002000003 File: E:\...\ConsoleApplication1.exe Parent Class: 00007ffa89684908 Module: 00007ffa2b5b2fc8 Method Table: 00007ffa2b5b4370 Vtable Slots: 4 Total Method Slots: 6 Class Attributes: 100000 Transparency: Critical NumInstanceFields: 1 NumStaticFields: 1 MT Field Offset Type VT Attr Value Name 00007ffa89d60e08 4000002 8 System.String 0 instance _name 00007ffa89d60e08 4000003 10 System.String 0 static 00000033ba2c35a0 _STR 

We go to the parent:
 0:003> !dumpclass 00007ffa89684908 Class Name: System.Object mdToken: 0000000002000002 File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Parent Class: 0000000000000000 -  . Module: 00007ffa89681000 Method Table: 00007ffa89d613e8 Vtable Slots: 4 Total Method Slots: a Class Attributes: 102001 Transparency: Transparent NumInstanceFields: 0 NumStaticFields: 0 

Who holds mcClass = new MyClass ():
 0:003> !gcroot 00000033ba2c65f8 Thread 3310: 00000033b81fedb0 00007ffa2b6d031f ConsoleApplication1.Program.Main rbx: -> 00000033ba2c65f8 ConsoleApplication1.MyClass 

It looks like the truth.


6) Who is Foreach.
Tyts
1. Using foreach creates a hidden local variable - a loop iterator.

2. The foreach statement automatically calls Dispose () at the end of the loop if the IDisposable interface has been implemented.

3. Compiled into a call to the GetEnumerator (), MoveNext () methods and a call to the Current property.

4. Foreach, like yield return, LINQ is a lazy iteration, very useful when, for example, we read a multi-gigabyte file one line at a time, saving memory.

5. Foreach for an array uses its Length property and an array indexer, rather than creating an iterator object.

6. In C # 5, the captured variables inside the foreach loops are now working correctly, while C # 3 and C # 4 capture only one instance of the variable (the last one).


7) LINQ
Klats
1. LINQ to Object is executed in JIT as normal delegates (in-process query), LINQ to SQL builds an expression tree and executes it already in SQL, or any other medium. The tree can be translated to delegates.

2. OrderBy for LINQ to Objects requires loading all data.

3. When using Join () in LINQ to Objects, the right sequence is buffered, but for the left one a stream is organized, so if you want to connect a large sequence with a small one, then it is useful to specify the small sequence as right if possible.

4. EnumType.Select (x => x) - this is called a degenerate query expression, the result is just a sequence of elements and not the source itself, it can be important from the point of view of data integrity. (Valid for properly designed LINQ data providers.)


8) Collections.
Cry
List T - internally stores an array. Adding a new element is either setting a value in the array, or copying an existing array into a new one, which is twice as large (undocumented) and then setting the value. Removing an item from List T requires copying the items behind it to a position backward. By the RemoveAt () index, remove significantly faster than by the Remove () value (each element is compared wherever it is).

Arrays are always fixed in size, but changeable in terms of elements.

LinkedList T - linked list, allows you to quickly delete, insert new items, there is no index, but the passage on it remains effective.

ReadOnlyDictionary is just a wrapper that hides all mutable operations behind an explicit interface implementation. You can change items through the collection passed on to its base.


9) Optional method parameters.
Spoiler!
 void Method1( int x ) { x = 5; } IL: .method private hidebysig instance void Method1(int32 x) cil managed { // Code size 4 (0x4) .maxstack 8 IL_0000: ldc.i4.5 IL_0001: starg.sx IL_0003: ret } // end of method TestClass::Method1 void Method ( int x = 5 ) { } IL: .method private hidebysig instance void Method([opt] int32 x) cil managed { .param [1] = int32(0x00000005) // Code size 1 (0x1) .maxstack 8 IL_0000: ret } // end of method TestClass::Method 

int x is a constant. And the constants are stored directly in the metadata, and it means that to change them, you must recompile all the code using this method. (2 source, page 413.)

Starting in C # 4, renaming method parameters can affect other code if it uses named arguments.


10) Optimization of applications on the .NET platform
hacer clic
1. Performance counters .
Counters are updated no more than several times per second, while the Performance Monitor itself does not allow reading the values ​​of the counters more often than once per second.

2. Event Tracing for Windows ETW .
This is a high-performance event registration framework.

Read ETW events:
a) Windows Performance Toolkit .
b) PerfMonitor . (open project CLR team at Microsoft.)
c) PerfView . (Microsoft's free combine.)

3. Memory profiler (besides VS built-in) CLR Profiler .
It can connect to existing processes (CLR 4.0 or lower) or start new ones, collect all memory allocation and garbage collection events. Builds a bunch of graphs.

Common templates for malfunctioning multi-threaded applications .


11) Synchronization.
Tyk
 lock ( obj ) { } 

It is carried out only on demand, time-consuming. The CLR creates the “sync block” structure in the global “sync block table” array, it has a backward reference to the object that owns the lock on a weak link (for recyclability) and another link to the monitor implemented on Win32 events. The numeric index of the sync block is stored in the title word of the object. If the synchronization object has been disposed of, its connection with the synchronization unit is erased for reuse on another object.

But not everything is so simple, there is still a thin lock (thin lock). If the synchronization block has not been created yet and only one thread owns the object, another one will try to wait for a short time when the owner information disappears from the object title word, if this does not happen, then a thin lock will be converted to the normal one.


12) Packaging.
Package Nnada
We have structure:
 public struct Point { public int X; public int Y; } List<Point> polygon = new List<Point>(); for ( int i = 0; i < 10000000; i++ ) { polygon.Add( new Point() { X = rnd.Next(), Y = rnd.Next() } ); } Point point = new Point { X = 5, Y = 7 }; bool contains = polygon.Contains( point ); 

We are launching number 1.

Now add the methods:
 public override int GetHashCode() { return (X & Y) ^ X; //  . } public override bool Equals( object obj ) { if ( !( obj is Point ) ) return false; Point other = ( Point ) obj; return X == other.X && Y == other.Y; } public bool Equals( Point other ) { return X == other.X && Y == other.Y; } 

We are launching number 2.

Now add the interface implementation (there is already a suitable method):
 public struct Point : IEquatable<Point> { ... } 

We are launching number 3.
(List T has no IEquatable T interface implementation)

Try anonymous type:
 var someType = new { Prop1 = 2, Prop2 = 80000 }; var items = Enumerable.Range( 0, 10000000 ) .Select( i => new { Prop1 = i, Prop2 = i+i } ) .ToList(); items.Contains(someType); 

We are launching number 4.
The compiler found out that the type of someType is identical to the type in extension methods, and therefore there were no problems.

Results
Test results:


And this is what an anonymous type looks like in IL:


If you are wondering what someType looks like in memory
 var someType = new { Prop1 = 2, Prop2 = 80000 }; 0:005> !do 0000008da2745e08 Name: <>f__AnonymousType0`2[[System.Int32, mscorlib],[System.Int32, mscorlib]] MethodTable: 00007ffa2b5b4238 EEClass: 00007ffa2b6c2548 Size: 24(0x18) bytes File: E:\...\BoxingUnboxingPointList.exe Fields: MT Field Offset Type VT Attr Value Name 0...0 4000003 8 System.Int32 1 instance 2 <Prop1>i__Field 0...0 4000004 c System.Int32 1 instance 80000 <Prop2>i__Field 

The type of value stores the most value - 2 and 80,000.

Method table:
 0:005> !dumpmt -md 00007ffa2b5b4238 EEClass: 00007ffa2b6c2548 Module: 00007ffa2b5b2fc8 Name: <>f__AnonymousType0`2[[System.Int32, mscorlib],[System.Int32, mscorlib]] mdToken: 0000000002000004 File: E:\...\BoxingUnboxingPointList.exe BaseSize: 0x18 ComponentSize: 0x0 Slots in VTable: 7 Number of IFaces in IFaceMap: 0 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 0...8 0...0 NONE <>f__AnonymousType0`2[[...]].ToString() 0...0 0...8 NONE <>f__AnonymousType0`2[[...]].Equals(System.Object) 0...8 0...0 NONE <>f__AnonymousType0`2[[...]].GetHashCode() 0...0 0...0 PreJIT System.Object.Finalize() 0...0 0...8 NONE <>f__AnonymousType0`2[[...]]..ctor(Int32, Int32) 0...8 0...0 NONE <>f__AnonymousType0`2[[...]].get_Prop1() 0...0 0...8 NONE <>f__AnonymousType0`2[[...]].get_Prop2() 

I also expected to see more :)


13) Async / Await
Inside it is bigger
Async - has no representation in the generated code.
Await - state machine, structure. If by the time of this type of meeting the result of the work is already available, the method will continue to work with the result obtained in a synchronous mode. The Task TResult.ConfigureAwait method with a value of true will attempt to marshal the continuation back to the original captured context, if this is not required, we use the value false.

Excellent free video lesson on this topic from Alexander.

It is also very good to read the translation of the article " SynchronizationContext - when MSDN fails ."


14) Garbage Collection
Tyk
If you play with the fixed objects in the generation "0", then the CLR can declare this generation older, and select a new one for yourself.

The generation “1” can only be reached from “0”, objects having finalization will definitely be in it.

The size of generation “2” is not artificially limited, all available memory is used (which Windows can share with the CLR), but the GC does not wait for it to be completely filled, but uses threshold values ​​(which I don’t know).

In the marking phase, an object marked as live may lose its link, thus surviving one assembly.

Objects larger than 85 KB fall into the Large Object Heap, but this refers to one object and not its graph (included objects). Associated with the generation of "2", get together.


Sources:
1) John Robbins "Debugging Applications for Microsoft .NET and Microsoft Windows."
2) John Skit "C # for Professionals. Programming Subtleties", 3rd edition.
3) Sasha Goldstein, Dima Zurbalev, Ido Flatov “Application Optimization on the .Net Platform”.

A lot of letters turned out, the JIT compiler remains for later.
Thanks for attention.

Source: https://habr.com/ru/post/260047/


All Articles