Пришло время выложить вторую часть заметок по .NET.
Файл конфигурации берет реванш.
*.CONFIG — действительно можно задавать любому приложению?
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
Случайно обнаружил что PowerGUI использует файл конфигурации для скриптов PowerShell которые компилируются в EXE (можно даже запаролить или сразу сделать службу).
Сами файлы: Untitled.exe и 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 — может сообщить JIT-компилятору что сборку не нужно оптимизировать. Значит в Release можно оптимизировать MSIL, а JIT-ом уже управлять через этот файл, не используя два разных билда.
[.NET Framework Debugging Control] GenerateTrackinglnfo = 1 AllowOptimize = 0
.Local — Dynamic-Link Library Redirection
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\Current Version\Image File Execution Options\
Создаем раздел реестра MyApp.EXE и внутри него новое строковое значение Debugger, в котором указываем полный путь к отладчику (если бы), пишем calc.exe.
Теперь при попытке запустить MyApp.EXE на самом деле будет запускаться калькулятор.
1) Немного истории C#
Обобщения, типы допускающие null, анонимные методы и улучшения с делегатами, итеративные блоки yield. Частичные типы, статические классы, свойства с отличающимися модификаторами доступа, псевдонимы пространств имен (локально — using WinForms = System.Windows.Forms; глобально -FirstAlias::Demo и SecondAlias::Demo), дерективы pragma, буферы фиксированного размера в небезопасном коде (fixed byte data[20]).
C# 3.0
LINQ, автоматические свойства, неявная типизация массивов и локальных переменных, инициализаторы объектов и коллекций в месте объявления, анонимные типы. Лямбда выражения и деревья выражений, расширяющие методы, частичные методы.
C# 4.0
Именованные аргументы, необязательные параметры, обобщенная вариантность, тип dynamic.
C# 5.0
Async/Await, изменение в цикле foreach, атрибуты информации о вызывающем компоненте.
2) Минимальный размер экземпляра ссылочного типа в памяти.
class MyClass { }
Компилирую в 32 бита, узнаю размер в Windbg:
0:005> !do 023849bc Name: ConsoleApplication1.MyClass MethodTable: 006c39d4 EEClass: 006c1904 Size: 12(0xc) bytes - вот он размер. File: E:\...\ConsoleApplication1.exe Fields: None
Компилирую в 64 бита:
0:003> !do 0000007c8d8465b8 Name: ConsoleApplication1.MyClass MethodTable: 00007ffa2b5c4320 EEClass: 00007ffa2b6d2548 Size: 24(0x18) bytes File: E:\...\ConsoleApplication1.exe Fields: None
Меньше не будет потому что первые 4 или 8 байт — слово заголовка объекта. Используется для синхронизации, хранения служебной информации сборщика мусора, финализации, хранения хэш-кода. Некоторые биты этого поля определяют, какая информация хранится в нем в каждый конкретный момент времени.
Вторые 4 или 8 байт — ссылка на таблицу методов.
Третьи 4 или 8 байт для данных и выравнивания, даже если в них ничего нет.
Итого минимальный размер экземпляра ссылочного типа для x86 — 12 байт, x64 — 24 байта.
3) Нестатические поля и методы экземпляра класса в памяти (x64).
class MyClass { private string _field1 = "Some string 1"; public string Field2 { get; set; } }
IL видим два поля:
.field private string '<Field2>k__BackingField' .field private string _field1
И два метода:
.method public hidebysig specialname instance string get_Field2() cil managed .method public hidebysig specialname instance void set_Field2(string 'value') cil managed
Посмотрим кто куда попал:
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
Поля попали прямо к экземпляру, и повлияли на его минимальный размер (32 потому что с 17 по 24 бит заняла первая ссылка (ранее были пустыми), а 25-32 вторая (что бы сохранить порядок их следования есть атрибут). Но методов непосредственно в экземпляре нет, только ссылка на них, и соответственно они не повлияли на его размер.
Посмотрим таблицу методов:
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)
А вот и они, оба еще не прошли JIT-компиляцию, кроме конструктора и унаследованных экземплярных методов от System.Object которые Ngen себя при установке .NET.
В заключение этого пункта посмотрим полный размер экземпляра с размером объектов на которые указывают его поля:
MyClass mcClass = new MyClass(); mcClass.Field2 = "Some string 2"; 0:003> !objsize 0000005400006600 sizeof(0000005400006600) = 144 (0x90) bytes (ConsoleApplication1.MyClass)
Проверим это посмотрев на размер полей:
0:003> !objsize 0000005400006620 sizeof(0000005400006620) = 56 (0x38) bytes (System.String) 0:003> !objsize 00000054000035a0 sizeof(00000054000035a0) = 56 (0x38) bytes (System.String)
Итого: 56 + 56 + 32 = 144.
4) Статическое поле и метод (х64).
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);
Минимальный размер экземпляра (статическое поле не учитывается):
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
Список методов:
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() — статический конструктор выполнился только потому что я обратился к статическому полю. Его также называют конструктором типа, и когда он точно вызывается не известно. Он создается автоматически при наличии статических полей. Если ни каких действий производить в нем не нужно то лучше не прописывать его явно, т.к. это помещает оптимизации при помощи флага beforefieldinit в метаданных. Подробней msdn.microsoft.com/ru-ru/library/dd335949.aspx.
Проверяем размеры:
0:003> !objsize 00000033ba2c65f8 sizeof(00000033ba2c65f8) = 72 (0x48) bytes (ConsoleApplication1.MyClass) 0:003> !objsize 00000033ba2c6610 sizeof(00000033ba2c6610) = 48 (0x30) bytes (System.String)
Итого: 24 + 48 = 72.
Статическое поле как и методы, не храниться копией в каждом экземпляре.
5) Найдем родителя и кто удерживает экземпляр от сборки мусора.
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
Сходим к родителю:
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
Кто удерживает mcClass = new MyClass():
0:003> !gcroot 00000033ba2c65f8 Thread 3310: 00000033b81fedb0 00007ffa2b6d031f ConsoleApplication1.Program.Main rbx: -> 00000033ba2c65f8 ConsoleApplication1.MyClass
Похоже на правду.
6) Кто такой Foreach.
2. Оператор foreach автоматически вызывает Dispose() в конце цикла, если был реализован интерфейс IDisposable.
3. Компилируется в вызов методов GetEnumerator(), MoveNext() и обращение к свойству Current.
4. Foreach как и yield return, LINQ — ленивая итерация, очень полезна когда например читаем мгогогигабайтный файл по одной строке за раз, экономя память.
5. Foreach для массива использует его свойство Length и индексатор массива, а не создает объект итератора.
6. В C# 5 захваченные переменные внутри циклов foreach теперь отрабатывают правильно, в то время как C# 3 и C# 4 захватят лишь один экземпляр переменной (последний).
7) LINQ
2. OrderBy для LINQ to Objects требует загрузки всех данных.
3. При использовании Join() в LINQ to Objects правая последовательность буферизируется, но для левой организуется поток, поэтому если нужно соединить крупную последовательность с мелкой, то полезно по возможности указывать мелкую последовательность как правую.
4. EnumType.Select( x => x ) — это называется вырожденным выражением запроса, результатом является просто последовательность элементов а не сам источник, это может быть важно с точки зрения целостности данных. (Справедливо для правильно спроектированных поставщиков данных LINQ.)
8) Коллекции.
Массивы всегда фиксированы по размеру, но изменяемы в терминах элементов.
LinkedList T — связанный список, позволяет быстро удалять, вставлять новые элементы, нет индекса, но проход по нему остается эффективным.
ReadOnlyDictionary — просто оболочка, которая скрывает все изменяемые операции за явной реализацией интерфейса. Можно изменять элементы через переданную в ее основу коллекцию.
9) Необязательные параметры метода.
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 — это константа. А константы хранятся напрямую в метаданных, и значит что для их изменения надо перекомпилировать все использующие этот метод код. (2 источник, страница 413.)
Начиная с C# 4 переименование параметров метода может повлиять на другой код, если в нем использовались именованные аргументы.
10) Оптимизация приложений на платформе .NET
Счетчики обновляются не чаще нескольких раз в секунду, а сам Performance Monitor не позволяет читать значения счетчиков чаще, чем один раз в секунду.
2. Event Tracing for Windows ETW.
Это высокопроизводительный фреймворк регистрации событий.
Читают события от ETW:
а) Windows Performance Toolkit.
б) PerfMonitor. (открытый проект команды CLR в Microsoft.)
в) PerfView. (бесплатный комбайн от Microsoft.)
3. Профилировщик памяти (помимо встроенного в VS) CLR Profiler.
Может подключаться к существующим процессам (CLR не ниже 4.0) или стартовать новые, собирает все события выделения памяти и сборки мусора. Строит кучу графиков.
Общие шаблоны для неправильно работающих многопоточных приложений.
11) Синхронизация.
lock ( obj ) { }
Выполняется только по требованию, затратна по времени. CLR создает структуру «sync block» в глобальном массиве «sync block table», она имеет обратную ссылку на объект владеющий блокировкой по слабой ссылке (для возможности утилизации) и еще ссылку на monitor реализованном на событиях Win32. Числовой индекс блока синхронизации храниться в слове заголовка объекта. Если объект синхронизации был утилизирован, то его связь с блоком синхронизации затирается для возможности повторного использования на другом объекте.
Но не все так просто, существует еще тонкая блокировка (thin lock). Если блок синхронизации еще не создан и только один поток владеет объектом, то другой при попытке выполнения будет ждать короткое время когда информация о владельце исчезнет из слова заголовка объекта, если этого не произойдет то тонкая блокировка будет преобразована в обычную.
12) Упаковка.
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 );
Производим запуск № 1.
Теперь добавим методы:
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; }
Производим запуск № 2.
Теперь добавим реализацию интерфейса (подходящий метод уже есть):
public struct Point : IEquatable<Point> { ... }
Производим запуск № 3.
(List T не имеет реализации интерфейса IEquatable T )
Пробуем анонимный тип:
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);
Производим запуск № 4.
Компилятор выяснил что тип someType идентичен типу в методах расширения, и поэтому проблем не возникло.
![](https://habrastorage.org/files/d9f/8b8/39b/d9f8b839b56c490a8c18ba3014b79b58.png)
А вот как выглядит анонимный тип в IL:
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
Тип значения хранит самое значение — 2 и 80000.
Таблица методов:
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()
Я тоже ожидал увидеть другое 🙂
13) Async/Await
Await — конечный автомат, структура. Если к моменту встречи этого типа результат работы уже будет доступен, то метод продолжит работу с полученным результатом в синхронном режиме. По разному ведет себя в консолях, WinForms, WPF… Через Task TResult.ConfigureAwait можно управлять в каком именно потоке должен вернуться результат работы.
14) Сборка мусора
В поколение «1» можно попасть только из «0», объекты имеющие финализацию точно окажутся в нем.
Размер поколения «2» не ограничивается искусственно, используется вся доступная память (которой Windows может поделиться с CLR), но GC не ждет ее полного заполнения а использует пороговые значения (какие не знаю).
На фазе маркировки объект помеченный как живой может потерять свою ссылку, тем самым пережив одну сборку.
В Large Object Heap попадают объекты больше 85 кбайт, но это относится к одному объекту а не его графу (включенные объекты). Связан с поколением «2», собираются вместе.
Источники:
1) Джон Роббинс «Отладка приложений для Microsoft .NET и Microsoft Windows».
2) Джон Скит «C# для профессионалов.Тонкости программирования», 3 издание.
3) Саша Голдштейн, Дима Зурбалев, Идо Флатов «Оптимизация приложений на платформе .Net».
Много букв получилось, JIT-компилятор остается на потом.
Спасибо за внимание.
ссылка на оригинал статьи http://habrahabr.ru/post/260047/
Добавить комментарий