Информация об изменениях

Сообщение Re[43]: MS забило на дотнет. Питону - да, сишарпу - нет? от 05.09.2021 20:28

Изменено 05.09.2021 21:07 vdimas

Re[43]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:

V>>Не обязательно ограничена интеропом, просто хорошо подходит как доп. искуственно-вводимые разработчиком ограничения через систему типов.

V>>Для пущей безопасности, кароч, чтобы временные ссылки никуда случайно не утекли.
S>Ну так в том-то и дело, что не так много случаев, когда в гражданском коде мы будем пользоваться вот такой вот структурой "ограниченного действия".

ref struct вообще не выглядит средством для "гражданского кода". ))
Т.е., тут планка входа должна быть чуть выше, чем нулевая.
Это инструмент для тех, кто понимает происходящее.
Оно не сложное, это происходящее, но понимать его обязательно.


V>>Т.е. структуры, попадающие под ограничение unmanaged.

V>>В т.ч. в теле которых располагаются inplace-массивы примитивных типов или тоже unmanaged структур.
S>Ага, вы говорите про "массивы структур". Но ведь с ними и до этого особенных проблем не было

Были проблемы, я их обрисовал, похоже, ты не понял на словах.
Даю в коде.
Вот так было нельзя:
struct Struct1 {
    int a, b;
}

Struct1* ptr = stackalloc Struct1[42];


А вот так до сих пор нельзя в 5-м дотнете C# 9.0:
unsafe struct Struct2 {
    // error CS1663: Fixed size buffer type must be one of the following: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float or double
    fixed Struct1 inplaceArray[42];    
}


Я не проверял еще в 6-м дотнете, ты там вроде хвастался, что дёргал C# 10.0, самое время проверить.


S>Я-то имел в виду работу с flexible array member https://en.wikipedia.org/wiki/Flexible_array_member


fixed struct тебе в помощь, но за границей памяти следить самостоятельно:
    unsafe struct Struct2
    {
        public fixed byte implaceArray[1];
    }

    ...

    byte * ptr1 = stackalloc byte[10000];
    Struct2 * ptr2 = (Struct2 *)ptr1;

    // пишем за пределы структуры:
    ptr2->implaceArray[9999] = 42;



V>>А как GC должен знать, где в стеке управляемые данные, а где игнорить?

S>Программист ему подскажет, где можно безопасно игнорить, при помощи SuppressGCTransition.

Если бы еще нейтив вызывался исключительно для тривиальных вещей...
Но за подсказку о новой фиче спасибо.


V>>Стек замыкается в любом случае, т.к. GC stop world никто не отменял.

S>Для конкретного метода с инкрементом отключение GC Transition ускоряет вызов в натив вдвое. Если вычитать baseline — то втрое.

С инкрементом понятно.
Прямо сейчас не буду утверждать, но на вскидку не помню у себя случаев, чтобы нейтивный код не делал вызовов АПИ ОС, иначе такой код давно живёт в дотнете.
Т.е. в этом случае SuppressGCTransition использовать нельзя.

В прошлых проектах были вызовы медиа-кодеков из дотнета, но такие вещи лучше целиком убирать в нейтив, оставляя дотнету только управляющую роль — создать канал/стрим, настроить, запустить/остановить.


S>Для часто вызываемых микро-методов типа "набъём команды в буфер" — самое то.


На дотнетной стороне делается, например, в том же Unity в плагинах к драйверу.
Там в среднем 2-6 чисел в буфер надо напихать за раз, где первое число код команды, потом аргументы.
Вызывать ради этого нейтив не имеет смысла.


V>>Средней руки приложение может компиляться под AOT iOS несколько минут, сколько-нибудь большое — десятки минут.

V>>Поэтому, всё это херня собачья, гнаться за хотспотом.
S>Не уверен.

Я привел время, необходимое для оптимизирующей компиляции приложения.
Т.е., слишком много времени работы оптимизатора надо "размазывать" в процессе уже боевой работы программы.

Поэтому, ИМХО, альтернативы AOT нет.
Не зря Андроид практически полностью переехал на AOT.


V>>Ну, у нас зато тоже в некоторых проектах тонны статического-справочного кода, вот на такх проектах разница видна хорошо.

V>>И что характерно — если эти "справочные" данные бинарно сериализовать (не встроенной бинарной сериализацией, которой теперь тоже нет в .Net Core, а просто ручками), сохранить в ресурсах, а потом при старте прочесть — это занимает примерно 30 ms. А в исходном виде добавляло примерно 400 ms.
S>Непонятно, откуда тормоза.

От мегабайт кода инициализации справочников:
FieldDescriptor field1 = new FieldDescriptor (Tag42, DataType.Int32, "Field Description")

