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

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

Изменено 02.09.2021 3:41 vdimas

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

V>>Разве что абстракции бесплатны и способ их выбора прост.

S>При правильной реализации абстракции в дотнете тоже близки к бесплатным.

Это если транзитивно протягивать зависимость от платформы в свой код, т.е. генерить разные бинари под разные платформы, как это происходит в системных либах.
Тогда можно сделать без абстракций (в дотнетном смысле), просто задействуя разный код для разных конечных сборок.

Но в случае дотнета решиться на такое не очень просто.


V>>В месте вызова делают редко, иначе управляемость кода падает.

V>>Т.е., одна обыгрываемая кроссплатформенная функциональность должна обитать в одном месте в исходниках.
S>Ну, то есть тот же подход C#, вид сбоку. Всё равно всё упирается в #ifdef.

В C# можно пользоваться тем, что если до вызова external, помеченного DllImport, дело не доходит, то этого кода как бы и нет.

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

В итоге обыгрывание кроссплатформенности не выходит достаточно "точечным".
Понятно, что это всё специфика, ну мы же тут каждый своим субъективным опытом делимся...


V>>Под линуха и макось пока приходится делать с 0-ля.

S>Неужто всё ещё нет инструмента, который умеет определения структур из .h файлов перегонять напрямую в шарп? Там же вроде как более-менее прямолинейно всё.

Не скажи.
Структуру АПИ можно сделать классом в C# и подавать по обычной ссылке, а можно сделать value-типом и подавать через ref, а можно сделать ref struct, т.е. ограничить время жизни на стеке. А можно просто IntPtr и заниматься сериализацией-десериализацией.

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


S>Так-то понятно, что авторы библиотек на C++ поставляют хидеры на С++, а pinvoke хелперы не поставляют


Та да.
Особенно весело, когда эти описания отличаются для разных сборок Linux, например, в случае select — зависит от параметра ядра max handles, т.е. размер структуры fd_set бывает разный.


S>Да, вот тут есть некоторый факап дизайна дотнетного АПИ. Понятно, что хотелось спрятать все эти InternalSafeHandle с глаз подальше; но в платформе-то есть куча API, которые оперируют именно хэндлами, причём более-менее ООПшным образом. Есть несколько способов всё это исправить, но они довольно-таки затратные.


Дотнет пытается дать высокоуровневое "всё изкаробки".
Т.е., все эти вещи так или иначе связаны с асинхронным режимом работы, вот он и даёт.
Сейчас постепенно расползается по АПИ системных библиотек 3-е поколение асинхроннщины — на основе ValueTask.
Т.е. эволюция была такой — BeginOp/EndOp, Task, ValueTask.

Однако, с ValueTask есть кое-какие проблемы — непонятно как гарантировать, что после использования Result этот экземпляр ValueTask не будет использован в будущем.
Есть специальный метод ValueTask.Preserve для таких случаев, но тогда получаются гарантии из разряда вербальных.

Идеально было бы сделать ValueTask как ref-struct, чтобы ограничить время жизни локальным scope, но тогда их невозможно будет использовать для захватываемых переменных async-автомата, т.е. для асинхронного кода, для которого ValueTask и предназначен, собсно.

С другой стороны, этого и не требуется, т.к. м/у вхождениями в автомат работает не сам экземпляр ValueTask, а его awaiter, т.е. сохранять в машинке надо его, но проблема семантики ref struct остаётся — это надо будет сделать AsyncMethodBuilder совсем уж умным и что-то сделать с компилятором C#, чтобы дело вообще дошло до вызова своего AsyncMethodBuilder.

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


V>>Раньше мы работали на микросекундных задержках, сейчас бодаемся за наносекундные.

V>>Жаль, одна только стоимость вызова нейтивной ф-ии сравнима с задержками, даваемыми нейтивной частью уже с приготовленными данными. ))
S>Руки дойдут — посмотрю бенчмарки. Не должно там быть такого оверхеда — речь про единицы десятков ассемблерных инструкций.

