Зайдя на статью я изначально ожидал катастрофу, тем не менее почитав, в целом скажу неплохо.
Разве что выбило объяснение типов и литералов до переменных : )
Ну и немного критики по технической части (люблю придраться по мелочам):
-Нет суффикса u для uint и литералов с экспонентой.
-new не означает вызов конструктора, оно означает примерно тоже самое что и в C++: аллоцируй в куче и дай ссылку (упакованную в тип). На самом деле можно вызвать конструктор и без new, через :this()
С new можно объявлять массивы, анонимные типы и вообще new может быть модификатором или ограничением
Редакт: существование структур несколько ломает мою позицию здесь должен признать
-в C# есть >>> unsigned right shift (Ну и плюс unsafe)
-для Nullable<T> хорошо бы упомянуть .Value поле, что бы получить значение без каста, постоянно нужно
-для обработки ошибок хорошо бы упомянуть rethrow
-Структуры данных в C# кличут коллекциями, это не то что бы важно; что важно это почему нет LinkedList, одна из ключевых структур данных прямо по учебнику. (И HashSet тоже, в сравнении очередь практически не используется)
-"Для всех остальных случаев используйте foreach." Но foreach это вызов GetEnumerator(), его конечно хорошо оптимизировали, но... он буквально разворачивается в for цикл в IL.
Хороший пример силы for ещё проход про LinkedList
-DateTime.Now очень плохая идея, хотя бы DateTime.UtcNow, что бы не прострелить ногу часовыми поясами
-в Random очень полезен NextDouble, возвращает 0..1, жалко пропустить.
А если серьезно, прочитав комментарии пропало какое-либо желание быть серьезным. Люди все равно будут верить в свою собственную байку. Была сформирована довольно хорошая мысль: это как целый день использовать мышку левой рукой; и ведь правда, только зачем бы кто-то, помимо "прикола", захотел бы пользоваться не доминантной рукой. Так и здесь
JiT действительно может, но Roslyn просто не эмитит ему эти инструкции, потому что это меняет семантику, а оптимизации по определению не должны менять результат исполнения программы.
Мне было бы очень интересно посмотреть на примеры, которые стабильно эмитят tail call потестить
Должен отметить, предложение о хвостовой рекурсии есть в IL (поскольку есть в F#), но было отклонено на уровне LDM. Причина: ломает стек трейс, сильно травмирует отладку, есть комментарий Мэдса на тему.
Из планов на .NET11 известны ещё closed модификатор, nullable async/foreach, развивается unsafe блок в статический анализатор как было с nullable reference types, top-level methods и ещё всякого разного по мелочи.
Так-то да, из крупных анонсов ждем Union types и Runtime async (+extension everything), но об этом уже давно известно, ещё со времён .Net9. Самое интересное как по мне происходит в нишевых гитхаб коммитах
Вас ведь никто не спрашивал, вы посчитали очень уместным исправить по вашему мнению ошибку в определении. Знаете, это ведь не технический ресурс, а лингвистическим разбором слов пускай занимаются филологи
Полагаю требуется некий контекст, вполне есть ситуации где можно гарантировать отсутствие как освобождения в try, так и использования после finally, а исключения остаются проблемой.
Ещё finally намертво предотвращает инлайнинг, что наверно стоит иметь ввиду, раз уж мы уже говорим об оптимизациях
extension members просили довольно давно и это отличное нововведение, хотя два года я их и не ждал. Возможность расширять свойства и статические классы (вроде Math) также даёт много возможностей, которые я весьма приветствую. Уже печально известный .IsNullOrEmpty в виде свойства разойдется наверно по тысячам кодовых баз.
По некоторой причине есть категория людей, которая катастрофически не хочет давать разработчикам новую силу, потому что найдется тот, кто применит ее во зло. Видимо стакан у некоторых людей всегда наполовину пуст
Есть статическая compile-time рефлексия, она реализуется генераторами в том же C# без проблем. А вот runtime рефлексия требует переписывание кода в процессе работы приложения, что требует виртуальную машину. Это ведь крайне банальная логика
Так ведь и на C# нет популярных игровых движков, Unity компилирует C# в C++ код под капотом, он вообще не использует CLR даже в debug режиме, шарпы там не нативные. И использует его юнити потому что IL удобный и синтаксис простой, а не потому что производительность.
В C# есть очень мощный контроль, можно писать через указатели если нужно и стандартная библиотека этим пользуется. Там все оптимизировано вусмерть, часть вообще написана на C. У джавы ситуация другая, она не даёт программистам инструментов, но JVM берет эту ответственность за себя и решает за программиста где и что она будет оптимизировать.
Если очень просто, C# будет производительней и дешевле по памяти, если его реально оптимизировать, но обычный "просто рабочий" код высока вероятность будет лучше и дешевле по памяти на Java. Что мы собственно и видим со стандартным стеком.
Это касается ссылочных типов тоже, но на пользовательских классах может сломаться escape-analysis и JiT решит, что выделять на стеке небезопасно. "Небольшие размеры" очень грубо это около 10000 элементов. В целом не сомневаюсь, что в .Net11 список сильно расширится.
Аллоцировать массивы на стек можно было ещё в .Net9, но это касалось только знаковых примитивов и часто не срабатывало, поэтому и хайпа избежало. Сейчас это полноправная фишка
Причина в ограничении размера в том, что стек ограничен в размере операционкой, около 8Мб. Я здесь уже спекулирую, но я думаю на кучу уходят объекты, которые должны попасть в Large object heap, а это 80 килобайт
BCL написан через Span и ArrayPool во многом, но делегаты, и перечислители так заменить было нельзя (и ещё ряд объектов, так сходу не вспомню). Больше всего счастливо от этих изменений LINQ, хотя у них были ещё свои оптимизации сверху, они добавили больше реализаций перечислителей для частных случаев в цепочках.
И вы не плюйтесь на JVM, она в разработке намного дольше CLR. JiT научился грузить массивы на стек, но Java могла так уже очень давно и даже больше. Из-за отсутствия структур может показаться, что все выделяется на куче, но в реальности как раз JVM может выделять ваше классы на стеке, если считает что это безопасно.
C# производительный за счёт языковых фич, за счёт очень низкоуровневой библиотеки и штук вроде async await и true generics. Java имеет ужасный дизайн языка, но сама JVM очень крутая технология. Даже с точки зрения самого банального — инлайнинга
Впрочем команда .Net в последние годы выпускают больше оптимизаций, чем любой другой язык, так что этот разрыв стремительно сокращается и в некоторых областях CLR действительно начала выигрывать, что до недавнего времени было не так
Мне кажется что люди во многом недооценивают важность этого релиза
Выделение коллекций на стеке это game changer. Многие думают, "когда мне вообще нужно выделять такие буферы, это редкость? Да и есть ArrayPool для этого", но здесь речь не только про массивы, как можно подумать. RyuJiT научился аллоцировать на стек и сами объекты, те же делегаты(да, делегаты в некотором смысле коллекции) или Enumerator могут быть стек аллоцированными. И список будет только расширяться.
И тут стоит обратить внимание, что это используется везде в BCL, от этих оптимизаций выигрывает огромная часть стандартной библиотеки, давая бесплатный прирост производительности почти везде.
Фантастика! Я ждал этого ещё со времён анонса .NET9.
Когда этот барьер наконец-то преодолен, это открывает множество возможностей для оптимизаций в будущем. CLR догоняет JVM с огромной скоростью. Реальный список оптимизаций просто километровый, советую почитать официальные статьи на тему.
И мы уже знаем о планах runtime async и discriminated Unions в .NET11! Когда я начинаю думать "ну куда дальше", выходит Мэдс и предлагает новый game changer.
В общем бэнгер, а не релиз, закрывает старые гештальты. Вымирающий пример случая, когда Microsoft сделала что-то как надо
В Native AOT есть рефлексия! Но только ее урезанная версия, например typeof и Enum.Parse<T> будут работать, эти данные сохраняются. Это просто статические метаданные, они нужны банально для работы BCL.
Впрочем речь здесь явно не об этом, поэтому стоит упомянуть, что это невозможно, это логически невыполнимо. Native AoT прекомпилирует все методы и большинство метаданных удаляется, в этом весь ее смысл. Также как в C++ не может быть рефлексии, при ahead of time сборке в C# ее также не может быть. Рефлексия следствие JiT компилятора и везде где есть рефлексия есть JiT (ну или интерпритация)
Довольно поздно, но об этом как-то не упоминают, поэтому оставлю здесь так сказать "для будущих поколений".
Одна из критических особенностей Span заключается в обобщении коллекций, даже не оптимизации.
Можно написать метод T Sum (ReadOnlySpan<T> data) where T : INumber<T> и вызывать его для любой коллекции, представленной как последовательный блок памяти. Работает для T[], List<T>, IList<T>, ReadOnlyCollection<T> и даже T*
Без Span это бы требовало дубликации кода и огромная часть BCL написана именно так. Раньше им приходилось писать методы на C/C++ и дергать их с PInvoke. Конечно, это не что-то новое, просто теперь работать с этим несколько проще.
В данном простом примере откровенно говоря было бы проще сделать абстрактный класс с protected полем и унаследовать оба от него. А глобально мы создаём не очевидные зависимости и раздуваем код сложно читаемыми (по крайней мере для стороннего программиста) атрибутами и псевдонимами. Решение, имхо, вызывает больше проблем, чем проблема internal поле, которое оно решает. Keep It Simple Stupid. Может быть грубовато, но на западе такое зовут overengineered garbage code.
Генераторы кода вещь крутая, несомненно, но для замены рефлексии, а подобным выкрутасам я ещё должен увидеть применение. Возможно в будущем кто-то докажет мою неправоту. А причина почему в C# нет friend не функциональность, а скорее безопасность, это нарушает контракт private и ломает гарантии, в том числе для JiT
assert имеет на самом деле весьма узкое применение, аргументация о падении программы в дебаге понятна, но в дебаге и не суть важно, если она продолжит работать некорректно, но с логами. А вот отсутствие проверок в рантайме может привести к катастрофическим последствиям. И я могу сходу представить огромную палитру багов от разработчика, которые очень сильно пострадают от assert. assert актуален только для стабильных compile-time ошибок, что большая редкость.
Допустим у нас какая-то data-driven разработка, мы читаем информацию из xml, собранного нами же самими. Делается assert на ошибку разработчика, мы множество раз протестировали в дебаге все работает. А потом в рантайме файл меняется, assert в релизе не срабатывает и у нас посыпалась вся логика без какой либо обработки. И все что бы сохранить парочку cpu инструкций!
Не чушь ли, товарищи. assert имеет место быть, но в узком направлении ошибок, статья гипер-обобщает, на мой взгляд
Зайдя на статью я изначально ожидал катастрофу, тем не менее почитав, в целом скажу неплохо.
Разве что выбило объяснение типов и литералов до переменных : )
Ну и немного критики по технической части (люблю придраться по мелочам):
-Нет суффикса u для uint и литералов с экспонентой.
-new не означает вызов конструктора, оно означает примерно тоже самое что и в C++: аллоцируй в куче и дай ссылку (упакованную в тип). На самом деле можно вызвать конструктор и без new, через :this()
С new можно объявлять массивы, анонимные типы и вообще new может быть модификатором или ограничением
Редакт: существование структур несколько ломает мою позицию здесь должен признать
-в C# есть >>> unsigned right shift (Ну и плюс unsafe)
-для Nullable<T> хорошо бы упомянуть .Value поле, что бы получить значение без каста, постоянно нужно
-для обработки ошибок хорошо бы упомянуть rethrow
-Структуры данных в C# кличут коллекциями, это не то что бы важно; что важно это почему нет LinkedList, одна из ключевых структур данных прямо по учебнику. (И HashSet тоже, в сравнении очередь практически не используется)
-"Для всех остальных случаев используйте foreach." Но foreach это вызов GetEnumerator(), его конечно хорошо оптимизировали, но... он буквально разворачивается в for цикл в IL.
Хороший пример силы for ещё проход про LinkedList
-DateTime.Now очень плохая идея, хотя бы DateTime.UtcNow, что бы не прострелить ногу часовыми поясами
-в Random очень полезен NextDouble, возвращает 0..1, жалко пропустить.
Чтошш, ваше счастье, discriminated unions с C# обещают в этом году
Звучит как skill issue
А если серьезно, прочитав комментарии пропало какое-либо желание быть серьезным. Люди все равно будут верить в свою собственную байку. Была сформирована довольно хорошая мысль: это как целый день использовать мышку левой рукой; и ведь правда, только зачем бы кто-то, помимо "прикола", захотел бы пользоваться не доминантной рукой. Так и здесь
JiT действительно может, но Roslyn просто не эмитит ему эти инструкции, потому что это меняет семантику, а оптимизации по определению не должны менять результат исполнения программы.
Мне было бы очень интересно посмотреть на примеры, которые стабильно эмитят tail call потестить
Должен отметить, предложение о хвостовой рекурсии есть в IL (поскольку есть в F#), но было отклонено на уровне LDM. Причина: ломает стек трейс, сильно травмирует отладку, есть комментарий Мэдса на тему.
Из планов на .NET11 известны ещё closed модификатор, nullable async/foreach, развивается unsafe блок в статический анализатор как было с nullable reference types, top-level methods и ещё всякого разного по мелочи.
Так-то да, из крупных анонсов ждем Union types и Runtime async (+extension everything), но об этом уже давно известно, ещё со времён .Net9. Самое интересное как по мне происходит в нишевых гитхаб коммитах
Вас ведь никто не спрашивал, вы посчитали очень уместным исправить по вашему мнению ошибку в определении. Знаете, это ведь не технический ресурс, а лингвистическим разбором слов пускай занимаются филологи
Полагаю требуется некий контекст, вполне есть ситуации где можно гарантировать отсутствие как освобождения в try, так и использования после finally, а исключения остаются проблемой.
Ещё finally намертво предотвращает инлайнинг, что наверно стоит иметь ввиду, раз уж мы уже говорим об оптимизациях
extension members просили довольно давно и это отличное нововведение, хотя два года я их и не ждал. Возможность расширять свойства и статические классы (вроде Math) также даёт много возможностей, которые я весьма приветствую. Уже печально известный .IsNullOrEmpty в виде свойства разойдется наверно по тысячам кодовых баз.
По некоторой причине есть категория людей, которая катастрофически не хочет давать разработчикам новую силу, потому что найдется тот, кто применит ее во зло. Видимо стакан у некоторых людей всегда наполовину пуст
В go compile time рефлексия, это просто метаданные. В C# runtime рефлексия (RTTI). C# поддерживает статическую рефлексию в native aot уже!
В общем ваши слова не противоречат моим, а мои не противоречат вашим. Было бы проще, если бы немного внимательнее читали написанное
Есть статическая compile-time рефлексия, она реализуется генераторами в том же C# без проблем. А вот runtime рефлексия требует переписывание кода в процессе работы приложения, что требует виртуальную машину. Это ведь крайне банальная логика
Например Array.Sort написан на C. Или по крайней мере был написан, так сразу с головы тяжело сказать.
Да, в шарпе больше контроля, Шарп крутой. Но это не вина JVM и не заслуга CLR, это вина самого языка
Так ведь и на C# нет популярных игровых движков, Unity компилирует C# в C++ код под капотом, он вообще не использует CLR даже в debug режиме, шарпы там не нативные. И использует его юнити потому что IL удобный и синтаксис простой, а не потому что производительность.
В C# есть очень мощный контроль, можно писать через указатели если нужно и стандартная библиотека этим пользуется. Там все оптимизировано вусмерть, часть вообще написана на C. У джавы ситуация другая, она не даёт программистам инструментов, но JVM берет эту ответственность за себя и решает за программиста где и что она будет оптимизировать.
Если очень просто, C# будет производительней и дешевле по памяти, если его реально оптимизировать, но обычный "просто рабочий" код высока вероятность будет лучше и дешевле по памяти на Java. Что мы собственно и видим со стандартным стеком.
stackalloc это явная ref struct, а здесь на стек отправляется стандартный int[], большая разница
Это касается ссылочных типов тоже, но на пользовательских классах может сломаться escape-analysis и JiT решит, что выделять на стеке небезопасно. "Небольшие размеры" очень грубо это около 10000 элементов. В целом не сомневаюсь, что в .Net11 список сильно расширится.
Аллоцировать массивы на стек можно было ещё в .Net9, но это касалось только знаковых примитивов и часто не срабатывало, поэтому и хайпа избежало. Сейчас это полноправная фишка
Причина в ограничении размера в том, что стек ограничен в размере операционкой, около 8Мб. Я здесь уже спекулирую, но я думаю на кучу уходят объекты, которые должны попасть в Large object heap, а это 80 килобайт
BCL написан через Span и ArrayPool во многом, но делегаты, и перечислители так заменить было нельзя (и ещё ряд объектов, так сходу не вспомню). Больше всего счастливо от этих изменений LINQ, хотя у них были ещё свои оптимизации сверху, они добавили больше реализаций перечислителей для частных случаев в цепочках.
И вы не плюйтесь на JVM, она в разработке намного дольше CLR. JiT научился грузить массивы на стек, но Java могла так уже очень давно и даже больше. Из-за отсутствия структур может показаться, что все выделяется на куче, но в реальности как раз JVM может выделять ваше классы на стеке, если считает что это безопасно.
C# производительный за счёт языковых фич, за счёт очень низкоуровневой библиотеки и штук вроде async await и true generics. Java имеет ужасный дизайн языка, но сама JVM очень крутая технология. Даже с точки зрения самого банального — инлайнинга
Впрочем команда .Net в последние годы выпускают больше оптимизаций, чем любой другой язык, так что этот разрыв стремительно сокращается и в некоторых областях CLR действительно начала выигрывать, что до недавнего времени было не так
Мне кажется что люди во многом недооценивают важность этого релиза
Выделение коллекций на стеке это game changer. Многие думают, "когда мне вообще нужно выделять такие буферы, это редкость? Да и есть ArrayPool для этого", но здесь речь не только про массивы, как можно подумать. RyuJiT научился аллоцировать на стек и сами объекты, те же делегаты(да, делегаты в некотором смысле коллекции) или Enumerator могут быть стек аллоцированными. И список будет только расширяться.
И тут стоит обратить внимание, что это используется везде в BCL, от этих оптимизаций выигрывает огромная часть стандартной библиотеки, давая бесплатный прирост производительности почти везде.
Фантастика! Я ждал этого ещё со времён анонса .NET9.
Когда этот барьер наконец-то преодолен, это открывает множество возможностей для оптимизаций в будущем. CLR догоняет JVM с огромной скоростью. Реальный список оптимизаций просто километровый, советую почитать официальные статьи на тему.
И мы уже знаем о планах runtime async и discriminated Unions в .NET11! Когда я начинаю думать "ну куда дальше", выходит Мэдс и предлагает новый game changer.
В общем бэнгер, а не релиз, закрывает старые гештальты. Вымирающий пример случая, когда Microsoft сделала что-то как надо
В Native AOT есть рефлексия! Но только ее урезанная версия, например typeof и Enum.Parse<T> будут работать, эти данные сохраняются. Это просто статические метаданные, они нужны банально для работы BCL.
Впрочем речь здесь явно не об этом, поэтому стоит упомянуть, что это невозможно, это логически невыполнимо. Native AoT прекомпилирует все методы и большинство метаданных удаляется, в этом весь ее смысл. Также как в C++ не может быть рефлексии, при ahead of time сборке в C# ее также не может быть. Рефлексия следствие JiT компилятора и везде где есть рефлексия есть JiT (ну или интерпритация)
Довольно поздно, но об этом как-то не упоминают, поэтому оставлю здесь так сказать "для будущих поколений".
Одна из критических особенностей Span заключается в обобщении коллекций, даже не оптимизации.
Можно написать метод T Sum (ReadOnlySpan<T> data) where T : INumber<T> и вызывать его для любой коллекции, представленной как последовательный блок памяти. Работает для T[], List<T>, IList<T>, ReadOnlyCollection<T> и даже T*
Без Span это бы требовало дубликации кода и огромная часть BCL написана именно так. Раньше им приходилось писать методы на C/C++ и дергать их с PInvoke. Конечно, это не что-то новое, просто теперь работать с этим несколько проще.
Сложное решение несуществующей проблемы.
В данном простом примере откровенно говоря было бы проще сделать абстрактный класс с protected полем и унаследовать оба от него. А глобально мы создаём не очевидные зависимости и раздуваем код сложно читаемыми (по крайней мере для стороннего программиста) атрибутами и псевдонимами. Решение, имхо, вызывает больше проблем, чем проблема internal поле, которое оно решает. Keep It Simple Stupid. Может быть грубовато, но на западе такое зовут overengineered garbage code.
Генераторы кода вещь крутая, несомненно, но для замены рефлексии, а подобным выкрутасам я ещё должен увидеть применение. Возможно в будущем кто-то докажет мою неправоту. А причина почему в C# нет friend не функциональность, а скорее безопасность, это нарушает контракт private и ломает гарантии, в том числе для JiT
assert имеет на самом деле весьма узкое применение, аргументация о падении программы в дебаге понятна, но в дебаге и не суть важно, если она продолжит работать некорректно, но с логами. А вот отсутствие проверок в рантайме может привести к катастрофическим последствиям. И я могу сходу представить огромную палитру багов от разработчика, которые очень сильно пострадают от assert. assert актуален только для стабильных compile-time ошибок, что большая редкость.
Допустим у нас какая-то data-driven разработка, мы читаем информацию из xml, собранного нами же самими. Делается assert на ошибку разработчика, мы множество раз протестировали в дебаге все работает. А потом в рантайме файл меняется, assert в релизе не срабатывает и у нас посыпалась вся логика без какой либо обработки. И все что бы сохранить парочку cpu инструкций!
Не чушь ли, товарищи. assert имеет место быть, но в узком направлении ошибок, статья гипер-обобщает, на мой взгляд