И вот такого кода бесконечно.
Джит его честно джитил ради однократного исполнения.
В случае хранения в ресурсах я экспериментировал — джитится только код десериализатора, загрузка одного массива байт из ресурсов быстрая всё отрабатывает на порядок с лишним быстрее.

В нейтиве такие вещи сидят в сегменте data, т.е. просто подгружаются в память при загрузке бинаря на исполнение, а в дотнете статические данные инициализируются по-старинке, как будто у нас всё еще есть апп-домены с динамическим выделением статических данных для каждого домена.


S>Ну так для того хотспот в джаве и умеет повторно джиттить уже отджиттенный код. Нагрузка поменялась => метод перекомпилировали.

V>>В нейтиве для этого отродясь было PGO.
V>>К АОТ его тоже надо прикручивать.
S>У PGO ровно та же самая проблема — устаревание данных.

Я плохо себе представляю сценарий, когда полностью оптимизированный код надо опять оптимизировать.
PGO управляет обычно выбором — меньше кода или больше инлайна.
Т.е. для холодных участков код можно сделать поменьше в объёме, для горячих — больше инлайна.
Но при максимальной оптимизации никакая дальнейшая оптимизация лучше не сделает.
Хуже — да, например, опять перекомпилять ход как холодный, т.е. уменьшив занимаемый кодом размер в памяти.


V>>Хотя, АОТ хорош тем, что не рассматривает типы как открытые, хотя бы по виртуальности/наследованию.

S>Это как это он так не рассматривает? А если я динамически код сгенерю?

Нельзя.


S>А если будет подгружена новая сборка?


Нельзя.
Эти вещи надо выносить как стадию сборки, например, это можно делать в LinqToDb, не обязательно заниматься кодогенерацией во время работы приложения.


V>>Плюс, у него достаточно времени на всевозможные оптимизации.

S>Для "настольного" применения — согласен, АОТ нужен. А для серверсайда, когда приложение живёт неделями, выделить 1% времени на дооптимизацию даст столько времени, что никакому АОТ столько не дадут.

Я вообще не понимаю, зачем это делать, особенно сейчас, когда всё-равно нет перезагрузки апп-доменов ввиду отсутствия оных.
Зачем нагружать компиляцией-оптимизацией боевую машину/машины?
Скомпиллировать-оптимизировать можно на машине, предназначенной для компиляции-оптимизации.
Re[43]: MS забило на дотнет. Питону - да, сишарпу - нет?
Здравствуйте, Sinclair, Вы писали:

V>>Не обязательно ограничена интеропом, просто хорошо подходит как доп. искуственно-вводимые разработчиком ограничения через систему типов.

V>>Для пущей безопасности, кароч, чтобы временные ссылки никуда случайно не утекли.
S>Ну так в том-то и дело, что не так много случаев, когда в гражданском коде мы будем пользоваться вот такой вот структурой "ограниченного действия".

ref struct вообще не выглядит средством для "гражданского кода". ))
Т.е., тут планка входа должна быть чуть выше, чем нулевая.
Это инструмент для тех, кто понимает происходящее.
Оно не сложное, это происходящее, но понимать его обязательно.


V>>Т.е. структуры, попадающие под ограничение unmanaged.

V>>В т.ч. в теле которых располагаются inplace-массивы примитивных типов или тоже unmanaged структур.
S>Ага, вы говорите про "массивы структур". Но ведь с ними и до этого особенных проблем не было

Были проблемы, я их обрисовал, похоже, ты не понял на словах.
Даю в коде.
Вот так было нельзя:
struct Struct1 {
    int a, b;
}

Struct1* ptr = stackalloc Struct1[42];


А вот так до сих пор нельзя в 5-м дотнете C# 9.0:
unsafe struct Struct2 {
    // error CS1663: Fixed size buffer type must be one of the following: bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float or double
    fixed Struct1 inplaceArray[42];    
}


Я не проверял еще в 6-м дотнете, ты там вроде хвастался, что дёргал C# 10.0, самое время проверить.


S>Я-то имел в виду работу с flexible array member https://en.wikipedia.org/wiki/Flexible_array_member


fixed struct тебе в помощь, но за границей памяти следить самостоятельно:
    unsafe struct Struct2
    {
        public fixed byte inplaceArray[1];
    }

    ...

    byte * ptr1 = stackalloc byte[10000];
    Struct2 * ptr2 = (Struct2 *)ptr1;

    // пишем за пределы структуры:
    ptr2->inplaceArray[9999] = 42;



V>>А как GC должен знать, где в стеке управляемые данные, а где игнорить?

S>Программист ему подскажет, где можно безопасно игнорить, при помощи SuppressGCTransition.