Можно пройтись глазами по хелперу нейтивного вызова, ссылку я дал.
Сравнить с вызовом managed-кода, когда просто в RAX загружается значение переменной и просто делается call RAX.


V>>И не потому что "ловушки GC", "фреймы стека" и прочее — а просто глупый.

V>>Например, два-три раза подряд записать в стековую переменную ноль разными способами, и это значение нигде потом не используется.
S>Это скорее вопрос к качеству IL-кода. Джит, по большому счёту, рассчитан на то, что семантические оптимизации делает компилятор высокого уровня.

Простой пример:
interface IConfig {
   bool SomeFlag1 { get; }
}

class SomeClass<TConfig> 
   where TConfig : struct, IConfig
{
    public void Foo() {
        if(default(TConfig).SomeFlag1) {
           // case 1
        } else {
           // case 2
        }
    }
}

struct Config1 : IConfig {
   public SomeFlag1 => true;
}

struct Config2 : IConfig {
   public SomeFlag1 => false;
}

...

SomeClass<Config1> c1;
c1.Foo();

SomeClass<Config1> c2;
c2.Foo();


После работы JIT с оптимизацией изсчезает ветвление в методе Foo, т.к. ветвление происходит по константе, т.е. ассемблер показывает, что остаётся просто одна из веток.
Но, создание экземпляра Config1 длиной 1 байт происходит.

Причём, происходит в 2 этапа — сначала под него выделяется память и обнуляется.
Потом в эту память записывается 0 в результате вызова дефолтного конструктора.
Потом эта память нигде не используется, т.к. вызов св-ва SomeFlag1 JIT заинлайнил, соптимизировал и даже выкинул ненужную ветку if.

Кстате, кто-то сетовал, что в C# нельзя шаблоны генериков делать константами — вот, можно, через тип-"провайдер" таких констант.

Это техника из плюсов, где в подобных случаях при ветвлении на константе оптимизирующий компилятор сразу отбрасывает лишнюю ветку кода, поэтому, плюсовики без проблем позволяют себе лишние if-ы в подобных случаях.

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


S>Именно там должна проводиться трассировка использования (вот эти вот все варнинги про value assigned to t is never used). У джита мало ресурсов и очень мало времени на то, чтобы отслеживать глобальные потоки данных.


Что странно — лишнюю ветку выкинул, а лишние данные — нет.
У JIT-компилятора джавы с этим никаких проблем, т.к. блок escape-анализа отслеживает жизненный путь данных, и если данные не нужны, они и не создаются.

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


S>Я когда в прошлом году экспериментировал, убедился, что начиная с определённого количества переменных в методе джит просто забивает на частоты использования и lifetime, и тупо уносит всё в стек.


Он не справляется даже в показанном простейшем случае.
Я периодически открываю issue-s в дотнете, но тут рука пока не поднялась, бо явно не самый нужный сценарий для отрасли, хотя был бы полезен конкретно мне. ))

Я всё жду (очень много лет) когда они приличный AOT-оптимизатор заделают.
Вот туда уже стоит вкладываться.


S>На самом деле джит тоже опенсорсный, и прогресс в нём впечатляет. Народ постоянно контрибутит туда офигенные вещи. Про шестой дотнет я пока не смотрел, а вот в пятом было добавлено до фига "смысловых" оптимизаций.


Дотнет ускорился однозначно в низкоуровневых операциях.

Например, мне пришлось выкинуть достаточно большую библиотеку обработки-конверсии строк, которая давала прирост в разы на прошлых дотнетах, с версии Core 3.1 давала меньше, а с выходом 5.0 перестала иметь смысл...

Но, блин, это всё самые базовые вещи...
И я когда-то потратил прилично времени на эту либу, и она была нужна долго, а вот теперь вложения помножились на ноль. ))


V>>В итоге застой в .Net продлился примерно на 5-6 лет дольше, чем высмеиваемый (справедливо) когда-то застой в С++.

S>Такого не может быть Дотнету столько лет нет, сколько будет, если прибавить 5 лет к срокам застоя в плюсах

Плюсы активно менялись до стандарта 2003.
А потом на 8 лет застой.
Т.е., речь не про общее время жизни технологии, а про периоды отсутствия заметного развития.

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


V>>Липперт, конечно, сладкий болтун, особенно по мелочам, но вовремя подняться над ситуацией и осмотреть её с высоты птичьего полёта не смог.

V>>Наверно, был очень занят все эти годы.
S>Пока Липперт работал в команде, прогресс с компилятором был ого-го себе.

Сейчас прогресс именно языка виднее.
Разумеется, в т.ч. из-за вклада Липперта когда-то.

Его ошибка классическая — он попытался съесть слона целиком, а стоило это делать по частям.


S>Ну, как мы уже выяснили, по сравнению с вами и Билл Гейтс — неудачник


Гейтс красавчик, не надо ля-ля.
Его решения всегда были достаточно оперативны, своевременны (ключевое).
Даже когда он якобы "просрал" интернет — это просирание было от силы год и было наверстано с большим запасом в течении опять же года.
Это был шок и трепет, когда из отстающего они через год превратились в законодателя мод в интернете, что вся отрасль сидела на IE затем почти два десятилетия.
А как конкуренты "обиделись" — любо дорого было посмотреть.
Но своей обидой нанесли вред индустрии, как ни крути, все эти судебные процессы против бесплатного IE.
По сегодняшним меркам выглядит дико.


S>Лично я считаю Липперта очень, очень квалифицированным разработчиком. Как и Хейльсберга


Сравнил хрен с пальцем.
Хейльсберг тоже красавчег.

Малость странно, что ты сравниваешь людей с потрясающим нюхом, т.е. даром предвидения (никакого "дара" нет, разумеется, это хорошие аналитически скиллы, умение видеть одну и ту же картинку с разных фокусных расстояний) с "просто хорошим ремесленником".

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

V>>Разве что абстракции бесплатны и способ их выбора прост.

S>При правильной реализации абстракции в дотнете тоже близки к бесплатным.

Это если транзитивно протягивать зависимость от платформы в свой код, т.е. генерить разные бинари под разные платформы, как это происходит в системных либах.
Тогда можно сделать без абстракций (в дотнетном смысле), просто задействуя разный код для разных конечных сборок.

Но в случае дотнета решиться на такое не очень просто.


V>>В месте вызова делают редко, иначе управляемость кода падает.

V>>Т.е., одна обыгрываемая кроссплатформенная функциональность должна обитать в одном месте в исходниках.
S>Ну, то есть тот же подход C#, вид сбоку. Всё равно всё упирается в #ifdef.

В C# можно пользоваться тем, что если до вызова external, помеченного DllImport, дело не доходит, то этого кода как бы и нет.

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

В итоге обыгрывание кроссплатформенности не выходит достаточно "точечным".
Понятно, что это всё специфика, ну мы же тут каждый своим субъективным опытом делимся...


V>>Под линуха и макось пока приходится делать с 0-ля.

S>Неужто всё ещё нет инструмента, который умеет определения структур из .h файлов перегонять напрямую в шарп? Там же вроде как более-менее прямолинейно всё.

Не скажи.
Структуру АПИ можно сделать классом в C# и подавать по обычной ссылке, а можно сделать value-типом и подавать через ref, а можно сделать ref struct, т.е. ограничить время жизни на стеке. А можно просто IntPtr и заниматься сериализацией-десериализацией.

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


S>Так-то понятно, что авторы библиотек на C++ поставляют хидеры на С++, а pinvoke хелперы не поставляют


Та да.
Особенно весело, когда эти описания отличаются для разных сборок Linux, например, в случае select — зависит от параметра ядра max handles, т.е. размер структуры fd_set бывает разный.


