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



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

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

    Tyts
    I launch the well-known utility from Mark R. "Procmon.exe", then my test window application and immediately close it, stop collecting events. In the received log I filter my application by name (Include). Here's what you see:

    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
    


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


    .INI - can tell the JIT compiler that the assembly does not need to be optimized. So in Release it is possible to optimize MSIL, and the JIT can be 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), write calc.exe.

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

    1) A bit of C # history
    What was added in different versions
    C # 2.0
    Generics, nullable types, anonymous methods and delegate enhancements, 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 at the place of declaration, anonymous types. Expression lambda and expression trees, extension methods, partial methods.

    C # 4.0
    Named arguments, optional parameters, generalized variation, type dynamic.

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


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

    Compile in 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
    

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

    It will not be smaller because the first 4 or 8 bytes is the title word of the object. It is used for synchronization, storage of garbage collector overhead, 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 is a link to the method table.
    Third 4 or 8 bytes for data and alignment, even if there is nothing in them.
    Total minimum size of an instance of a reference type for x86 is 12 bytes, x64 is 24 bytes.


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

    IL we see two fields:
    .field private string '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 k__BackingField
    


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

    Let's see 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 passed the JIT compilation, except for the constructor and the inherited instance methods from System.Object which Ngen themselves when installing .NET.

    In conclusion of this paragraph, we will look at the full size of the instance with the size of the objects indicated by its fields:
    MyClass mcClass = new MyClass();
    mcClass.Field2 = "Some string 2";
    0:003> !objsize 0000005400006600
    sizeof(0000005400006600) = 144 (0x90) bytes (ConsoleApplication1.MyClass)
    

    Let's check this 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);
    

    Minimum instance size (static field 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
    

    Method List:
    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 the static field. It is also called a type constructor, and when it is called exactly is not known. It is created automatically in the presence of static fields. If you don’t need to perform any actions in it, it’s better not to prescribe it explicitly, because this puts optimizations using the beforefieldinit flag in metadata. More details 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, should not be stored as a copy in each instance.


    5) Find the parent and who holds the instance 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
    

    Sounds like the truth.


    6) Who is Foreach.
    Tyts
    1. When using foreach, a hidden local variable is created - a loop iterator.

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

    3. It is 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, reading a mogogigabyte file one line at a time, saving memory.

    5. Foreach for an array uses its Length property and array indexer, and does not create an iterator object.

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


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

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

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

    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, this 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 the array. Adding a new element is either setting the value in the array, or copying the existing array into a new one, which is twice as large (undocumented) and only then setting the value. Removing an item from List T requires copying the items behind it to a position back. By the RemoveAt () index, deleting is much faster than by the Remove () value (each element is compared wherever it is).

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

    LinkedList T is a linked list, it allows you to quickly delete, insert new items, there is no index, but traversing it remains effective.

    ReadOnlyDictionary is just a wrapper that hides all mutable operations behind an explicit interface implementation. You can change elements through the collection passed 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.s    x
      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, which means that to change them, you must recompile all the code using this method. (2 sources, page 413.)

    Starting with C # 4, renaming method parameters can affect other code if named arguments are used.


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

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

    Read events from ETW:
    a) Windows Performance Toolkit .
    b) PerfMonitor . (Microsoft CLR team open project.)
    c) PerfView . (Free processor from Microsoft.)

    3. Memory profiler (in addition to the built-in VS) CLR Profiler .
    It can connect to existing processes (CLR no lower than 4.0) or start new ones; it collects all events of memory allocation and garbage collection. Builds a bunch of graphs.

    Common patterns for malfunctioning multithreaded applications .


    11) Synchronization.
    Tyk
    lock ( obj ) { }
    

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

    But not everything is so simple, there is still a thin lock. If the synchronization block has not yet been created and only one thread owns the object, then the other, when trying to execute, will wait a short time when information about the owner disappears from the header word of the object, if this does not happen, then a thin lock will be converted to normal.


    12) Packing.
    Nnada package
    We have the structure:
    public struct Point
    {
        public int X;
        public int Y;
    }
    List polygon = new List();
    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 launch No. 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 start No. 2.

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

    We launch No. 3.
    (List T has no IEquatable T interface implementation)

    Try an 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 start No. 4.
    The compiler found out that the type someType is identical to the type in the extension methods, and therefore there were no problems.

    Summary
    Test results:


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


    If interested, 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 i__Field
    0...0  4000004        c         System.Int32  1 instance            80000 i__Field
    

    The value type stores the value itself - 2 and 80000.

    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 something else :)


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

    A great free video tutorial 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 fixed objects in the “0” generation, then the CLR can declare this generation to be older, and select a new one for yourself.

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

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

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

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


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

    Many letters turned out, the JIT compiler remains for later.
    Thanks for attention.

    Also popular now: