Пришло время выложить вторую часть заметок по .NET.

Файл конфигурации берет реванш.
*.CONFIG — действительно можно задавать любому приложению?

Тыц
Запускаю известную утилиту от Марка Р. «Procmon.exe», затем свое тестовое оконное приложение и сразу закрываю, останавливаю сбор событий. Фильтрую в полученном логе свое приложение по имени (Include). Вот что там видно:

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#
Что добавлялось в разных версиях
C# 2.0
Обобщения, типы допускающие 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) Минимальный размер экземпляра ссылочного типа в памяти.
Для x86 и x64
Создаю пустой класс:
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) Найдем родителя и кто удерживает экземпляр от сборки мусора.
Для кучи
Данные и адреса с 3 пункта.
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.
Тыц
1. При использовании 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
Клац
1. LINQ to Object исполняется в JIT как обычные делегаты (внутрипроцессный запрос), LINQ to SQL строит дерево выражений и выполняет его уже SQL, или любая другая среда. Дерево может быть переведено в делегаты.

2. OrderBy для LINQ to Objects требует загрузки всех данных.

3. При использовании Join() в LINQ to Objects правая последовательность буферизируется, но для левой организуется поток, поэтому если нужно соединить крупную последовательность с мелкой, то полезно по возможности указывать мелкую последовательность как правую.

4. EnumType.Select( x => x ) — это называется вырожденным выражением запроса, результатом является просто последовательность элементов а не сам источник, это может быть важно с точки зрения целостности данных. (Справедливо для правильно спроектированных поставщиков данных LINQ.)


8) Коллекции.
Клик
List T — внутренне хранит массив. Добавление нового элемента это либо установление значения в массиве, либо копирование существующего массива в новый, который в два раза больше (недокументированно) и только потом установке значения. Удаление элемента из List T требует копирования расположенных за ним элементов на позицию назад. По индексу RemoveAt() удалять значительно быстрее чем по значению Remove() (происходит сравнение каждого элемента где бы он не находился).

Массивы всегда фиксированы по размеру, но изменяемы в терминах элементов.

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
hacer clic
1. Счетчики производительности.
Счетчики обновляются не чаще нескольких раз в секунду, а сам 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:


Если интересно, как выглядит someType в памяти
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
Внутри он больше
Async — не имеет представления в сгенерированном коде.
Await — конечный автомат, структура. Если к моменту встречи этого типа результат работы уже будет доступен, то метод продолжит работу с полученным результатом в синхронном режиме. Метод Task TResult.ConfigureAwait со значением true попытается выполнить маршалинг продолжения обратно в исходный захваченный контекст, если этого не требуется используем значение false.

Отличный бесплатный видео урок на эту тему от Александра.

Также очень хорошо прочитать перевод статьи "SynchronizationContext — когда MSDN подводит".


14) Сборка мусора
Тык
Если играться с закрепленными объектами в поколении «0», то CLR может объявить это поколение более старшим, а себе выделить новое.

В поколение «1» можно попасть только из «0», объекты имеющие финализацию точно окажутся в нем.

Размер поколения «2» не ограничивается искусственно, используется вся доступная память (которой Windows может поделиться с CLR), но GC не ждет ее полного заполнения а использует пороговые значения (какие не знаю).

На фазе маркировки объект помеченный как живой может потерять свою ссылку, тем самым пережив одну сборку.

В Large Object Heap попадают объекты больше 85 кбайт, но это относится к одному объекту а не его графу (включенные объекты). Связан с поколением «2», собираются вместе.


Источники:
1) Джон Роббинс «Отладка приложений для Microsoft .NET и Microsoft Windows».
2) Джон Скит «C# для профессионалов.Тонкости программирования», 3 издание.
3) Саша Голдштейн, Дима Зурбалев, Идо Флатов «Оптимизация приложений на платформе .Net».

Много букв получилось, JIT-компилятор остается на потом.
Спасибо за внимание.