S>Да, вот тут есть некоторый факап дизайна дотнетного АПИ. Понятно, что хотелось спрятать все эти InternalSafeHandle с глаз подальше; но в платформе-то есть куча API, которые оперируют именно хэндлами, причём более-менее ООПшным образом. Есть несколько способов всё это исправить, но они довольно-таки затратные.


Дотнет пытается дать высокоуровневое "всё изкаробки".
Т.е., все эти вещи так или иначе связаны с асинхронным режимом работы, вот он и даёт.
Сейчас постепенно расползается по АПИ системных библиотек 3-е поколение асинхроннщины — на основе ValueTask.
Т.е. эволюция была такой — BeginOp/EndOp, Task, ValueTask.

Однако, с ValueTask есть кое-какие проблемы — непонятно как гарантировать, что после использования Result этот экземпляр ValueTask не будет использован в будущем.
Есть специальный метод ValueTask.Preserve для таких случаев, но тогда получаются гарантии из разряда вербальных.

Идеально было бы сделать ValueTask как ref-struct, чтобы ограничить время жизни локальным scope, но тогда их невозможно будет использовать для захватываемых переменных async-автомата, т.е. для асинхронного кода, для которого ValueTask и предназначен, собсно.

С другой стороны, этого и не требуется, т.к. м/у вхождениями в автомат работает не сам экземпляр ValueTask, а его awaiter, т.е. сохранять в машинке надо его, но проблема семантики ref struct остаётся — это надо будет сделать AsyncMethodBuilder совсем уж умным и что-то сделать с компилятором C#, чтобы дело вообще дошло до вызова своего AsyncMethodBuilder.

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


V>>Раньше мы работали на микросекундных задержках, сейчас бодаемся за наносекундные.

V>>Жаль, одна только стоимость вызова нейтивной ф-ии сравнима с задержками, даваемыми нейтивной частью уже с приготовленными данными. ))
S>Руки дойдут — посмотрю бенчмарки. Не должно там быть такого оверхеда — речь про единицы десятков ассемблерных инструкций.

Можно пройтись глазами по хелперу нейтивного вызова, ссылку я дал.
Сравнить с вызовом managed-кода, когда просто в RAX загружается значение переменной и просто делается call RAX.


V>>И не потому что "ловушки GC", "фреймы стека" и прочее — а просто глупый.

V>>Например, два-три раза подряд записать в стековую переменную ноль разными способами, и это значение нигде потом не используется.
S>Это скорее вопрос к качеству IL-кода. Джит, по большому счёту, рассчитан на то, что семантические оптимизации делает компилятор высокого уровня.

Простой пример:
interface IConfig {
   bool SomeFlag1 { get; }
}

class SomeClass<TConfig> 
   where TConfig : struct, IConfig
{
    public void Foo() {
        if(default(TConfig).SomeFlag1) {
           // case 1
        } else {
           // case 2
        }
    }
}

struct Config1 : IConfig {
   public bool SomeFlag1 => true;
}

struct Config2 : IConfig {
   public bool SomeFlag1 => false;
}

...

SomeClass<Config1> c1;
c1.Foo();

SomeClass<Config1> c2;
c2.Foo();


После работы JIT с оптимизацией изсчезает ветвление в методе Foo, т.к. ветвление происходит по константе, т.е. ассемблер показывает, что остаётся просто одна из веток.
Но, создание экземпляра Config1 длиной 1 байт происходит.

Причём, происходит в 2 этапа — сначала под него выделяется память и обнуляется.
Потом в эту память записывается 0 в результате вызова дефолтного конструктора.
Потом эта память нигде не используется, т.к. вызов св-ва SomeFlag1 JIT заинлайнил, соптимизировал и даже выкинул ненужную ветку if.

Кстате, кто-то сетовал, что в C# нельзя шаблоны генериков делать константами — вот, можно, через тип-"провайдер" таких констант.

Это техника из плюсов, где в подобных случаях при ветвлении на константе оптимизирующий компилятор сразу отбрасывает лишнюю ветку кода, поэтому, плюсовики без проблем позволяют себе лишние if-ы в подобных случаях.

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


