Пришло время выложить вторую часть заметок по .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 идентичен типу в методах расширения, и поэтому проблем не возникло.
А вот как выглядит анонимный тип в 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 — конечный автомат, структура. Если к моменту встречи этого типа результат работы уже будет доступен, то метод продолжит работу с полученным результатом в синхронном режиме. Метод Task TResult.ConfigureAwait со значением true попытается выполнить маршалинг продолжения обратно в исходный захваченный контекст, если этого не требуется используем значение false.
Отличный бесплатный видео урок на эту тему от Александра.
Также очень хорошо прочитать перевод статьи "SynchronizationContext — когда MSDN подводит".
14) Сборка мусора
В поколение «1» можно попасть только из «0», объекты имеющие финализацию точно окажутся в нем.
Размер поколения «2» не ограничивается искусственно, используется вся доступная память (которой Windows может поделиться с CLR), но GC не ждет ее полного заполнения а использует пороговые значения (какие не знаю).
На фазе маркировки объект помеченный как живой может потерять свою ссылку, тем самым пережив одну сборку.
В Large Object Heap попадают объекты больше 85 кбайт, но это относится к одному объекту а не его графу (включенные объекты). Связан с поколением «2», собираются вместе.
Источники:
1) Джон Роббинс «Отладка приложений для Microsoft .NET и Microsoft Windows».
2) Джон Скит «C# для профессионалов.Тонкости программирования», 3 издание.
3) Саша Голдштейн, Дима Зурбалев, Идо Флатов «Оптимизация приложений на платформе .Net».
Много букв получилось, JIT-компилятор остается на потом.
Спасибо за внимание.
Комментарии (30)
lair
13.06.2015 22:02+2Через Task TResult.ConfigureAwait можно управлять в каком именно потоке должен вернуться результат работы.
Если вкратце, то нет, нельзя.
Возьметесь доказать свое утверждение?Dywar Автор
13.06.2015 22:25-1Отчасти так, детали не документированы явно. Передавая FALSE мы лишь сообщим что на с не заботит где выполниться продолжение, а не использовать специфический контекст.
lair
13.06.2015 22:37+1Вообще не так.
Когда вы передаетеfalse
вы говорите, что система не должна пытаться вернуть выполнение в контекст. Когда вы передаетеtrue
(или, что эквивалентно, не вызываете метод), система будет пытаться вернуть вас в контекст, который был на момент запуска метода (если какой-то был), что в определенных случаях приводит к дедлокам.
Но, что важно: это все не имеет никакого отношения к потокам, только к контексту. Яркий пример — asp.net, где выполнение скачет между потоками как угодно, но движок следит за тем, чтобы весь конекст сохранялся. Только на этой неделе отлавливали связанные с этим баги.Dywar Автор
14.06.2015 09:44Согласен что сложно, именно поэтому «По разному ведет себя в консолях, WinForms, WPF…».
Не имеет отношения к потокам? Тогда зачем Task и пул.
Не мешайте все в одну кучу. Если вы очень хорошо знаете работу этого типа в контексте aps.net, то это не распространяется в равной степени на другие его формы.lair
14.06.2015 09:47+2Согласен что сложно, именно поэтому «По разному ведет себя в консолях, WinForms, WPF…».
На самом деле, формально — нет. Ведет себя строго одинаково — возвращает (или нет) на контекст. Это контексты отличаются.
Не имеет отношения к потокам? Тогда зачем Task и пул.
Пула там (явно) нет, есть только таск. А таск там именно затем, чтобы абстрагироваться от потоков. Task, async/await, вообще TPL — это абстракции более высокого порядка, нежели потоки.
Если вы очень хорошо знаете работу этого типа в контексте aps.net, то это не распространяется в равной степени на другие его формы.
Это не работа контекста, это работа рантайма. Он и без контекста себя так ведет.Dywar Автор
14.06.2015 10:19-3«Люди видят то, что хотят видеть; слышат то, что хотят слышать; верят в то, во что хотят верить и отказываются верить в то, что им не нравится.» Скилеф
Игра слов, вы безусловно правы, согласен с вами.lair
14.06.2015 15:40+3Вы считаете разницу между потоком и контекстом — игрой слов?
Казалось бы, простой пример:
Thread.CurrentPrincipal = GePrincipal(); if (Thread.CurrentPrincipal.Identity.IsAuthenticated) { await DoSomeWork(); //continuation will run on captured context Debug.Assert(Thread.CurrentPrincipal.Identity.IsAutenticated); //WTF?! }
Dywar Автор
14.06.2015 17:24-2Вы серьезно?
В контексте asp.net имеется ввиду сам asp.net.lair
14.06.2015 17:57+2Я совершенно серьезно, потому что везде в этой дискуссии я имел в виду другой контекст.
Dywar Автор
14.06.2015 18:42-2Согласен с вами, в asp.net поведение отличается от консольного или того же WinForms.
Именно для этого в топике предложение:
«По разному ведет себя в консолях, WinForms, WPF…», можно дописать asp.net, wcf и т.д.
Но изучить и описать в одном пункте сразу все возможные повороты я не могу, тем более что по словам Джона Скита «информация не документирована явно». И когда это средство впервые было представлено публике Хейлсбергом произошло много изменений, и не факт что их не будет в дальнейшем (без нарушения работы существующих решений).
Ваше замечание уместно и справедливо, спасибо что подсказали как оно в asp.net.
P.S. Два года назад я проходил курс внутреннего устройства Windows NT, и думаю что догадываюсь что такое контекст потока.lair
14.06.2015 19:00+2Согласен с вами, в asp.net поведение отличается от консольного или того же WinForms.
Да нет же. Поведениеawait
везде одинаково — захват контекста, и если тот существует, то continuation будет отправлен на него, а если нет — выполнен в произвольном контексте. Именно поэтому я сразу сказал, что управлять потоком вы не можете никак (разве что вы решите написать свой собственный шедулер/контекст).
Отличается только поведение контекстов синхронизации (и оно в среднем — за исключением консольных приложений — реализовано весьма логично).lam0x86
14.06.2015 19:15Извиняюсь, что влезаю не в тему, но судя по всему, тут собрались спецы в async/await-ах :) Мы в своём проекте используем Reactive Extensions, и в этом фреймворке довольно гибко реализована возможность написания юнит-тестов: можно ввести виртуальное время и исполнять задачи на специальном TestScheduler-е. А как с этим обстоит в TPL? То, что я нагуглил, оставляет довольно грустное впечатление.
szKarlen
14.06.2015 19:24По разному ведет себя в консолях, WinForms, WPF…
это вообще как?
таски очень завязаны на SyncronizationContext (о котором говорит товарищ lair).
Пишите ли Вы простую либу с асинхронным методом, будьте любезны использовать ConfigureAwait(false).
Вне зависимости от предназначения вашего кода.
WPF, WinForms, ASP.NET, XYZ — все работает абсолютно идентично в вопросе async/await.
JeStoneDev
14.06.2015 08:41+2Размер поколения «2» не ограничивается искусственно, используется вся доступная память (которой Windows может поделиться с CLR), но GC не ждет ее полного заполнения а использует пороговые значения (какие не знаю).
Цитата из «Under the Hood of .NET Management» от Red-Gate:
The GC runs automatically on a separate thread under one of the conditions below.
• When the size of objects in any generation reaches a generation-specific threshold. To
be precise, when:
• Gen 0 hits ~256 K
• Gen 1 hits ~ 2 MB (at which point the GC collects Gen 1 and 0)
• Gen 2 hits ~10 MB (at which point the GC collects Gen 2, 1 and 0)
• GC.Collect() is called in code
• the OS sends a low memory notification.Dywar Автор
14.06.2015 10:12У меня есть эта книжечка, спасибо что напомнили про нее.
В разных источниках разные данные:
Поколение 0 — изначально отводится от 256 Кбайт до 4 Мбайт, далее может меняется динамически. Он зависит от разрядности ОС, размера кэш памяти процессора L2 и L3.
Поколение 1 — немногим больше поколения 0.
Поколение 2 — не ограничен искусственно. 10 mb наверное первое пороговое значение, можно проверить в отладчике размер куч если интересно.
* CLR резервирует блоки памяти (запрашивает у Windows), но не выдает их процессу сразу и полностью. Блока два, один SOH другой LOH, и с ними тоже есть свои тонкости и настройки (подробней в 3 источнике).Dywar Автор
14.06.2015 11:11Что бы убрать сомнения и разобраться самому, приведу результаты тестов консольного приложения в котором создается 10 млн (+1) Point и затем сразу 10 млн (+1) анонимных типов. Сбор данных идет до запуска циклов и после.
До0:003> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x000000c200001030 generation 1 starts at 0x000000c200001018 generation 2 starts at 0x000000c200001000 ephemeral segment allocation context: none segment begin allocated size 000000c200000000 000000c200001000 000000c200005fe8 0x4fe8(20456) Large object heap starts at 0x000000c210001000 segment begin allocated size 000000c210000000 000000c210001000 000000c210009728 0x8728(34600) Total Size: Size: 0xd710 (55056) bytes. ------------------------------ GC Heap Size: Size: 0xd710 (55056) bytes. 0:003> !heapstat Heap Gen0 Gen1 Gen2 LOH Heap0 20408 24 24 34600 Free space: Percentage Heap0 24 0 0 152SOH: 0% LOH: 0%
JeStoneDev
14.06.2015 11:36В принципе, цитата и не утверждает, что Gen 2 ограничен размером ~10MB. Это очевидно. Гораздо более интересно, что означает ~10MB при неограниченном размере Gen 2. В книге подробной информации по этому поводу нет.
Я предполагаю, что это не первый порог, а итеративное значение суммы масс всех объектов, перешедших с Gen 1 в Gen 2 с момента последнего запуска Full GC collects (Gen 2, 1 and 0). Если эта сумма равна или больше ~10MB, то запускается полная сборка мусора, а значение суммы масс обнуляется. И так далее. Но это лишь мои догадки.Dywar Автор
14.06.2015 11:48Согласен с вами. Я цитату прочитал внимательно уже когда тесты произвел, и понял что вы имели ввиду совсем другое.
P.S. Gen 0 = почти равен L2; Gen 1 = L3. Но об этом потом.
szKarlen
14.06.2015 19:331. LINQ to Object исполняется в JIT как обычные делегаты (внутрипроцессный запрос), LINQ to SQL строит дерево выражений и выполняет его уже SQL, или любая другая среда. Дерево может быть переведено в делегаты.
Не все так просто :)
LINQ to SQL — есть тот же самый LINQ to Everything, правда подставляется свой провайдер IQueryable. исполняет его CLR (ваш K.O).
Дерево переводится в таких случаях для делегаты с переписыванием самого дерева (привет CompiledQuery).
SQL генерируется и передается в ADO.NET или xyz.
Поэтому всегда можно создать LINQ to anything.
А так, да, дерево выражений всегда можно перевести в делегат через LambdaExpression.
szKarlen
14.06.2015 19:43Про сборку мусора… эх двумя словами и не скажешь.
Так массив из System.Double при размере 10 600 элементов (85000 / 8 байт) должен попасть в LOH. Однако это происходит уже при размере 1000+.Dywar Автор
14.06.2015 21:00Вы число точно написали, или это шутки такие :)
10 0000:003> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x0000002a13ec1030 generation 1 starts at 0x0000002a13ec1018 generation 2 starts at 0x0000002a13ec1000 ephemeral segment allocation context: none segment begin allocated size 0000002a13ec0000 0000002a13ec1000 0000002a13edb898 0x1a898(108696) Large object heap starts at 0x0000002a23ec1000 segment begin allocated size 0000002a23ec0000 0000002a23ec1000 0000002a23ec9728 0x8728(34600) Total Size: Size: 0x22fc0 (143296) bytes. ------------------------------ GC Heap Size: Size: 0x22fc0 (143296) bytes. 0:003> !heapstat Heap Gen0 Gen1 Gen2 LOH Heap0 108648 24 24 34600 Free space: Percentage Heap0 24 0 0 152SOH: 0% LOH: 0% 0:003> !dumpheap 0x0000002a23ec1000 Address MT Size 0000002a23ec1000 0000002a11e9b380 24 Free 0000002a23ec1018 0000002a11e9b380 30 Free 0000002a23ec1038 00007ffefe1b4918 8848 0000002a23ec32c8 0000002a11e9b380 30 Free 0000002a23ec32e8 00007ffefe1b4918 1056 0000002a23ec3708 0000002a11e9b380 30 Free 0000002a23ec3728 00007ffefe1b4918 8192 0000002a23ec5728 0000002a11e9b380 30 Free 0000002a23ec5748 00007ffefe1b4918 16352 Statistics: MT Count TotalSize Class Name 0000002a11e9b380 5 144 Free 00007ffefe1b4918 4 34448 System.Object[] Total 9 objects 0:003> !dumpheap -type System.Double[] Address MT Size 0000002a13ec6a08 00007ffefe218450 80024 Statistics: MT Count TotalSize Class Name 00007ffefe218450 1 80024 System.Double[] Total 1 objects 0:003> !do 0000002a13ec6a08 Name: System.Double[] MethodTable: 00007ffefe218450 EEClass: 00007ffefdc05138 Size: 80024(0x13898) bytes Array: Rank 1, Number of elements 10000, Type Double Fields: None
szKarlen
14.06.2015 22:30In 32-bit architrctures CLR’s execution engine attempts to place these arrays > 1000 doubles on the LOH because of the performance benefit of accessing aligned doubles. However there are no benefits of applying the same heuristics on 64-bit architectures because doubles are already aligned on an 8-byte boundary. As such we have disabled this heuristics for 64-bit architectures in .,NET 4.5
Thanks,
Abhishek Mondal
Program Manager
Common Language Runtime
http://blogs.msdn.com/b/dotnet/archive/2011/10/04/large-object-heap-improvements-in-net-4-5.aspx
и еще
http://stackoverflow.com/questions/11791038/large-object-heap-fragmentation-issues-with-arrays
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=266330&wa=wsignin1.0
p.s.
у Вас похоже пришел апдейт.Dywar Автор
14.06.2015 22:50Спасибо, не знал.
x32 .NET 4.5
10010:003> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x02c01018 generation 1 starts at 0x02c0100c generation 2 starts at 0x02c01000 ephemeral segment allocation context: none segment begin allocated size 02c00000 02c01000 02c05ff4 0x4ff4(20468) Large object heap starts at 0x03c01000 segment begin allocated size 03c00000 03c01000 03c07300 0x6300(25344) Total Size: Size: 0xb2f4 (45812) bytes. ------------------------------ GC Heap Size: Size: 0xb2f4 (45812) bytes. 0:003> !heapstat Heap Gen0 Gen1 Gen2 LOH Heap0 20444 12 12 25344 Free space: Percentage Heap0 12 0 0 96SOH: 0% LOH: 0% 0:003> !dumpheap -type System.Double[] Address MT Size 03c053a8 70bafe70 8020 Statistics: MT Count TotalSize Class Name 70bafe70 1 8020 System.Double[] Total 1 objects 0:003> !do 03c053a8 Name: System.Double[] MethodTable: 70bafe70 EEClass: 70811530 Size: 8020(0x1f54) bytes Array: Rank 1, Number of elements 1001, Type Double Fields: None 0:003> !dumpheap 0x03c01000 Address MT Size 03c01000 01230af8 10 Free 03c01010 01230af8 14 Free 03c01020 70b60cbc 4424 03c02168 01230af8 14 Free 03c02178 70b60cbc 528 03c02388 01230af8 14 Free 03c02398 70b60cbc 4096 03c03398 01230af8 14 Free 03c033a8 70b60cbc 8176 03c05398 01230af8 14 Free 03c053a8 70bafe70 8020 Statistics: MT Count TotalSize Class Name 01230af8 6 80 Free 70bafe70 1 8020 System.Double[] 70b60cbc 4 17224 System.Object[] Total 11 objects
lam0x86
>> Отличный бесплатный видео урок на эту тему от Александра.
«Вы, как начинающие программисты...»
«Почему они назвали эту машину „Машиной состояний“ вам сейчас будет пока непонятно, но подождите, я сейчас всё объясню»
и т.д.
Как же мне не нравятся презентаторы, считающие себя умнее других… Если честно, он напоминает того самого брата из фильма Брат 2, не в обиду будет сказано :)