Комментарии (30)


  1. lam0x86
    13.06.2015 21:49
    +4

    >> Отличный бесплатный видео урок на эту тему от Александра.
    «Вы, как начинающие программисты...»
    «Почему они назвали эту машину „Машиной состояний“ вам сейчас будет пока непонятно, но подождите, я сейчас всё объясню»
    и т.д.
    Как же мне не нравятся презентаторы, считающие себя умнее других… Если честно, он напоминает того самого брата из фильма Брат 2, не в обиду будет сказано :)


  1. lair
    13.06.2015 22:02
    +2

    Через Task TResult.ConfigureAwait можно управлять в каком именно потоке должен вернуться результат работы.

    Если вкратце, то нет, нельзя.

    Возьметесь доказать свое утверждение?


    1. Dywar Автор
      13.06.2015 22:25
      -1

      Отчасти так, детали не документированы явно. Передавая FALSE мы лишь сообщим что на с не заботит где выполниться продолжение, а не использовать специфический контекст.


      1. lair
        13.06.2015 22:37
        +1

        Вообще не так.

        Когда вы передаете false вы говорите, что система не должна пытаться вернуть выполнение в контекст. Когда вы передаете true (или, что эквивалентно, не вызываете метод), система будет пытаться вернуть вас в контекст, который был на момент запуска метода (если какой-то был), что в определенных случаях приводит к дедлокам.

        Но, что важно: это все не имеет никакого отношения к потокам, только к контексту. Яркий пример — asp.net, где выполнение скачет между потоками как угодно, но движок следит за тем, чтобы весь конекст сохранялся. Только на этой неделе отлавливали связанные с этим баги.


        1. Dywar Автор
          14.06.2015 09:44

          Согласен что сложно, именно поэтому «По разному ведет себя в консолях, WinForms, WPF…».
          Не имеет отношения к потокам? Тогда зачем Task и пул.
          Не мешайте все в одну кучу. Если вы очень хорошо знаете работу этого типа в контексте aps.net, то это не распространяется в равной степени на другие его формы.


          1. lair
            14.06.2015 09:47
            +2

            Согласен что сложно, именно поэтому «По разному ведет себя в консолях, WinForms, WPF…».

            На самом деле, формально — нет. Ведет себя строго одинаково — возвращает (или нет) на контекст. Это контексты отличаются.

            Не имеет отношения к потокам? Тогда зачем Task и пул.

            Пула там (явно) нет, есть только таск. А таск там именно затем, чтобы абстрагироваться от потоков. Task, async/await, вообще TPL — это абстракции более высокого порядка, нежели потоки.

            Если вы очень хорошо знаете работу этого типа в контексте aps.net, то это не распространяется в равной степени на другие его формы.

            Это не работа контекста, это работа рантайма. Он и без контекста себя так ведет.


            1. Dywar Автор
              14.06.2015 10:19
              -3

              «Люди видят то, что хотят видеть; слышат то, что хотят слышать; верят в то, во что хотят верить и отказываются верить в то, что им не нравится.» Скилеф

              Игра слов, вы безусловно правы, согласен с вами.


              1. 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?!
                }
                


                1. Dywar Автор
                  14.06.2015 17:24
                  -2

                  Вы серьезно?
                  В контексте asp.net имеется ввиду сам asp.net.


                  1. lair
                    14.06.2015 17:57
                    +2

                    Я совершенно серьезно, потому что везде в этой дискуссии я имел в виду другой контекст.


                    1. Dywar Автор
                      14.06.2015 18:42
                      -2

                      Согласен с вами, в asp.net поведение отличается от консольного или того же WinForms.

                      Именно для этого в топике предложение:
                      «По разному ведет себя в консолях, WinForms, WPF…», можно дописать asp.net, wcf и т.д.

                      Но изучить и описать в одном пункте сразу все возможные повороты я не могу, тем более что по словам Джона Скита «информация не документирована явно». И когда это средство впервые было представлено публике Хейлсбергом произошло много изменений, и не факт что их не будет в дальнейшем (без нарушения работы существующих решений).

                      Ваше замечание уместно и справедливо, спасибо что подсказали как оно в asp.net.

                      P.S. Два года назад я проходил курс внутреннего устройства Windows NT, и думаю что догадываюсь что такое контекст потока.


                      1. lair
                        14.06.2015 19:00
                        +2

                        Согласен с вами, в asp.net поведение отличается от консольного или того же WinForms.

                        Да нет же. Поведение await везде одинаково — захват контекста, и если тот существует, то continuation будет отправлен на него, а если нет — выполнен в произвольном контексте. Именно поэтому я сразу сказал, что управлять потоком вы не можете никак (разве что вы решите написать свой собственный шедулер/контекст).

                        Отличается только поведение контекстов синхронизации (и оно в среднем — за исключением консольных приложений — реализовано весьма логично).


                        1. lam0x86
                          14.06.2015 19:15

                          Извиняюсь, что влезаю не в тему, но судя по всему, тут собрались спецы в async/await-ах :) Мы в своём проекте используем Reactive Extensions, и в этом фреймворке довольно гибко реализована возможность написания юнит-тестов: можно ввести виртуальное время и исполнять задачи на специальном TestScheduler-е. А как с этим обстоит в TPL? То, что я нагуглил, оставляет довольно грустное впечатление.


                          1. lair
                            14.06.2015 19:17
                            +1

                            Я не видел никаких специальных расширений для тестирования времени в TPL. К сожалению.


                            1. lam0x86
                              14.06.2015 21:20

                              Спасибо, похоже на правду. (Ответил сам себе случайно. Должно было быть lair )


                        1. Dywar Автор
                          14.06.2015 19:20

                          Слава богу, разобрались.


                          1. lair
                            14.06.2015 19:21

                            Вот только в посте у вас все так же написано ложное утверждение.


                            1. Dywar Автор
                              14.06.2015 19:51

                              Исправил, спасибо.
                              Думал одно, писал другое.
                              Оказывается не документирована именно работа метода со значением FALSE.


          1. szKarlen
            14.06.2015 19:24

            По разному ведет себя в консолях, WinForms, WPF…

            это вообще как?

            таски очень завязаны на SyncronizationContext (о котором говорит товарищ lair).

            Пишите ли Вы простую либу с асинхронным методом, будьте любезны использовать ConfigureAwait(false).
            Вне зависимости от предназначения вашего кода.

            WPF, WinForms, ASP.NET, XYZ — все работает абсолютно идентично в вопросе async/await.


  1. 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.


    1. Dywar Автор
      14.06.2015 10:12

      У меня есть эта книжечка, спасибо что напомнили про нее.

      В разных источниках разные данные:
      Поколение 0 — изначально отводится от 256 Кбайт до 4 Мбайт, далее может меняется динамически. Он зависит от разрядности ОС, размера кэш памяти процессора L2 и L3.

      Поколение 1 — немногим больше поколения 0.

      Поколение 2 — не ограничен искусственно. 10 mb наверное первое пороговое значение, можно проверить в отладчике размер куч если интересно.

      * CLR резервирует блоки памяти (запрашивает у Windows), но не выдает их процессу сразу и полностью. Блока два, один SOH другой LOH, и с ними тоже есть свои тонкости и настройки (подробней в 3 источнике).


      1. 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%
        


        1. 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, то запускается полная сборка мусора, а значение суммы масс обнуляется. И так далее. Но это лишь мои догадки.


          1. Dywar Автор
            14.06.2015 11:48

            Согласен с вами. Я цитату прочитал внимательно уже когда тесты произвел, и понял что вы имели ввиду совсем другое.

            P.S. Gen 0 = почти равен L2; Gen 1 = L3. Но об этом потом.


  1. szKarlen
    14.06.2015 19:33

    1. 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.


  1. szKarlen
    14.06.2015 19:43

    Про сборку мусора… эх двумя словами и не скажешь.

    Так массив из System.Double при размере 10 600 элементов (85000 / 8 байт) должен попасть в LOH. Однако это происходит уже при размере 1000+.


    1. Dywar Автор
      14.06.2015 21:00

      Вы число точно написали, или это шутки такие :)

      10 000
      0: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
      


      1. szKarlen
        14.06.2015 22:30

        In 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.
        у Вас похоже пришел апдейт.


        1. Dywar Автор
          14.06.2015 22:50

          Спасибо, не знал.
          x32 .NET 4.5

          1001
          0: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
          


        1. JeStoneDev
          15.06.2015 02:10

          arrays > 1000 doubles

          Если быть более точным, то >= 1000