S>Именно там должна проводиться трассировка использования (вот эти вот все варнинги про value assigned to t is never used). У джита мало ресурсов и очень мало времени на то, чтобы отслеживать глобальные потоки данных.


Что странно — лишнюю ветку выкинул, а лишние данные — нет.
У JIT-компилятора джавы с этим никаких проблем, т.к. блок escape-анализа отслеживает жизненный путь данных, и если данные не нужны, они и не создаются.

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


S>Я когда в прошлом году экспериментировал, убедился, что начиная с определённого количества переменных в методе джит просто забивает на частоты использования и lifetime, и тупо уносит всё в стек.


Он не справляется даже в показанном простейшем случае.
Я периодически открываю issue-s в дотнете, но тут рука пока не поднялась, бо явно не самый нужный сценарий для отрасли, хотя был бы полезен конкретно мне. ))

Я всё жду (очень много лет) когда они приличный AOT-оптимизатор заделают.
Вот туда уже стоит вкладываться.


S>На самом деле джит тоже опенсорсный, и прогресс в нём впечатляет. Народ постоянно контрибутит туда офигенные вещи. Про шестой дотнет я пока не смотрел, а вот в пятом было добавлено до фига "смысловых" оптимизаций.


Дотнет ускорился однозначно в низкоуровневых операциях.

Например, мне пришлось выкинуть достаточно большую библиотеку обработки-конверсии строк, которая давала прирост в разы на прошлых дотнетах, с версии Core 3.1 давала меньше, а с выходом 5.0 перестала иметь смысл...

Но, блин, это всё самые базовые вещи...
И я когда-то потратил прилично времени на эту либу, и она была нужна долго, а вот теперь вложения помножились на ноль. ))


V>>В итоге застой в .Net продлился примерно на 5-6 лет дольше, чем высмеиваемый (справедливо) когда-то застой в С++.

S>Такого не может быть Дотнету столько лет нет, сколько будет, если прибавить 5 лет к срокам застоя в плюсах

Плюсы активно менялись до стандарта 2003.
А потом на 8 лет застой.
Т.е., речь не про общее время жизни технологии, а про периоды отсутствия заметного развития.

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


V>>Липперт, конечно, сладкий болтун, особенно по мелочам, но вовремя подняться над ситуацией и осмотреть её с высоты птичьего полёта не смог.

V>>Наверно, был очень занят все эти годы.
S>Пока Липперт работал в команде, прогресс с компилятором был ого-го себе.

Сейчас прогресс именно языка виднее.
Разумеется, в т.ч. из-за вклада Липперта когда-то.

Его ошибка классическая — он попытался съесть слона целиком, а стоило это делать по частям.


S>Ну, как мы уже выяснили, по сравнению с вами и Билл Гейтс — неудачник


Гейтс красавчик, не надо ля-ля.
Его решения всегда были достаточно оперативны, своевременны (ключевое).
Даже когда он якобы "просрал" интернет — это просирание было от силы год и было наверстано с большим запасом в течении опять же года.
Это был шок и трепет, когда из отстающего они через год превратились в законодателя мод в интернете, что вся отрасль сидела на IE затем почти два десятилетия.
А как конкуренты "обиделись" — любо дорого было посмотреть.
Но своей обидой нанесли вред индустрии, как ни крути, все эти судебные процессы против бесплатного IE.
По сегодняшним меркам выглядит дико.


S>Лично я считаю Липперта очень, очень квалифицированным разработчиком. Как и Хейльсберга


Сравнил хрен с пальцем.
Хейльсберг тоже красавчег.

Малость странно, что ты сравниваешь людей с потрясающим нюхом, т.е. даром предвидения (никакого "дара" нет, разумеется, это хорошие аналитически скиллы, умение видеть одну и ту же картинку с разных фокусных расстояний) с "просто хорошим ремесленником".

Это несравнимые весовые категории.