Если бы еще нейтив вызывался исключительно для тривиальных вещей...
Но за подсказку о новой фиче спасибо.


V>>Стек замыкается в любом случае, т.к. GC stop world никто не отменял.

S>Для конкретного метода с инкрементом отключение GC Transition ускоряет вызов в натив вдвое. Если вычитать baseline — то втрое.

С инкрементом понятно.
Прямо сейчас не буду утверждать, но на вскидку не помню у себя случаев, чтобы нейтивный код не делал вызовов АПИ ОС, иначе такой код давно живёт в дотнете.
Т.е. в этом случае SuppressGCTransition использовать нельзя.

В прошлых проектах были вызовы медиа-кодеков из дотнета, но такие вещи лучше целиком убирать в нейтив, оставляя дотнету только управляющую роль — создать канал/стрим, настроить, запустить/остановить.


S>Для часто вызываемых микро-методов типа "набъём команды в буфер" — самое то.


На дотнетной стороне делается, например, в том же Unity в плагинах к драйверу.
Там в среднем 2-6 чисел в буфер надо напихать за раз, где первое число код команды, потом аргументы.
Вызывать ради этого нейтив не имеет смысла.


V>>Средней руки приложение может компиляться под AOT iOS несколько минут, сколько-нибудь большое — десятки минут.

V>>Поэтому, всё это херня собачья, гнаться за хотспотом.
S>Не уверен.

Я привел время, необходимое для оптимизирующей компиляции приложения.
Т.е., слишком много времени работы оптимизатора надо "размазывать" в процессе уже боевой работы программы.

Поэтому, ИМХО, альтернативы AOT нет.
Не зря Андроид практически полностью переехал на AOT.


V>>Ну, у нас зато тоже в некоторых проектах тонны статического-справочного кода, вот на такх проектах разница видна хорошо.

V>>И что характерно — если эти "справочные" данные бинарно сериализовать (не встроенной бинарной сериализацией, которой теперь тоже нет в .Net Core, а просто ручками), сохранить в ресурсах, а потом при старте прочесть — это занимает примерно 30 ms. А в исходном виде добавляло примерно 400 ms.
S>Непонятно, откуда тормоза.

От мегабайт кода инициализации справочников:
FieldDescriptor field1 = new FieldDescriptor (Tag42, DataType.Int32, "Field Description")

И вот такого кода бесконечно.
Джит его честно джитил ради однократного исполнения.
В случае хранения в ресурсах я экспериментировал — джитится только код десериализатора, загрузка одного массива байт из ресурсов быстрая всё отрабатывает на порядок с лишним быстрее.

В нейтиве такие вещи сидят в сегменте data, т.е. просто подгружаются в память при загрузке бинаря на исполнение, а в дотнете статические данные инициализируются по-старинке, как будто у нас всё еще есть апп-домены с динамическим выделением статических данных для каждого домена.


S>Ну так для того хотспот в джаве и умеет повторно джиттить уже отджиттенный код. Нагрузка поменялась => метод перекомпилировали.

V>>В нейтиве для этого отродясь было PGO.
V>>К АОТ его тоже надо прикручивать.
S>У PGO ровно та же самая проблема — устаревание данных.

Я плохо себе представляю сценарий, когда полностью оптимизированный код надо опять оптимизировать.
PGO управляет обычно выбором — меньше кода или больше инлайна.
Т.е. для холодных участков код можно сделать поменьше в объёме, для горячих — больше инлайна.
Но при максимальной оптимизации никакая дальнейшая оптимизация лучше не сделает.
Хуже — да, например, опять перекомпилять ход как холодный, т.е. уменьшив занимаемый кодом размер в памяти.


V>>Хотя, АОТ хорош тем, что не рассматривает типы как открытые, хотя бы по виртуальности/наследованию.

S>Это как это он так не рассматривает? А если я динамически код сгенерю?

Нельзя.


S>А если будет подгружена новая сборка?


Нельзя.
Эти вещи надо выносить как стадию сборки, например, это можно делать в LinqToDb, не обязательно заниматься кодогенерацией во время работы приложения.


V>>Плюс, у него достаточно времени на всевозможные оптимизации.

S>Для "настольного" применения — согласен, АОТ нужен. А для серверсайда, когда приложение живёт неделями, выделить 1% времени на дооптимизацию даст столько времени, что никакому АОТ столько не дадут.

Я вообще не понимаю, зачем это делать, особенно сейчас, когда всё-равно нет перезагрузки апп-доменов ввиду отсутствия оных.
Зачем нагружать компиляцией-оптимизацией боевую машину/машины?
Скомпиллировать-оптимизировать можно на машине, предназначенной для компиляции-оптимизации.