Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 28.02.17 05:42
Оценка: 10 (2) +1
Очевидное-невероятное заказывали
Автор: Sinix
Дата: 27.02.17
?
        static void Do(ImmutableArray<int> x)
        {
            if (x.Length == 0)
                return;

            // ...
        }

Всё норм, да? ; )

UPD Поскольку до главного WTF народ таки не добрался, подсказка:
        static void Do(ImmutableArray<int> x)
        {
            if (x.Length == 0)
                return;

            // ...
        }
        static void Do(ICollection<int> x)
        {
            if (x.Count == 0)
                return;

            // ...
        }


  совсем подсказка
См. на название топика
Отредактировано 28.02.2017 8:16 Sinix . Предыдущая версия . Еще …
Отредактировано 28.02.2017 5:43 Sinix . Предыдущая версия .
минутка wtf
Re: Минутка WTF-19: Catch me if you can
От: vasmann  
Дата: 28.02.17 06:11
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Всё норм, да? ; )


Документация с наличием свойства IsEmpty намекает что не всё норм.
Не пробовал что делает код, но судя по теме похоже выбрасывает ексепшон.
Re[2]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 28.02.17 06:17
Оценка:
Здравствуйте, vasmann, Вы писали:

S>>Всё норм, да? ; )

V>Документация с наличием свойства IsEmpty намекает что не всё норм.
Наоборот же Документация (см Remarks) не заморачивается такими мелочами.


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

Ну таки да, но тема до конца не раскрыта.
Re[3]: Минутка WTF-19: Catch me if you can
От: Venom  
Дата: 28.02.17 06:28
Оценка:
Здравствуйте, Sinix, Вы писали:

V>>Документация с наличием свойства IsEmpty намекает что не всё норм.

S>Наоборот же Документация (см Remarks) не заморачивается такими мелочами.

Но там ничего не написано про IsEmpty или Lenght.
Re: Минутка WTF-19: Catch me if you can
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 28.02.17 06:34
Оценка: 2 (1) +1
Здравствуйте, Sinix, Вы писали:

S>Всё норм, да? ; )


Да, это сильно. Хотя документация, точнее комментарии в сорцах, вполне доходчиво объясняет, что да как. Но что этот комментарий делает в коде и почему он не находится в секции remarks, я хз!

З.Ы. Это очередное доказательство того, что стоимость использования специализированных коллекций существенно выше, чем коллекций общего назначения. Правда есть небольшая проблема с тем, что не всегда понятно, что является специализированным, а что нет, и где тебя подстерегает такая засада

У меня в текущем проекте много структур. Правда много. Большинство из них неизменяемые, но бывают и исключения. Но все они содержат свойство IsValid, что позволяет отличить валидный экземпляр от невалидного. Да, можно сказать, что ImmutableArray.IsEmpty играет ту же самую роль, но, ИМХО, это не так. Для большинства разработчиков, и меня в том числе, foo.IsEmpty и foo.Length == 0 должны быть эквивалентными семантически, пусть и с разницей (потенциальной) с производительностью.
Re[3]: Минутка WTF-19: Catch me if you can
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.02.17 06:34
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, vasmann, Вы писали:


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

S>Ну таки да, но тема до конца не раскрыта.

new ImmutableArray<int>() что ли? Но это не засада, судя по тому что
internal void ThrowNullRefIfNotInitialized()
{
    int num = this.array.Length;
}

Т.е. они такое делают чуть ли не в каждом методе.
Re[4]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 28.02.17 06:35
Оценка:
Здравствуйте, Venom, Вы писали:

V>>>Документация с наличием свойства IsEmpty намекает что не всё норм.

S>>Наоборот же Документация (см Remarks) не заморачивается такими мелочами.

V>Но там ничего не написано про IsEmpty или Lenght.


Как это не написано?

ImmutableArray<T>.IsEmpty
Gets a value indicating whether this ImmutableArray<T> is empty.

...

ImmutableArray<T>.Length

Gets the number of elements in the array.


Соблюдая традиции
Автор: Sinix
Дата: 18.08.11
, ага.
Re[3]: Минутка WTF-19: Catch me if you can
От: vasmann  
Дата: 28.02.17 06:35
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, vasmann, Вы писали:


S>>>Всё норм, да? ; )

V>>Документация с наличием свойства IsEmpty намекает что не всё норм.
S>Наоборот же Документация (см Remarks) не заморачивается такими мелочами.

Не, я про то, что наличие свойства IsEmpty настораживает. Но пацаны да, решили не пояснять почему нужно это свойство а не обычное Length == 0
С этим типом не работал, потому самому интересно что с этим WTF.
Re[2]: Минутка WTF-19: Catch me if you can
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.02.17 06:36
Оценка: +1 :)
Здравствуйте, SergeyT., Вы писали:

ST>Для большинства разработчиков, и меня в том числе, foo.IsEmpty и foo.Length == 0 должны быть эквивалентными семантически, пусть и с разницей (потенциальной) с производительностью.

А они и эквивалентны. IsDefault скажет что array == null.
Re[2]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 28.02.17 06:40
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Да, это сильно.

Это преуменьшение Я подробней распишу чуть пожже, как народ раскачается и нормально ответит.
Re[3]: Минутка WTF-19: Catch me if you can
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 28.02.17 06:40
Оценка: +1
Здравствуйте, samius, Вы писали:

ST>>Для большинства разработчиков, и меня в том числе, foo.IsEmpty и foo.Length == 0 должны быть эквивалентными семантически, пусть и с разницей (потенциальной) с производительностью.

S>А они и эквивалентны. IsDefault скажет что array == null.

Точно. предыдущий комментарий сбил меня с толку и я не проверил.

Но IsDefault, ИМХО, явно не передает всей прелести ситуации. Ведь по сути, общение с экземпляром, чей IsDefault возвращает true равносильно работе с нулевой ссылкой. Но вот имя как-то не располагает к такой аналогии.
Re[4]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 28.02.17 06:49
Оценка:
Здравствуйте, samius, Вы писали:

S>Т.е. они такое делают чуть ли не в каждом методе.

Оок, подсказка: что именно бросается при обращению к свойству .Length?
Re[5]: Минутка WTF-19: Catch me if you can
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.02.17 06:51
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, samius, Вы писали:


S>Оок, подсказка: что именно бросается при обращению к свойству .Length?

NullReferenceException
и?
Re[6]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 28.02.17 06:58
Оценка:
Здравствуйте, samius, Вы писали:

S>>Оок, подсказка: что именно бросается при обращению к свойству .Length?

S>NullReferenceException
S>и?

И смотрим на название топика. Как при таком раскладе корректно ловить ситуацию "кривой массив передали"?
Re[3]: Минутка WTF-19: Catch me if you can
От: vasmann  
Дата: 28.02.17 06:59
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, SergeyT., Вы писали:


ST>>Да, это сильно.

S>Это преуменьшение Я подробней распишу чуть пожже, как народ раскачается и нормально ответит.

Могу предположить что ноги растут из массива неизменяемых массивов.
IA — ImmutableArray
1. Поскольку если выделить IA<T>[] = new IA<T>[100]; Для них не вызовется конструктор и все поля будут нулами/нулями.
2. Из этого по идее следует логичное поведение new IA<T>() так же должен будет быть проинициализирован нулом.
3. Так же можно и просто описать переменную IA<T> someIa.
Соответственно внутреннее поле остается пустым до заполнения данными, плюс нету дерганья памяти для инициализации пустым массивом new T[0].

Такие вот мысли.
Re[4]: Минутка WTF-19: Catch me if you can
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.02.17 07:01
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Но IsDefault, ИМХО, явно не передает всей прелести ситуации. Ведь по сути, общение с экземпляром, чей IsDefault возвращает true равносильно работе с нулевой ссылкой. Но вот имя как-то не располагает к такой аналогии.

Не передает, но намекает на default(ImmutableArray<T>). Какое бы решение выглядело лучше?
В моем проекте есть тоже структура-обертка над массивом, там я сделал IsNull. Так чуть ближе к народу, но намного ли?
Re[7]: Минутка WTF-19: Catch me if you can
От: vasmann  
Дата: 28.02.17 07:03
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, samius, Вы писали:


S>>>Оок, подсказка: что именно бросается при обращению к свойству .Length?

S>>NullReferenceException
S>>и?

S>Как при таком раскладе корректно ловить ситуацию "кривой массив передали"?


.IsDefault что надо признать адски не очевидно и это надо бы красными буквами жирными выделять в документации
Re[7]: Минутка WTF-19: Catch me if you can
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.02.17 07:12
Оценка: +2
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, samius, Вы писали:



S>И смотрим на название топика. Как при таком раскладе корректно ловить ситуацию "кривой массив передали"?

Я думал что названия топика для того что бы гугломэйл группировал сообщения

Обычно я хожу в документацию, но там пока все плохо, значит в ILSpy. А там пока пишут, что полетит NullReferenceException в случае default. Изменится ли это — фиг знает.
Я согласен, что как-то не очень. Но как сделать лучше — с разбегу не скажу.
Re[5]: Минутка WTF-19: Catch me if you can
От: Venom  
Дата: 28.02.17 08:41
Оценка:
Здравствуйте, Sinix, Вы писали:

S>>>Наоборот же Документация (см Remarks) не заморачивается такими мелочами.

V>>Но там ничего не написано про IsEmpty или Lenght.
S>Как это не написано?
Ой, что-то я протупил. Просто ты указал секцию Remarks (см выделение в процитированном) и я посмотрел только там.
Re: Минутка WTF-19: Catch me if you can
От: IT Россия linq2db.com
Дата: 01.03.17 00:25
Оценка:
Здравствуйте, Sinix, Вы писали:

S>
  совсем подсказка
S>См. на название топика


Прделагаю под двумя катами запрятать совсем севсем подсказку для совсем совсем ленивых.
Если нам не помогут, то мы тоже никого не пощадим.
Re[2]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 01.03.17 05:51
Оценка: 2 (1) :)
Здравствуйте, IT, Вы писали:


IT>Прделагаю под двумя катами запрятать совсем севсем подсказку для совсем совсем ленивых.


А тут уже подсказывать нечего. Народ вообще разленился

default(ImmutableArray<T>) бросает исключения при попытке обращения к почти любому member-у. Если обращение идёт напрямую, то бросается NullReferenceException, что я расцениваю как тончайший троллинг со стороны авторов структуры. Но это ещё полбеды.

При обращении к ImmutableArray через интерфейсы бросается InvalidOperationException, которое, разумеется, никто не ожидает и не перехватывает. Результаты (из того, что дошло до конечных пользователей):
Failed to launch visual studio 2017,
(ASP.Net, VS 2015)design time host build fails with ImmutableArrayT error,
Can't sign in with Visual Studio 2017 RC
etc.

В общем народу понравилось, и теперь custom default struct constructors пропихивают в шарп. Верните Хейлсберга.
Re[2]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 01.03.17 05:52
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Да, это сильно.


S>>Я подробней распишу чуть пожже, как народ раскачается и нормально ответит.


С "раскачается" не вышло, так что расписал
Автор: Sinix
Дата: 01.03.17
.
Отредактировано 01.03.2017 5:53 Sinix . Предыдущая версия .
Re[3]: Минутка WTF-19: Catch me if you can
От: IT Россия linq2db.com
Дата: 01.03.17 13:46
Оценка:
Здравствуйте, Sinix, Вы писали:

S>В общем народу понравилось, и теперь custom default struct constructors пропихивают в шарп. Верните Хейлсберга.


Нет. Сначала пропихните в шарп ПМ, а потом уже возвращайте кого хотите.
Если нам не помогут, то мы тоже никого не пощадим.
Отредактировано 02.03.2017 0:04 IT . Предыдущая версия .
Re[4]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 01.03.17 13:57
Оценка: +4
Здравствуйте, IT, Вы писали:

S>>Верните Хейлсберга.

IT>Нет. Сначала проаихните в шарп ПМ, а потом уже возвращайте кого хотите.

Некогда ж. Зато у нас есть тюплы, 8 синтаксисов для задекларить свойство и local-функции

Хотя не, вру. Ещё throw as expression есть. Который как бы часть match expression, но последнего пока нет. Не выбрасывать же
И вишенкой на торте: соблюдая традиции, codegen переехал в c# 9. Вроде бы переехал. И вроде бы в 9.


Как ни крути, при Хейлсберге такого эпичного раздолбайства не было.
Re: Минутка WTF-19: Catch me if you can
От: Философ Ад http://vk.com/id10256428
Дата: 02.03.17 15:03
Оценка: :)
Здравствуйте, Sinix, Вы писали:

Совсем подсказка должна заключаться в том, как вот такое скомпилировать.
using System;
using System.Collections.Generic;

#IF COMPILE_ME_IF_U_CAN
using System.Collections.Immutable;
#ENDIF


<TargetFrameworkVersion>v4.5</TargetFrameworkVersion> — Пробовал.
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> — тоже.

Уже потом можно будет будет шарады с этой штукой разглядывать.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[2]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 02.03.17 16:31
Оценка:
Здравствуйте, Философ, Вы писали:


Ф>Совсем подсказка должна заключаться в том, как вот такое скомпилировать.

  внезапно
Re[5]: Минутка WTF-19: Catch me if you can
От: _NN_ www.nemerleweb.com
Дата: 02.03.17 18:45
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Как ни крути, при Хейлсберге такого эпичного раздолбайства не было.

Зато есть прекрасный TypeScript
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[6]: Минутка WTF-19: Catch me if you can
От: fddima  
Дата: 02.03.17 23:14
Оценка:
Здравствуйте, _NN_, Вы писали:

S>>Как ни крути, при Хейлсберге такого эпичного раздолбайства не было.

_NN>Зато есть прекрасный TypeScript
Это тонкий троллинг? Язык хорошо разрекламирован, хороший доя этого класса синтаксис и абсолютно тупой компилятор и полное отсутствие модульности. Вы как хотите — но писать на goigle-closure — намного приятнее когда у вас кода под мегабайт и больше и есть привычка разделять тип на файл, что является общепринятой практикой. Инклуды через коменты — не являются заменой. При этом реализация с выкидыванием простейшего мертвого кода буквально на уровне файлов — элементарна. Так что нет. Это всё ещё игрушка.
Re[7]: Минутка WTF-19: Catch me if you can
От: _NN_ www.nemerleweb.com
Дата: 03.03.17 04:17
Оценка: 12 (1)
Здравствуйте, fddima, Вы писали:

F>Здравствуйте, _NN_, Вы писали:


S>>>Как ни крути, при Хейлсберге такого эпичного раздолбайства не было.

_NN>>Зато есть прекрасный TypeScript
F> Это тонкий троллинг? Язык хорошо разрекламирован, хороший доя этого класса синтаксис и абсолютно тупой компилятор и полное отсутствие модульности. Вы как хотите — но писать на goigle-closure — намного приятнее когда у вас кода под мегабайт и больше и есть привычка разделять тип на файл, что является общепринятой практикой. Инклуды через коменты — не являются заменой. При этом реализация с выкидыванием простейшего мертвого кода буквально на уровне файлов — элементарна. Так что нет. Это всё ещё игрушка.
Это уже в прошлом.
Есть import как полагается: https://www.typescriptlang.org/docs/handbook/modules.html#import
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[7]: Минутка WTF-19: Catch me if you can
От: TK Лес кывт.рф
Дата: 03.03.17 06:29
Оценка:
Здравствуйте, fddima, Вы писали:

F> Инклуды через коменты — не являются заменой. При этом реализация с выкидыванием простейшего мертвого кода буквально на уровне файлов — элементарна.


Комменты ничего не инклюдят — они просто говорят где брать типизацию.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[7]: Минутка WTF-19: Catch me if you can
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 03.03.17 09:43
Оценка:
Здравствуйте, fddima, Вы писали:

F>Здравствуйте, _NN_, Вы писали:


S>>>Как ни крути, при Хейлсберге такого эпичного раздолбайства не было.

_NN>>Зато есть прекрасный TypeScript
F> Это тонкий троллинг? Язык хорошо разрекламирован, хороший доя этого класса синтаксис и абсолютно тупой компилятор и полное отсутствие модульности. Вы как хотите — но писать на goigle-closure — намного приятнее когда у вас кода под мегабайт и больше и есть привычка разделять тип на файл, что является общепринятой практикой. Инклуды через коменты — не являются заменой. При этом реализация с выкидыванием простейшего мертвого кода буквально на уровне файлов — элементарна. Так что нет. Это всё ещё игрушка.

Ты видно давно не брал в руки TS. При этом нужно учитывать, что при компиляции может использоваться ES6 со всеми вкусностями.
Ты видно и Angular 2 в руки не брал. Googl как раз много делает для его развития именно в связке с TypeScript.

https://angular-2-training-book.rangle.io/handout/modules/introduction.html
и солнце б утром не вставало, когда бы не было меня
Re[3]: Минутка WTF-19: Catch me if you can
От: vdimas Россия  
Дата: 18.03.17 19:26
Оценка:
Здравствуйте, Sinix, Вы писали:

S>default(ImmutableArray<T>) бросает исключения при попытке обращения к почти любому member-у. Если обращение идёт напрямую, то бросается NullReferenceException, что я расцениваю как тончайший троллинг со стороны авторов структуры.


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


S>В общем народу понравилось, и теперь custom default struct constructors пропихивают в шарп. Верните Хейлсберга.


Это надо было пропихнуть одновременно с выходом генериков в 2005-м.
Дефолтная инициализация структур — это дыра в системе типов.
Не всякая дефолтная структура валидна. ImmutableArray — отличный пример.

Более того, было бы неплохо довести эту фичу до конца и дать возможность запретить дефолтный конструктор для структур, точно так же как для классов. Это всё для целей consistency, ес-но.
Re[4]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 18.03.17 19:58
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Т.е. значение этой обертки — это значение сохранённой внутри ссылки.

V>Задача такой обертки — максимально дешево транслировать вызовы на объект по ссылке унутре.

Чисто технически — да. С точки зрения разработчика под дотнет (и тем более — энтерпрайз разработчика) добавление такого типа в public api без подробной документации и без рослиновского аналайзера — признак серьёзнейших проблем в команде. Потому что либо код последовательно прошёл прототипирование, design review, тесты и внутренний догфудинг, либо команда работает без всего этого. Оба варианта хуже.

Не, может для кого-то добавление в библиотеку типа, использование которого приводит к крэшам студии, невозможности подтвердить лицензию студии, ошибкам в дизайнерах XAML и asp.net, исключениям в компиляторе, крэше рефакторинга в VB, багам в xunit, падению тестов newtonsoft.json и тыды и тыпы выглядит приемлемо, не знаю

Для меня — это залёт.


V>Это надо было пропихнуть одновременно с выходом генериков в 2005-м.

Ну, т.е. одного типа с неотлавливаемым исключением в произвольный момент времени мало, да?



V>Дефолтная инициализация структур — это дыра в системе типов.

V>Не всякая дефолтная структура валидна. ImmutableArray — отличный пример.
Матчасть читать надо

DO ensure that a state where all instance data is set to zero, false, or null (as appropriate) is valid.
This prevents accidental creation of invalid instances when an array of the structs is created. For example, the following struct is incorrectly designed. The parameterized constructor is meant to ensure a valid state, but the constructor is not executed when an array of the struct is created and so the instance field value gets initialized to 0, which is not a valid value for this type.

(с) FDG book.
Т.е. если официальные гадлайны требуют трактовать default(ImmutableArray) как пустой массив, — тем хуже для гадлайнов.


V>Более того, было бы неплохо довести эту фичу до конца и дать возможность запретить дефолтный конструктор для структур, точно так же как для классов. Это всё для целей consistency, ес-но.

Не вопрос. Благодаря этой чудесной политике "совместимость? не, не слышал" .net core сегодня полумёртв как платформа. Ибо переход на него с взрослого дотнета ничем принципиально не отличается от перехода на любую другую платформу. Хотя не, вру. С остальными платформами рисков меньше.
Re[5]: Минутка WTF-19: Catch me if you can
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 18.03.17 20:15
Оценка:
Здравствуйте, Sinix, Вы писали:
S>Не вопрос. Благодаря этой чудесной политике "совместимость? не, не слышал" .net core сегодня полумёртв как платформа. Ибо переход на него с взрослого дотнета ничем принципиально не отличается от перехода на любую другую платформу. Хотя не, вру. С остальными платформами рисков меньше.
То есть NetStandard 2 не изменит твоего мнения?

Да есть проблемы переноса, но новый проект лучше делать на .Net Core, так как он развивается в отличие от ...
и солнце б утром не вставало, когда бы не было меня
Отредактировано 18.03.2017 20:16 Serginio1 . Предыдущая версия .
Re[6]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 18.03.17 20:30
Оценка: +2 -1
Здравствуйте, Serginio1, Вы писали:

S> То есть NetStandard 2 не изменит твоего мнения?


А моё мнение тут при чём? Оно как раз в противоположную сторону повёрнуто, т.к. я хоть и краем глаза, но за этим праздником жизни поглядываю.

А вот народ, который принимает решения — он куда как менее оптимистичен, причём не в одной и не в двух конторах. Как я понял из обсуждений, логика такая: ошибки в новых проектах, сырость инструментария и прочее — фиг с ним, рабочий момент. А вот принципиально неверные решения, которые идут вразрез с прошлым опытом, но продавливаются до последнего и разруливаются только постфактум, после выкатки даже не бет — релиза — это ой. Ну а если такое повторяются раз за разом, причём по нескольким проектам внутри одной компании — только любим помним скорбим. Желательно заранее, чтобы потом в очереди не толкаться.

С моей точки зрения всё не так мрачно, но будем посмотреть.
Re[4]: Дефолтная инициализация структур
От: Qbit86 Кипр
Дата: 18.03.17 20:39
Оценка: +1 :)
Здравствуйте, vdimas, Вы писали:

S>>В общем народу понравилось, и теперь custom default struct constructors пропихивают в шарп. Верните Хейлсберга.


V>Дефолтная инициализация структур — это дыра в системе типов.

V>Это надо было пропихнуть...

Обсуждаемые безаргументные конструкторы — это не про дефолтную инициализацию структур. Семантика `default(S)` не поменяется — это зануление. Речь про `new S()`.

V>...одновременно с выходом генериков в 2005-м.


Безаргументные конструкторы изначально были в CLI. И даже в превью C# побывали в течение небольшого времени в 2015 году, насколько я помню. Но в релизе C# 6.0 их выпилили из-за нюансов совместимости.
Глаза у меня добрые, но рубашка — смирительная!
Re[5]: Минутка WTF-19: Catch me if you can
От: vdimas Россия  
Дата: 19.03.17 12:07
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Для меня — это залёт.


Это из-за отсутствия практики оперирования умными указателями.
Их точно так же надо проверять на null, как и обычные, т.е. такие типы надо рассматривать как ссылочные.

Представь, что вы обычные ссылочные типы перестанете проверять на null? ))

Но меня тут ДРУГОЙ залёт рассмешил.
Он в самой системе типов C# (недавно обсуждали с коллегой samius).

Фишка вот в чём:
— если брать тру-генерик-алгоритмы с ограничениями, то с такой структурой будет происходить работа без боксинга:
void MyAlgorithm<T>(T collection) where T : IEnumerable<T>
{...}

Но тогда невозможна будет ad hoc перегрузка сигнатуры:
void MyAlgorithm<T>(T collection) where T : IQueryable<T>
{...}

Для сравнения, в Хаскель такая перегрузка сигнатур возможна и является основным ср-вом построения иерархий типов в программах на ём.

В дотнете же приходится делать так:
void MyAlgorithm<T>(IEnumerable<T> collection)  
{...}

void MyAlgorithm<T>(IQueryable<T> collection) 
{...}

Все базовые либы построены именно по такому паттерну, и в этом месте возникает дополнительный боксинг такой обертки.
Внутри MyAlgorithm происходит проверка на null, ОК, но боксированная структура не будет null-ом никогда.
Вот причина засады.
Она фундаментальна, её не перепрыгнешь.

Я бы тут предложил для ValueType ввести статический метод:
class SmartPtr {
...
  public static implicit operator Box<SmartPtr>(SmartPtr value) {
    if(value.IsEmpty)
      return null;

    return new Box<SmartPtr>(this);
  }
}


Некий тип Box всё-равно присутствует унутре VM, можно этот тип как-то отобразить наружу.
Ведь именно тип Box реализует (в смысле подставляет) виртуальные методы value-типа.


V>>Это надо было пропихнуть одновременно с выходом генериков в 2005-м.

S>Ну, т.е. одного типа с неотлавливаемым исключением в произвольный момент времени мало, да?

Наоборот, возможность строгой инициализации исключит вероятность таких ошибок даже в случае дефолтного боксинга.


V>>Дефолтная инициализация структур — это дыра в системе типов.

V>>Не всякая дефолтная структура валидна. ImmutableArray — отличный пример.
S>Матчасть читать надо

Ой, начинается... ))
Тут как раз НЕ проблема сделать так, чтобы для value-полей объектов или для элементов массива вызывались конструкторы структур по-умолчанию.

Т.е., если мы определили некую структуру:
struct ImmutableArray<T> {
  public SmartPointer() {
    _array = Array.Empty<T>();  
  }

private:
  T[] _array;
}

То ничего страшного от вызова дефолтных конструкторов в полях объектов или в массивах не случится. На этом сидит С++ и неплохо себя чувствует.
Совсем другое дело, когда дефолтный конструктор запрещен, фича из С++:
struct NotNull<T> where T : class {
  public NotNull(T obj) {
    if(obj == null) throw ...;
    _obj = obj;
  }

  NotNull() = delete;    // или объявлен в приватной секции

private:
  T _obj;
}


Тогда создать массив таких структур можно будет только с подачей дефолтного значения (как в oCaml), типа так:
using MyTypeRef = NotNull<MyType>;

var defaultValue = new MyTypeRef(MyType::DummyObj);
MyTypeRef[] values = new MyTypeRef[42](defaultValue);


А для полей других типов требовать явной инициализации через один из доступных конструкторов или методов-фабрик.


S>Т.е. если официальные гадлайны требуют трактовать default(ImmutableArray) как пустой массив, — тем хуже для гадлайнов.


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


V>>Более того, было бы неплохо довести эту фичу до конца и дать возможность запретить дефолтный конструктор для структур, точно так же как для классов. Это всё для целей consistency, ес-но.

S>Не вопрос. Благодаря этой чудесной политике "совместимость? не, не слышал"

Похоже, ты недочитал.
Не запретить, а дать возможность запретить.
Т.е. речь идёт о неких новых типах.


S>.net core сегодня полумёртв как платформа. Ибо переход на него с взрослого дотнета ничем принципиально не отличается от перехода на любую другую платформу. Хотя не, вру. С остальными платформами рисков меньше.


Я тебя умоляю. Это не проблема программистов, это проблема популярности Win10/UWP на ноутах и девайсах.

Если будет спрос на такие девайсы, то переход будет легким, радостным и непременное самоинициативным у каждого конкретного разработчика.
Re[6]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 19.03.17 12:59
Оценка: +1
Здравствуйте, vdimas, Вы писали:

S>>Для меня — это залёт.

V>Это из-за отсутствия практики оперирования умными указателями.

Блин, я уж не знаю как объяснить.

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

В очередной версии ты игноришь свои же рекомендации, которые соблюдались с полтора десятка лет и в public API добавляешь тип, который нельзя передавать и принимать в качестве аргумента без валидации в каждом из методов. Иначе рано или поздно приложение скрэшится с null ref exception. Понятно, что ни один вменяемый девелопер не будет перехватывать null ref где-то в теле метода, это изврат из серии on error resume next.

Ну, т.е. ты добавил в public API тип, который бросает неперехватываемые исключение.

И теперь начинается момент, который ты не слышишь: в ответ на "WTF, бро?" клиенты слышат не "ну упс, вот документация, вот аналайзер для рослина / правило решарпера/ещё что-нибудь, вот guidelines, не повторяйте наших ошибок". Неа, им начинают продавать отсутствие практики работы с смартпойтерами, рецепты лучших слоноводов и прочий набор баззвордов для буллшит бинго. Причём предыдущие n версий всё как-то работало без вышеперечисленного

Не, с технической стороны всё ок, но с точки зрения клиентов всё выглядит так, как я написал выше.
При этом клиенты знают, что похожее решение уже пробовали (code contracts пытались протолкнуть моду на неперехватываемые исключения) и это решение (в числе прочих) привело к смерти продукта. Т.е. негативный опыт игнорится.

Также клиенты припоминают, что такие же косяки с полным игнором фидбэка за последние пару лет встречались с полдесятка раз, причём по всем командам, от рослина с EF и до corefx. В связи с чем возникает резонный вопрос: а не пора ли раскидать яйца по другим корзинкам?

Вот в этом реальная проблема, а не в мелочах типа

V>Но тогда невозможна будет ad hoc перегрузка сигнатуры:


которые будут решены через релиз. Если ничего не поломается опять, конечно.



V>Все базовые либы построены именно по такому паттерну, и в этом месте возникает дополнительный боксинг такой обертки.

Кэп: к моменту, когда тебя в плане производительности будет волновать лишний боксинг, все IEnumerable и Queryable будут давно выпилены из hotpath. Реальные проблемы искать и чинить надо, а не те, про которые народ на форуме пишет



V>Внутри MyAlgorithm происходит проверка на null, ОК, но боксированная структура не будет null-ом никогда.

V>Вот причина засады.
V>Она фундаментальна, её не перепрыгнешь.

Неа. Причина засады в том, что кто-то "догадался" сделать структуру IEnumerable/IQueryable. Всё остальное — уже следствие. Ну, как сломать палец, а затем жаловаться что стенки слишком твёрдые — тыкать в них больно.


S>>Ну, т.е. одного типа с неотлавливаемым исключением в произвольный момент времени мало, да?

V>Наоборот, возможность строгой инициализации исключит вероятность таких ошибок даже в случае дефолтного боксинга.
Вот без обид, но не надо рассказывать, как сделать язык 1 на знаниях языка 2. Потому что твоё требование ломает пустые массивы, десериализацию, orm, маппинг, интероп, dynamic, expression trees, code emit и ещё кучу вещей в фреймворке. И заодно блочит рельно perf-critical вещи типа Span<T>.

Ну, т.е. или троллинг, или полное незнание внутреннего устройства.
В любом из вариантов спор на тему "что не так в фреймворке" не получится


S>>.net core сегодня полумёртв как платформа. Ибо переход на него с взрослого дотнета ничем принципиально не отличается от перехода на любую другую платформу. Хотя не, вру. С остальными платформами рисков меньше.

V>Я тебя умоляю. Это не проблема программистов, это проблема популярности Win10/UWP на ноутах и девайсах.

Ну, т.е. оно само так получилось, а не как результат кучи мегафакапов, в том числе и политики в отношении разработчиков, да?
По факту ситуация такая: консумерский десктоп мёртв, для "я хочу продавать продукт по цене чашки кофе" есть андроид и яблоко и там всё пока неплохо с xamarin/unity. Вопрос про веб, энтерпрайз и облако.
Re[7]: Минутка WTF-19: Catch me if you can
От: vdimas Россия  
Дата: 19.03.17 13:45
Оценка:
Здравствуйте, Sinix, Вы писали:

S>>>Для меня — это залёт.

V>>Это из-за отсутствия практики оперирования умными указателями.
S>Блин, я уж не знаю как объяснить.

А и не надо. Это ж букварь.
А у меня стёб над ним и дележ другими практиками.

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

Тип ImmutableArray — тому подтверждение.
Лично мне тут весело, потому что аналог ImutableArray у меня появился сразу же во всех дотнетных проектах, как только появились генерики.
И не только у меня.
И плевали все на эти лузерские гайды.
Ведь не только я плевал, я же не в вакууме живу — я видел просто тонны примеров, когда вменяемые разработчики закрывали глаза и пилили value-обертки над ref-классами.
Потому что смириться с чудовищной стоимостью абстракций непросто.
Тут или отказываться от абстракций, или отказываться от дотнета, или закрывать глаза на гайды.
ИМХО, выбор тут слишком очевиден, чтобы пытаться даже рыпаться в сторону защиты неких гайдов.

Помнишь я тут как-то показывал увеличение в 2.5 раза на дотнете с пол-тыка за счет уменьшения косвенности?
В реальных приложухах за счёт этого до 3-4-х раз идёт улучшение эффективности, когда вообще всё перерабатывается через такой же трюк.
А какой срач вдруг разыгрался, помнишь?
Причем, придирались как раз ко всякой совершенно не принципиальной фигне.
Показательно. Потому что им шашечки, а мне ехать.
И для меня это надёжнейшая лакмусовая бумажка для отделения карьеронацеленных очковтирателей от полезных людей.
Потому что гайды по первым практикам писались саботажниками-диверсантами, которых давно уже погнали ссаными тряпками из MS, слава богу.
Всё, забудь про них.
Липперт принёс плохую карму в MS, отмываться надо будет еще о-о-очень долго.
Вот что бывает, когда средней руки инженер превращается в маньяка-карьериста.

И пофик на "кол-во вовлечённых разработчиков", реально.
Интересовать может лишь полезный имеющийся код и понимание приоритета следующих для решения задач, а не половые трудности тяжелых на подъем разработчиков.

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

Разве сам не увидел, что тут вина разработчиков ImmutableArray лишь в том, что они "побежали впереди паровоза"?
Почему мне надо озвучивать вслух те банальности, что им тоже не шашечки, а ехать надо было.
Просто у них вполне определённых навыков еще не было, вот и спотыкаются пока:

Это из-за отсутствия практики оперирования умными указателями.
Их точно так же надо проверять на null, как и обычные, т.е. такие типы надо рассматривать как ссылочные.

Ну ничего, дотнет не сразу строился когда-то.
А сейчас ему (дотнету) кровь из носу надо перестаиваться или он исчезнет на твоё удивление слишком быстро.
Так шта, тебе тут выбирать — или расширять свои практики, или попрощаться с любимым инструментом.
Выбор очевиден, ИМХО.
И твои заламывания рук — это, таки —

А ошибки исправят. MS обычно оперативно правит ошибки, тут у них есть чему поучиться.
Например, можно везде в linq и прочих методах-расширениях ввести альтернативные сигнатуры сугубо для аргумента ImmutableArray<T>, где проверить его на пустоту ПРАВИЛЬНО. Затем достаточно будет лишь пересобрать имеющиеся исходники и ву а ля? NullReferenceException будет вылетать ровно там, где его первый раз подают, а не где пытаются использовать спустя километры тиков процессора. ))
Re[5]: Дефолтная инициализация структур
От: vdimas Россия  
Дата: 19.03.17 13:56
Оценка:
Здравствуйте, Qbit86, Вы писали:

V>>Дефолтная инициализация структур — это дыра в системе типов.

Q>Обсуждаемые безаргументные конструкторы — это не про дефолтную инициализацию структур.

Не надо меня поправлять, я выразился ровно так, как и собирался.


Q>Семантика `default(S)` не поменяется — это зануление. Речь про `new S()`.


Это и есть дыра. Потому что var s = default(S); или return default(S);
Дырой я называю такую ситуацию, когда обнулённое значение структуры невалидно.

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


Q>Безаргументные конструкторы изначально были в CLI. И даже в превью C# побывали в течение небольшого времени в 2015 году, насколько я помню. Но в релизе C# 6.0 их выпилили из-за нюансов совместимости.


Из-за того, что уже 2015-й год, а не 2005-й.
Надо было раньше...
Но они к этому всё-равно придут.
Просто релиз поджимал, некоторые фичи к релизу не успевали.
Не успели к этому, сделают к одному из последующих.

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

Там ведь слишком много зависимых моментов тянутся влед за всеми этими вещами.
Например, в С++ std::vector<T> может предвыделить память, а инициализировать значениями позже.
Аналогичный List<T> в дотнете под собой имеет вполне типизированный array в кач-ве буфера, элементы которого надо как-то инициализировать не позже, а прям сразу. Вот тебе достойная задачка для пошевелить шестерёнками, размять мозжечок, такскаать. ))
Отредактировано 19.03.2017 13:57 vdimas . Предыдущая версия .
Re[7]: Минутка WTF-19: Catch me if you can
От: vdimas Россия  
Дата: 19.03.17 14:02
Оценка:
Здравствуйте, Sinix, Вы писали:

V>>Но тогда невозможна будет ad hoc перегрузка сигнатуры:

S>которые будут решены через релиз. Если ничего не поломается опять, конечно.

Угу, там же нашел ссылку опять на Липперта:
https://blogs.msdn.microsoft.com/ericlippert/2009/12/10/constraints-are-not-part-of-the-signature/

Какой же он всё-таки нуб.
Re[8]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 19.03.17 14:24
Оценка:
Здравствуйте, vdimas, Вы писали:


V>Потому что те гайды были над некими одними практиками, но сейчас требуются другие по той причине, что первые показали свою несостоятельность. И ты это не перепрыгнешь никак, ты же не можешь и дальше закрывать глаза на несостоятельность старых практик?


Могу и буду Аргументацию давай. Пока с одной стороны дизайн с запасом прочности, которого хватило как минимум на пятнадцать лет. С другой — народ, который матчасть не изучал, проблем до конца не видит, но хочет, чтоб усё было

Силы не равны, кмк.

V>И плевали все на эти лузерские гайды.

Именно это команда .net core и делает. Точнее, делала, а затем мегаавральными темпами последний год исправляла. Не получилось игнорить почему-то


V>Помнишь я тут как-то показывал увеличение в 2.5 раза на дотнете с пол-тыка за счет уменьшения косвенности?

Не помню, подобных обсуждений куча была.

V>В реальных приложухах за счёт этого до 3-4-х раз идёт улучшение эффективности, когда вообще всё перерабатывается через такой же трюк.

В смысле, +300% end-to-end throughput за счёт избавления от виртуальных вызовов? Я таки хочу на это посмотреть. Кинь ссылочку, плиз


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

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


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


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


V>И я недоумеваю — почему топовые дотнетные специалисты этого форума САМИ не предположили ровно эти же изменения как вариант решения вполне конкретной инженерной задачи? Что с вами не так-то?


М.б. потому что мы таки работаем на реальных проектах и таки видим реальные проблемы, которые реально имеет смысл фиксить?
Не, ну серьёзно, смысл тратить усилия команды на хардкорную оптимизацию кода на шарпе, если вынос критичного куска кода в натив даёт пятикратный выигрыш и обходится где-то в неделю работы?

V>Разве сам не увидел, что тут вина разработчиков ImmutableArray лишь в том, что они "побежали впереди паровоза"?

V>Например, можно везде в linq и прочих методах-расширениях ввести альтернативные сигнатуры сугубо для аргумента ImmutableArray<T>, где проверить его на пустоту ПРАВИЛЬНО.

Это решается куда дешевле — аналайзером рослина, который гарантирует, что метод не возвращает и не сохраняет в поля пустой immutable array. Готовое API для data flow analysis есть, если что. Но этого тоже не было сделано.
Re[9]: Минутка WTF-19: Catch me if you can
От: vdimas Россия  
Дата: 19.03.17 17:30
Оценка:
Здравствуйте, Sinix, Вы писали:

V>>Потому что те гайды были над некими одними практиками, но сейчас требуются другие по той причине, что первые показали свою несостоятельность. И ты это не перепрыгнешь никак, ты же не можешь и дальше закрывать глаза на несостоятельность старых практик?

S>Могу и буду Аргументацию давай.

Я уже давал:
задача правильно поставленного процесса разработки — это наработка и циркуляция в коллективе удачных практик.

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

===========
Умный работодатель от таких избавляется. Я таких наблюдал не раз в своей работе, когда кто-то начинает с важным видом втирать то про "стандарты", то про "правила хорошего тона", что прям можно подумать, вокруг одни дураки или только из яйца вылупились. ))
Впервые, по молодости, натурально остолбеневал... то ли от бесконечной глупости, то ли от бесконечной циничности происходящего.
Обычно всё то, что с таким важным видом втирается, окружающие давно проглотили и неоднократно выкакали.
Т.е. лично я расцениваю подобные закидоны как смесь дикого невежества + необузданного хамства в одном флаконе.

Просто мне повезло несколько первых лет деятельности проработать среди суровых высококлассных специалистов, где о фигне подобного рода даже не могло быть и речи, ес-но, бо там дурака называли в лицо дураком сходу, без прелюдий. )) Зато как попал в более-менее большие чиста программерские коллективы... у-у-у-у, насмотрелся. ))

И как назло, почти всегда толку от именно таких программистов — практически ноль. Потому что работа программиста в том и заключается, что стандарты плавают, документация не актуальна, правила хорошего тона слишком общи, но задачу надо решать, причем, не самым худшим из способов. Потому что "зато я следовал стандартам/гадлайнам!" — это отмазка из разряда как чиновник бумажкой себе попу прикрывает "на всякий пожарный". Это не работа, это уже симуляция работы получается. Почти всегда такую "работу" надо кому-то переделывать по-людски.

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

Кароч. Гадлайнам надо не только следовать, их надо активно разрабатывать в процессе своей деятельности. Собсно, они сами должны оседать как "побочный эффект вычислений". Это если ты именно инженер, ес-но, а не старательный симулятор активной деятельности.


S>Пока с одной стороны дизайн с запасом прочности, которого хватило как минимум на пятнадцать лет.


Враки же.
Я с легковесными обертками-структурами возился сразу и сразу же спотыкался об эту дыру в системе типов, когда для value-type невозможно выразить инварианты в прикладном виде.

Причем, меня тут некоторые не особо углублявшиеся в суть предмета коллеги даже пытались высмеивать:
http://www.rsdn.org/forum/flame.comp/6660730.1

Собсно, вообще таких алгоритмов мало, которые можно выразить в дотнете для value и ref-типов в генериках и они будут корректно работать в обоих случаях. Я одно время на этом собаку съел, в поисках классов таких алгоритмов, которые, таки, работают в обоих случаях и в попытках сформулировать ограничения на такие алгоритмы.

Полюбуйся, отвечающий там даже топики переименовывал следом, в "Собаку съел". ))

А теперь делает лицо кирпичом и пытается участвовать в обсуждении вопроса, который еще совсем недавно всячески высмеивал.
Сюрр, как по мне. ))
Вот, рядом пытается меня поправлять, не понимая, как и тогда, и половины:
http://www.rsdn.org/forum/dotnet/6729409.1


S>С другой — народ, который матчасть не изучал, проблем до конца не видит, но хочет, чтоб усё было


Да проблема-то простая, хосподя.
Вот такая операция:
memset(address_of(someVar), size_of(someVar), 0) — это грубо запоротая память.

Вот рядом тоже я Влада2 порол точно так же:

А вот за это руки поотрывать и отлучать от программирования на веки вечные (ValueOption[T]):

public GetValueOrDefault() : T { _value }

Ты что творишь-то?
Это же одна из бед дотнета как такового, что некое "дефолтное значение" некоей прикладной структуры может являться не валидным значением, т.е. источником ошибок.

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


Т.е., проблема-то древняя. Я на неё уже 15 лет смотрю, и криво ухмыляюсь.

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


S>Силы не равны, кмк.


Чем больше будет в сторону нейтива, тем равнее будут силы.
Тут ведь всё просто до безобразия — у нейтива есть свои эффективные практики и надо их как-то начинать вносить в дотнет.
Одна из первейших практик — 99% абстракций должны быть абсолютно бесплатны.
Это, считай, закон такой.
Дотнет-то тормозит не столько из-за некачественной оптимизации JIT, сколько из-за порождения развесистого ссылочного графа в памяти.


V>>И плевали все на эти лузерские гайды.

S>Именно это команда .net core и делает. Точнее, делала, а затем мегаавральными темпами последний год исправляла. Не получилось игнорить почему-то

Почему "почему-то"?
Я ведь не зря про 2005-й год сказал.
Они тогда дополнили формат сборок, сильно доработали стандарты метаинформации.
Считай, что 2-й дотнет — это ДРУГАЯ платформа, чем дотнет 1.x.
Вот тогда и надо было с функциональным типом разобраться и с конструкторами структур.
К тому же еще инерционность была небольшая.
После 2-го дотнета стандарт метаинформации, считай, не менялся вовсе.
Потому что поезд тю-тю, уехал. Стало страшно его догонять и прыгать в него на ходу.
Но сейчас приходится делать именно это.


V>>В реальных приложухах за счёт этого до 3-4-х раз идёт улучшение эффективности, когда вообще всё перерабатывается через такой же трюк.

S>В смысле, +300% end-to-end throughput за счёт избавления от виртуальных вызовов? Я таки хочу на это посмотреть. Кинь ссылочку, плиз

Нет, не виртуальных. Я заменил все эти List<> на работу с голыми массивами.
Когда массив оборачивается структурой, получается такая же скорость.
(если найду, кину ссылку)
И там же писал о том, что пришлось сделать клоны основных коллекций в виде value-type и многие относительно большие объекты тоже сделать в виде value-type (по ref пуляешь и огонь, всё чудесно работает), но при этом так же пришлось в ручном режиме соблюдать определённую гигиену такого кода. Потому что именно из-за дыры в пустой инициализации структур.


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

S>Липперт скорее популяризатор, чем проектировщик базовых вещей.

Именно. Я его за популяризаторство того самого "псевдоинженерного подхода", который во все времена резко высмеивался, и ругаю.
Вот этот его сироп из разряда: "Здесь может показаться, что эффективней было бы сделать так-то и так-то, но давайте на минуточку представим... и бла-бла-бла", песня льётся соловьём. Поубивал бы. )) Столько хороших людей испортил в 2000-е. Ты вспомни что тут на сайте в середине 2000-х творилось — это же было торжество отсутствия здравого смысла. Это была жесть как она есть.

А еще знаешь, что в них всех все годы бесило? — показушность.
Им надо было показать, как у них всё хорошо получается.
Я терпеть этого в колегах не могу, если честно.
Потому что каждый божий раз, когда я такое вижу вживую, я при этом обязательно вижу, что человек работает "на отгребись" (в оригинале нецензурщина). Причем, стоит только попытаться таким людям показать, что, вообще-то, вот тут у тебя всё по наилегчайшему пути и вообще не фонтан, есть же вот известные практики, дающие намного больше толку в рантайме (мы же для рантайма ПО пишем), так там всегда мега-агрессия.

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

В этом смысле Липперта надо было хоронить еще в 2004-м, там диагноз уже был слишком очевиден, ИМХО.


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

S>Самое прикольное, что оно нафиг не нужно, особенно если сравнить затраты и выигрыш.

И что ты хочешь услышать в ответ?
Это ж как раз именно такие практики, которые повышают детерминированность программ.
В функциональном программировании, в С++, в D и во всех этих новых языках есть такая практика — "осмысленное" конструирование значения.


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


Факт наличия других независимых задач ортогонален, как бы. ))


S>Вот это даст эффект. Всё остальное — полумеры.


Э нет. Полумеры — это озвученное тобой. Если в памяти так и будет граф, если 70-80% памяти дотнетной программы (даже после конвертации в .Net Native) будут занимать ссылки, то это всё так и будет заметно отставать.

Насчет делегатов — да. Но для этого нужен полноценный функциональный тип, а его нет.


S>М.б. потому что мы таки работаем на реальных проектах и таки видим реальные проблемы, которые реально имеет смысл фиксить?


А я на нереальных, что ле, работал?
Просто, может, у вас проекты такие, что эффективность не нужна.
Ну граф и граф...
Ну и что, что полезных данных только 20% в памяти... ))

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


S>Не, ну серьёзно, смысл тратить усилия команды на хардкорную оптимизацию кода на шарпе, если вынос критичного куска кода в натив даёт пятикратный выигрыш и обходится где-то в неделю работы?


Верно. В хорошо поставленном вопросе содержится половина ответа.
Тебе ведь на нейтив надо выйти ровно затем, чтобы состряпать бесплатные абстракции и получить не граф объектов с бесконечным кол-вом узлов, а вполне счетное кол-во плоских "островков". Достаточно добавить такую же возможность в дотнет и не надо будет бегать в нейтив.

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


V>>Разве сам не увидел, что тут вина разработчиков ImmutableArray лишь в том, что они "побежали впереди паровоза"?

V>>Например, можно везде в linq и прочих методах-расширениях ввести альтернативные сигнатуры сугубо для аргумента ImmutableArray<T>, где проверить его на пустоту ПРАВИЛЬНО.
S>Это решается куда дешевле — аналайзером рослина, который гарантирует, что метод не возвращает и не сохраняет в поля пустой immutable array.

Это не дешевле, это дуля в правом кармане левой рукой. ))
Необходимо дать возможность компилять исходники даже обычным csc.exe из минимального тулчайна.
Поэтому, проще добавить сигнатуры в linq.
Это вообще работа на пару дней от силы.
Ну или можно накатать самому такое же расширение и подключать его в область видимости.
Пруф банален:
    struct ImmutableArray<T>
    {
        internal T[] _array;

        public bool IsEmpty
        {
            get { return _array == null; }
        }

        public ImmutableArray(T[] a)
        {
            _array = a;
        }
    }

    static class ImmutableArrayExtentions
    {
        private static void CheckNotNull<T>(ImmutableArray<T> ar) 
        {
            if (ar.IsEmpty)
                throw new NullReferenceException();
        }

        public static ImmutableArray<T> ToImmutable<T>(this T[] ar)
        {
            return new ImmutableArray<T>(ar);
        }

        public static int Sum(this ImmutableArray<int> source)
        {
            CheckNotNull(source);
            return Enumerable.Sum(source._array);
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            var ar = new int[] { 1, 2, 3 };
            var imm = ar.ToImmutable();
            var s = imm.Sum();  // ok
            int[] nar = null;
            var nimm = nar.ToImmutable();
            var ns = nimm.Sum();  // exception thrown
        }
    }

Причем, в моём пруфе мы убегаем от лишнего боксинга, заметь.


S>Готовое API для data flow analysis есть, если что. Но этого тоже не было сделано.


data flow analysis не так прост.
Это, собсно, "передовая грань" современного IT как такового.
Так еще своих собак не съели в нужном кол-ве.
Потому что вот это странно:

не сохраняет в поля пустой immutable array

Это с какой радости? Чего-то я не могу сохранить null-ссылку?
За что меня бить по рукам-то?
Отредактировано 19.03.2017 18:00 vdimas . Предыдущая версия .
Re[10]: Минутка WTF-19: Catch me if you can
От: Sinix  
Дата: 19.03.17 19:37
Оценка: 18 (1) +1 :)
Здравствуйте, vdimas, Вы писали:

V>Я уже давал:

V>задача правильно поставленного процесса разработки — это наработка и циркуляция в коллективе удачных практик.

Мы как-то по кругу ходим. Я выше уже дал примеров 5 реальных багов, связанных с использованием ImmutableArray<T>. Но ты продолжаешь упорно доказывать, что тип ок, это девелоперы неправильные. Если это твоя принципиальная позиция — ок, закругляемся.


V>И как назло, почти всегда толку от именно таких программистов — практически ноль. Потому что работа программиста в том и заключается, что стандарты плавают, документация не актуальна, правила хорошего тона слишком общи, но задачу надо решать, причем, не самым худшим из способов.


Эмм, ты описываешь worst practices и на этой основе доказываешь, что best не нужны? * чешет в затылке.


V>Кароч. Гадлайнам надо не только следовать, их надо активно разрабатывать в процессе своей деятельности. Собсно, они сами должны оседать как "побочный эффект вычислений". Это если ты именно инженер, ес-но, а не старательный симулятор активной деятельности.


Бинго. Только перед этим готовые рекомендации надо знать. Досконально. Чтобы не тратить время на детские ошибки и не вставать в позицию "я на эти грабли ещё не наступал => все, кто меня предупреждают == нубы". Вот как тут:


V>>>В реальных приложухах за счёт этого до 3-4-х раз идёт улучшение эффективности, когда вообще всё перерабатывается через такой же трюк.

S>>В смысле, +300% end-to-end throughput за счёт избавления от виртуальных вызовов?
V>Нет, не виртуальных. Я заменил все эти List<> на работу с голыми массивами.

Кто-то что-то мерял не так. Скорее всего, ещё и под старым x86 JIT-ом. C RyuJit не будет там разов, если дело именно в косвенных вызовах. 5..10 процентов — верю, больше — нет.
            Method |          Mean |     StdDev | Scaled | Scaled-StdDev |
------------------ |-------------- |----------- |------- |-------------- |
    TestRefWrapper |   963.0174 ns | 35.6947 ns |   1.00 |          0.00 |
 TestStructWrapper |   870.5784 ns | 12.3793 ns |   0.91 |          0.03 |
          TestList | 1,210.3565 ns | 26.7783 ns |   1.26 |          0.05 |
     TestListReset | 1,228.1166 ns | 51.7045 ns |   1.28 |          0.07 |
         TestArray |   875.6539 ns | 14.3256 ns |   0.91 |          0.03 |


В чём разница между RefWrapper и Lizt<T> — посмотри сам. Сам напросился — нефиг рассказывать про перфоманс с позиции "мне _кажется_, что причина в косвенных вызовах"

  пруфкод
using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace PerfTestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            if (IntPtr.Size !=8)
                throw new InvalidOperationException("As x64");
            new MemberAccessBenchmark().Run();
        }
    }

    public class MemberAccessBenchmark
    {
        private class RefWrapper
        {
            private int[] _data;

            public RefWrapper(int[] data)
            {
                _data = data;
            }

            public int this[int index] => _data[index];

            public int Length => _data.Length;
        }

        private struct StructWrapper
        {
            private int[] _data;

            public StructWrapper(int[] data)
            {
                _data = data;
            }

            public int this[int index] => _data[index];
            public int Length => _data.Length;
        }

        private RefWrapper _ref = new RefWrapper(Enumerable.Range(1, 1000).ToArray());
        private StructWrapper _val = new StructWrapper(Enumerable.Range(1, 1000).ToArray());
        private List<int> _list = new List<int>(Enumerable.Range(1, 1000).ToArray());
        private List<int> _listReset = new List<int>(Enumerable.Range(1, 1000).ToArray());
        private int[] _array = Enumerable.Range(1, 1000).ToArray();

        public void Run() => BenchmarkRunner.Run(GetType());

        [Setup]
        public void Setup()
        {
            _listReset.Clear();
            _listReset.AddRange(_array);
        }

        [Benchmark(Baseline = true)]
        public long TestRefWrapper()
        {
            var local = _ref;
            var result = 0L;
            for (int i = 0; i < local.Length; i++)
                result += local[i];
            return result;
        }

        [Benchmark]
        public long TestStructWrapper()
        {
            var local = _val;
            var result = 0L;
            for (int i = 0; i < local.Length; i++)
                result += local[i];
            return result;
        }

        [Benchmark]
        public long TestList()
        {
            var local = _list;
            var result = 0L;
            for (int i = 0; i < local.Count; i++)
                result += local[i];
            return result;
        }

        [Benchmark]
        public long TestListReset()
        {
            var local = _listReset;
            var result = 0L;
            for (int i = 0; i < local.Count; i++)
                result += local[i];
            return result;
        }

        [Benchmark]
        public long TestArray()
        {
            var local = _array;
            var result = 0L;
            for (int i = 0; i < local.Length; i++)
                result += local[i];
            return result;
        }
    }
}


Остальное поскипал, т.к. там тоже надо сначала матчасть объяснять, а мне это уже влом.
Re[6]: Дефолтная инициализация структур
От: AngeL B. Россия  
Дата: 19.03.17 20:45
Оценка: +2
Здравствуйте, vdimas, Вы писали:

V>Это и есть дыра. Потому что var s = default(S); или return default(S);

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

Не совсем верно.
Валидность или невалидность внутреннего представления определяется внутренней логикой объекта (класса, структуры), т.е. кодом его методов (в том числе конструкторов).
Я специально зашел посмотреть на исходный код ImmutableArray<T>. И у меня простой вопрос.
Как так получилось, что во всех методах/свойствах этого типа данных ПРЕДПОЛАГАЕТСЯ, что поле array != null? И это при невозможности определить для структур конструктор по умолчанию.

Так что по моему мнению, спор в первую очередь должен идти не об архитектуре или каких-то там дополнительных конструкторах.
Главный вопрос должен звучать — Каким образом такой "небезопасный" код прошел в продакшен.
Re[7]: "Небезопасный" код
От: Qbit86 Кипр
Дата: 19.03.17 21:08
Оценка:
Здравствуйте, AngeL B., Вы писали:

AB>Как так получилось, что во всех методах/свойствах этого типа данных ПРЕДПОЛАГАЕТСЯ, что поле array != null? И это при невозможности определить для структур конструктор по умолчанию.


В общем случае для класса нет способа трактовать zero-initialized состояние как валидное. Sinix предлагает трактовать его как пустой array. Но разработчики решили не делать специальных вариантов для этого случая, а трактовать его как любой другой класс, в котором zero-initialized состояние невалидное.

Например, класс `Ratio`, который хранит пару numerator/denominator, и который должен бы гарантировать, что denominator не нуль. Как разработчик класса должен отреагировать, если метод `.ToDouble()` вызывается у экземпляра без вызванного конструктора, то есть denominator остался нулём? Ну, наверное, выбросить исключение, что-нибудь типа `NotInitializedException`. Вот и разработчики класса `ImmutableArray<T>` решили не добавлять спец-трактовку, а выбросить какое-нибудь подходящее исключение `if (array == null) throw new NotInitializedException()`. Но в принципе, для этих целей можно использовать и существующий тип исключения: `if (array == null) throw new NullReferenceException()`. Этот тип исключения — `NullReferenceException` — здесь будет консистентен как в случае `var a = default(SomeMutableArray Reference Type<T>)` так и в случае `var a = default(SomeMutableArray Value Type<T>)`. В любом случае пользователь «нулевого» экземпляра получит тот же самый тип исключения, не зависимо от того, ссылочный тип или значимый; неинициализированная ссылка или структура при обращении к методам будут вести себя одинаково. Остаётся последний шаг: зачем вообще вручную проверять на `null` и бросать `NullReferenceException`, если в коде можно эту проверку убрать вообще, и её сделает рантайм, автоматически сопроводив выбросом `NullReferenceException`?
Глаза у меня добрые, но рубашка — смирительная!
Re[10]: Не понимая и половины
От: Qbit86 Кипр
Дата: 19.03.17 21:41
Оценка: :)
Здравствуйте, vdimas, Вы писали:

V>

V>Собсно, вообще таких алгоритмов мало, которые можно выразить в дотнете для value и ref-типов в генериках и они будут корректно работать в обоих случаях. Я одно время на этом собаку съел, в поисках классов таких алгоритмов, которые, таки, работают в обоих случаях и в попытках сформулировать ограничения на такие алгоритмы.


Так расскажи скорее же, почему generic-алгоритмы, принимающие пачки функций в обобщённом параметре, будут вести себя по разному в зависимости от того, класс ли провозит эту пачку функций, или структура.

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

V>Вот, рядом пытается меня поправлять

Ты, очевидно, просто не дочитал адресованного тебе комментария и не перешёл по ссылке. Поэтому на утверждение про дефолтные конструкторы ответил так, будто оно про дефолтную инициализацию. Я просто указал на эту неточность, ничего криминального.
Глаза у меня добрые, но рубашка — смирительная!
Re[8]: "Небезопасный" код
От: AngeL B. Россия  
Дата: 20.03.17 07:09
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Здравствуйте, AngeL B., Вы писали:


AB>>Как так получилось, что во всех методах/свойствах этого типа данных ПРЕДПОЛАГАЕТСЯ, что поле array != null? И это при невозможности определить для структур конструктор по умолчанию.

Q>В общем случае для класса нет способа трактовать zero-initialized состояние как валидное. Sinix предлагает трактовать его как пустой array. Но разработчики решили не делать специальных вариантов для этого случая, а трактовать его как любой другой класс, в котором zero-initialized состояние невалидное.

В том то и дело, что это не класс, а структура. Я бы присоединился к Sinix и ожидал бы от структуры поведения структуры. Т.е. при array == null, структура должна была бы вести себя как пустой массив. Но это скорее вопрос вкуса.

Q> Остаётся последний шаг: зачем вообще вручную проверять на `null` и бросать `NullReferenceException`, если в коде можно эту проверку убрать вообще, и её сделает рантайм, автоматически сопроводив выбросом `NullReferenceException`?


это странная логика, так можно дойти до того, чтобы вообще не проверять параметры функций на корректность. В крайнем случае рантайм все равно "бахнется".
Re[9]: "Небезопасный" код
От: Qbit86 Кипр
Дата: 20.03.17 07:33
Оценка: +1
Здравствуйте, AngeL B., Вы писали:

AB>В том то и дело, что это не класс, а структура. Я бы присоединился к Sinix и ожидал бы от структуры поведения структуры. Т.е. при array == null, структура должна была бы вести себя как пустой массив. Но это скорее вопрос вкуса.


Хорошо, в рассмотренном выше примере Ratio как должна вести себя такая zero-initialized дробь в смысле «поведения структуры»? Как 0/1, или как? Разработчики ImmutableArray<T> решили «не мудрить», а просто рассматривать zero-initialized случай как нарушение контракта. Как неподдерживаемый сценарий. Вполне имели право принять такое решение, и это имеет смысл.

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


К параметрам функции применяются существующий `ArgumentException` и производные. В рассматриваемом случае подошёл бы `NotInitializatedException` — и для ссылок, и для структур единообразно. Но такого стандартного нет, и всегда использовался `NullReferenceException` для ссылок. Разработчики решили «переиспользовать» его же — внутренняя ссылка же `null`. Могли бы добавить лишних проверок и бросать какой-то специальный тип исключения. В описанных Sinix'ом проявлениях ничего бы не поменялось кроме этого типа исключения.
Глаза у меня добрые, но рубашка — смирительная!
Re[10]: Поведение структуры
От: Qbit86 Кипр
Дата: 20.03.17 07:47
Оценка:
AB>>В том то и дело, что это не класс, а структура. Я бы присоединился к Sinix и ожидал бы от структуры поведения структуры. Т.е. при array == null, структура должна была бы вести себя как пустой массив.

vdimas выше писал, что ImmutableArray<T> фактически реализует семантику ссылки, а не структуры. Он сделан структурой не чтобы реализовать семантику структуры (он её не может реализовать для ссылочного типа, который оборачивает), а для оптимизации. Его можно было бы реализовать ссылочным типом с таким же текущим поведением («автоматический» `NullReferenceException` при обращении к неинициализированной переменной), просто это было бы менее эффективно.

«Левый» объект ссылочного типа в работе надо проверять на `null` перед обращением к экземплярным методам. Для значимых типов возможность такой проверки надо организовывать отдельно; для «левых» объектов типа `ImmutableArray<T>` это делается проверкой `IsDefault`.
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: array != null
От: Qbit86 Кипр
Дата: 20.03.17 10:47
Оценка:
Здравствуйте, AngeL B., Вы писали:

AB>Как так получилось, что во всех методах/свойствах этого типа данных ПРЕДПОЛАГАЕТСЯ, что поле array != null?


Не во всех, в каких-то есть вызовы `ThrowInvalidOperationIfNotInitialized()` и `ThrowNullRefIfNotInitialized()`. Полезно почитать комментарии:
ImmutableArray_1.Minimal.cs
ImmutableArray_1.cs
        /// <summary>
        /// Gets the number of elements in the array.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public int Length
        {
            [NonVersionable]
            get
            {
                // We intentionally do not check this.array != null, and throw NullReferenceException
                // if this is called while uninitialized.
                // The reason for this is perf.
                // Length and the indexer must be absolutely trivially implemented for the JIT optimization
                // of removing array bounds checking to work.
                return this.array.Length;
            }
        }

        /// <summary>
        /// Throws a null reference exception if the array field is null.
        /// </summary>
        internal void ThrowNullRefIfNotInitialized()
        {
            // Force NullReferenceException if array is null by touching its Length.
            // This way of checking has a nice property of requiring very little code
            // and not having any conditions/branches.
            // In a faulting scenario we are relying on hardware to generate the fault.
            // And in the non-faulting scenario (most common) the check is virtually free since
            // if we are going to do anything with the array, we will need Length anyways
            // so touching it, and potentially causing a cache miss, is not going to be an
            // extra expense.
            var unused = this.array.Length;
        }

        /// <summary>
        /// Throws an <see cref="InvalidOperationException"/> if the <see cref="array"/> field is null, i.e. the
        /// <see cref="IsDefault"/> property returns true.  The
        /// <see cref="InvalidOperationException"/> message specifies that the operation cannot be performed
        /// on a default instance of <see cref="ImmutableArray{T}"/>.
        ///
        /// This is intended for explicitly implemented interface method and property implementations.
        /// </summary>
        private void ThrowInvalidOperationIfNotInitialized()
        {
            if (this.IsDefault)
            {
                throw new InvalidOperationException(SR.InvalidOperationOnDefaultArray);
            }
        }
Глаза у меня добрые, но рубашка — смирительная!
Re[7]: Дефолтная инициализация структур
От: vdimas Россия  
Дата: 20.03.17 11:24
Оценка:
Здравствуйте, AngeL B., Вы писали:

V>>Это и есть дыра. Потому что var s = default(S); или return default(S);

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

AB>Не совсем верно.

AB>Валидность или невалидность внутреннего представления определяется внутренней логикой объекта (класса, структуры), т.е. кодом его методов (в том числе конструкторов).

Так верно или не верно?
Сам себе противоречишь.


AB>Я специально зашел посмотреть на исходный код ImmutableArray<T>. И у меня простой вопрос.

AB>Как так получилось, что во всех методах/свойствах этого типа данных ПРЕДПОЛАГАЕТСЯ, что поле array != null?

Ну а как так получилось, что, например, вот тут ты предполагаешь, что str не null?
string str = GetSomeString();

foreach(char c in str) ...

Вполне себе обычный код на C#.


AB>И это при невозможности определить для структур конструктор по умолчанию.


Странные вы люди... ))


AB>Так что по моему мнению, спор в первую очередь должен идти не об архитектуре или каких-то там дополнительных конструкторах.

AB>Главный вопрос должен звучать — Каким образом такой "небезопасный" код прошел в продакшен.

Это если путать цель и средства.
ImmutableArray — это средство. Средство для достижения чего (ты спросил себя)?
А здесь каждый второй и ты в том числе как-то так странно рассуждаете, будто ImmutableArray был целью проектирования.
Re[11]: Не понимая и половины
От: vdimas Россия  
Дата: 20.03.17 16:03
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>Так расскажи скорее же, почему generic-алгоритмы, принимающие пачки функций в обобщённом параметре, будут вести себя по разному в зависимости от того, класс ли провозит эту пачку функций, или структура.


1. Методы-расширения, как и обычные методы, не включают в свою сигнатуру ограничения.
2. Из-за этого интерфейс методов-расширений обычно такой:
int sum(IEnumarable<int> collection) {...}
double sum(IEnumarable<double> collection) {...}

а не такой:
int sum<T>(T collection) where T : IEnumerable<int> {...}
double sum<T>(T collection) where T : IEnumerable<double> {...}


Q>Ты, очевидно, просто не дочитал адресованного тебе комментария и не перешёл по ссылке. Поэтому на утверждение про дефолтные конструкторы ответил так, будто оно про дефолтную инициализацию. Я просто указал на эту неточность, ничего криминального.


Оно в данном вопросе связано, потому что:
3. Из-за п.2. в теле обычных и генерик-методов может случиться неявный боксинг структуры, а программист и не обратит внимания. Но результат боксинга структуры никогда не равен null, даже если структура не инициализирована. Т.е. последующая проверка на null пройдёт успешно.

Конкретно методы-расширения Linq — это лишь пример, там сразу будет вызван GetEnumerator и сразу же всё упадёт, если структура является оберткой над ссылкой на коллекцию и эта ссылка пуста. Намного неприятнее, когда боксированная ссылка после успешной проверки на null будет запомнена, т.е. будет использована "когда-нить потом". В этом случае поиск ошибок превращается в натуральный квест.

Все эти вещи насчет инициализаций структур дефолтными конструкторами надо решать в комплексе — т.е., если уж даётся возможность описать дефолтный конструктор, то точно так же надо дать возможность запрета дефолтной инициализации для каких-то типов. По-идее, последнее не должно ломать имеющийся код, т.к. новые ср-ва можно использовать в новых типах, не трогая старые. Например, появились же когда-то новые генерик-коллекции, не ломающие старый код?
Re[12]: Не понимая и половины
От: samius Япония http://sams-tricks.blogspot.com
Дата: 20.03.17 16:18
Оценка:
Здравствуйте, vdimas, Вы писали:

V>а не такой:

V>
V>int sum<T>(T collection) where T : IEnumerable<int> {...}
V>double sum<T>(T collection) where T : IEnumerable<double> {...}
V>


V>Оно в данном вопросе связано, потому что:

V>3. Из-за п.2. в теле обычных и генерик-методов может случиться неявный боксинг структуры, а программист и не обратит внимания. Но результат боксинга структуры никогда не равен null, даже если структура не инициализирована. Т.е. последующая проверка на null пройдёт успешно.

Перечислитель будет доступен лишь через методы интерфейса IEnumerator<int>. А значит — все равно отбоксится. Методы перечислителя будут вызываться многократно, а метод GetEnumerator() лишь однажды на забег. Без возможности вызывать методы перчислителя напрямую из метода sum, эта чушь не стоит обсуждения.
Re[13]: Не понимая и половины
От: samius Япония http://sams-tricks.blogspot.com
Дата: 20.03.17 16:25
Оценка:
Здравствуйте, samius, Вы писали:

S>Без возможности вызывать методы перчислителя напрямую из метода sum, эта чушь не стоит обсуждения.


как-то так
        static int Sum<TColl, TEnumerator>(TColl coll)
            where TColl : IMyEnumerable<TEnumerator>
            where TEnumerator : IEnumerator<int>
        {
            int result = 0;
            foreach(int v in coll)
            {
                result += v;
            }

            return result;
        }

        interface IMyEnumerable<out TEnumerator>
        {
            TEnumerator GetEnumerator();
        }
Re[8]: "Небезопасный" код
От: Sinix  
Дата: 20.03.17 17:03
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q> Остаётся последний шаг: зачем вообще вручную проверять на `null` и бросать `NullReferenceException`, если в коде можно эту проверку убрать вообще, и её сделает рантайм, автоматически сопроводив выбросом `NullReferenceException`?


Вот тут есть один милый нюанс: для ref-типов есть стандартное представление для non-initialized значений — null. Его понимают и структуры, и генерик-алгоритмы (с оптимизацией проверок на null JIT-ом), и будущие not-null refs в восьмом шарпе, и [NotNull] решарпера. Ну, т.е. если кто-то не хочет ловить null ref в продакшне — он просто использует соответствующие инструменты и не парится.

Для структур ничего подобного нет и в ближайшие четыре года не предвидится (если только джетбрейновцы не сподобятся, они могут). Оппоненты по сути предлагают следующее: _любая_ структура в сторонней библиотеке может внезапно обзавестись невалидным состоянием, не предоставляя никакого стандартного способа это состояние проверить.

Не, ну сколько можно на эти грабли наступать-то??? Басика с весёлой троицей null, empty и nothing мало?
Re[11]: Минутка WTF-19: Catch me if you can
От: vdimas Россия  
Дата: 20.03.17 19:05
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Мы как-то по кругу ходим. Я выше уже дал примеров 5 реальных багов, связанных с использованием ImmutableArray<T>. Но ты продолжаешь упорно доказывать, что тип ок, это девелоперы неправильные. Если это твоя принципиальная позиция — ок, закругляемся.


Не надо наговаривать. Я говорил, что девелоперы пытались использовать правильные практики.
Только опыта еще маловато, вестимо.


S>Эмм, ты описываешь worst practices и на этой основе доказываешь, что best не нужны? * чешет в затылке.


А судьи кто? ))

Твои best practices для дотнеты были разработаны с прицелом, что архитектура строится на ref-типах, а value-типы используются сугубо как "плоские данные". Но это уровень ноль, а не "best practices". По современным меркам это равносильно отсутствию всяких практик.


S>Бинго. Только перед этим готовые рекомендации надо знать. Досконально. Чтобы не тратить время на детские ошибки и не вставать в позицию "я на эти грабли ещё не наступал => все, кто меня предупреждают == нубы". Вот как тут:


Рекомендации писали на "отгребись", сорри.
Эти рекомендации не полны, не покрывают целого пласта востребованных кейзов.

Я же тебе писал:

Гадлайнам надо не только следовать, их надо активно разрабатывать в процессе своей деятельности. Собсно, они сами должны оседать как "побочный эффект вычислений".


Ну и даже лень повторять ту прописную истину, что рекомендации на то и "рекомендации", что описывают только наиболее общий случай.
А частный случай должен разруливать инженер. Его для этого долго учили в ВУЗ-е, а потом примерно еще столько же ни за что, считай, платили ЗП первые на первых местах его работы. ))


S>Кто-то что-то мерял не так. Скорее всего, ещё и под старым x86 JIT-ом. C RyuJit не будет там разов, если дело именно в косвенных вызовах. 5..10 процентов — верю, больше — нет.


Вот прям вот так? ))
И прям ни капли сомнений?
Т.е., тебя тоже как инженера уже можно начинать хоронить?


S>В чём разница между RefWrapper и Lizt<T> — посмотри сам. Сам напросился — нефиг рассказывать про перфоманс с позиции "мне _кажется_, что причина в косвенных вызовах"


Я не говорил "мне кажется", я говорил "есть некие тесты с конкретными результатами".
В общем, нашел я тот топик:
http://rsdn.org/forum/dotnet/4295992.1
Автор: vdimas
Дата: 01.06.11


Проверил только что на дотнете 4.6.1, получил разницу в 2.3 раза и в 1.8 раз на x86 и x64 соответственно.


S>
  пруфкод

Ох, и тебя самого ничего не настрожило разве? ))
А где же сомнения?
Без сомнений ведь не бывает здравого смысла.

Кароч, я добавил к длине массивов 3 нуля в твоём тесте и погонял.
Результаты для x86 :
            Method |          Mean |    StdDev | Scaled | Scaled-StdDev |
------------------ |-------------- |---------- |------- |-------------- |
    TestRefWrapper | 2,345.5918 us | 7.4546 us |   1.00 |          0.00 |
 TestStructWrapper |   961.5590 us | 6.0217 us |   0.41 |          0.00 |
          TestList | 1,499.5782 us | 4.4925 us |   0.64 |          0.00 |
     TestListReset | 1,497.4722 us | 4.1352 us |   0.64 |          0.00 |
         TestArray |   960.5809 us | 2.4241 us |   0.41 |          0.00 |


Результаты для x64 :
            Method |          Mean |    StdDev | Scaled | Scaled-StdDev |
------------------ |-------------- |---------- |------- |-------------- |
    TestRefWrapper |   816.8624 us | 3.9044 us |   1.00 |          0.00 |
 TestStructWrapper |   774.8507 us | 1.6223 us |   0.95 |          0.00 |
          TestList |   996.5568 us | 3.0444 us |   1.22 |          0.01 |
     TestListReset | 1,005.6156 us | 4.9246 us |   1.23 |          0.01 |
         TestArray |   775.9587 us | 3.2324 us |   0.95 |          0.01 |


Лично мне тут сходу всё стало ясно, бо чудес, увы, не бывает.
Например, в дебаге разница по-прежнему заметная.
Кароч, преодолевая свою лень, поясняю происходящее:

* Для простоты дизассемблированного кода сделал все поля статическими.

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

* Смотрим на дизассемблированный код:
public long TestRefWrapper()
...
            var result = 0L;
00007FFCF26D06C0  sub         rsp,28h  
00007FFCF26D06C4  xor         eax,eax  
            var local = _ref;
00007FFCF26D06C6  mov         rdx,24E100059A0h  
00007FFCF26D06D0  mov         rdx,qword ptr [rdx]  
            for (int i = 0, e = local.Length; i < e; i++)
00007FFCF26D06D3  xor         ecx,ecx  
00007FFCF26D06D5  mov         rdx,qword ptr [rdx+8]  
00007FFCF26D06D9  mov         r8d,dword ptr [rdx+8]  
00007FFCF26D06DD  mov         r9d,r8d  
00007FFCF26D06E0  test        r9d,r9d  
00007FFCF26D06E3  jle         00007FFCF26D0702  
00007FFCF26D06E5  mov         r10,rdx  
00007FFCF26D06E8  cmp         ecx,r8d  
00007FFCF26D06EB  jae         00007FFCF26D0707  
00007FFCF26D06ED  movsxd      r11,ecx  
00007FFCF26D06F0  mov         r10d,dword ptr [r10+r11*4+10h]  
00007FFCF26D06F5  movsxd      r10,r10d  
00007FFCF26D06F8  add         rax,r10  
00007FFCF26D06FB  inc         ecx  
00007FFCF26D06FD  cmp         ecx,r9d  
00007FFCF26D0700  jl          00007FFCF26D06E5


* Обращаем внимание на кеширование в регистрах внутренних полей объектов по ссылке (чего-чего???):
mov rdx,24E100059A0h      // взяли адрес статического объекта - ссылки _ref
mov rdx,qword ptr [rdx]   // загрузили значение по ссылке - это адрес объекта RefWrapper.
mov rdx,qword ptr [rdx+8] // сразу же прочитали первое поле объекта и запомнили его в rdx



Итого, в rdx у нас лежит указатель на "обернутый" массив, а вовсе не указатель на RefWrapper, который мы наивно собирались протестировать.
Далее в r9d помещается длина этого массива и в цикле происходит сравнение с ней счетчика i, под который используется регистр ecx, т.е. не происходит такого разыменования и при вызове метода Length.

* Итого, получилась профанация, а не тест. Через глобальный анализ джит (или кто там у них) "догадался", что обращения к полю _ref вне метода TestRefWrapper не наблюдается, поэтому он смело так предположил, что ему достаточно провести разыменование ссылки лишь в самом начале метода-теста, а в цикле уже не надо.

Но этого мало, смотри на цифры замеров еще раз.
Всего одна лишняя операция разыменования ДО начала основного цикла в тесте сделала результаты TestRefWrapper всё-равно хуже.


S>Остальное поскипал, т.к. там тоже надо сначала матчасть объяснять, а мне это уже влом.


Пару раз написал и стёр...
По грани ходишь, смотрю...

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

Просто, в плюсах в тестах вообще творится черт его знает что — это порой сложная задачка, прямо-таки вызов (ы-ы-ы), так составить код, чтобы компилятор не выкинул нафиг лишнее в процессе оптимизации, где само это "лишнее" и является объектом тестирования, собсно! ))

Я с такими вещами сталкиваюсь ежедневно, поэтому результатам твоего "теста" не удивился ни разу, ес-но.

ИМХО, тут надо не "матчасть глупому сишнику объяснять" (С), а попробовать найти рекомендации для подобных практик.
Спустись с небес, кароч, а то воспарил, смотрю. ))

=====================
Итак, анализируем проблему:

  • раз неявный боксинг является источником проблем, то предлагаю НЕ реализовывать в обертках навроде ImmutableArray интерфейсы IEnumerable и Ко. Это будет защита на уровне системы типов.

  • Далее можно попытаться придумать какую-нить общую практику для таких оберток, выраженную в виде хелпера, например так:
    interface IObjectProxy {
        bool IsEmpty { get; }
    }
    
    interface IEnumerableProxy<T> : IObjectProxy
    {
       IEnumerable<T> ToEnumerable();
       IEnumerator<T> GetEnumerator();
    }
    
    interface ICollectionProxy<T> : IEnumerableProxy<T>
    {
       ICollection<T> ToCollection();
    }
    
    interface IListProxy<T> : ICollectionProxy<T>
    {
       ICollection<T> ToCollection();
    }


  • Соответственно, методы-расширения в моём пруфе можно допилить до такого:
        private static void CheckNotNull<T>(ref T proxy) where T : IObjectProxy 
        {
            if (proxy.IsEmpty)
                throw new NullReferenceException();
        }
    
        public static int Sum(this T source) where T : IEnumerableProxy<int>
        {
            CheckNotNull(source);
            return Enumerable.Sum(source.ToEnumerable(););
        }

    Итого, уходит зависимость методов-расширений от некоего конкретного типа ImmutableArray<T>.

  • Ну и помним, что методы-хелперы для синтаксиса foreach кушатьинтерфейсов не просят.
    Ву а ля или еще нет?
  • Отредактировано 20.03.2017 19:10 vdimas . Предыдущая версия .
    Re[12]: Минутка WTF-19: Catch me if you can
    От: Sinix  
    Дата: 20.03.17 19:40
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    S>>Остальное поскипал, т.к. там тоже надо сначала матчасть объяснять, а мне это уже влом.

    V>ИМХО, тут надо не "матчасть глупому сишнику объяснять" (С), а попробовать найти рекомендации для подобных практик.
    V>Спустись с небес, кароч, а то воспарил, смотрю. ))

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

    UPD перечитал — что-то криво я сформулировал зачёркнутое. Отдельным постом распишу.


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

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

    Так что предлагаю закругляться, всё равно нормальной беседы не получается
    Отредактировано 20.03.2017 19:56 Sinix . Предыдущая версия .
    Re[12]: Минутка WTF-19: Catch me if you can
    От: Sinix  
    Дата: 20.03.17 20:10
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>* Итого, получилась профанация, а не тест. Через глобальный анализ джит (или кто там у них) "догадался", что обращения к полю _ref вне метода TestRefWrapper не наблюдается, поэтому он смело так предположил, что ему достаточно провести разыменование ссылки лишь в самом начале метода-теста, а в цикле уже не надо.


    Криво выше ответил про "неправильную оценку причин тормозов", исправляюсь.

    Смысл в следующем: в RyuJit одно поломали другое починили здорово подкрутили эвристики для loop hoisting, т.е. то самое вытаскивание обращения к полю за тело цикла.
    И, как результат, для вещей, которые JIT умеет оптимизировать (обсуждаемый случай — как раз из них) смысла заморачиваться с структурой нет — получаем 5%-10% выигрыша в лучшем случае в обмен на лишний боксинг в типовом случае работы с linq. Пожалуй, стоило это дело вчера нормально написать, без "посмотри сам". Сорри.

    Не, понятно, что для mutable-коллекций hoisting приведёт к нежелательным эффектам, но мы ж явно не их обсуждаем, раз запаковываем массив в структуру

    Как-то так.
    Re[13]: Не понимая и половины
    От: vdimas Россия  
    Дата: 20.03.17 21:02
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>Перечислитель будет доступен лишь через методы интерфейса IEnumerator<int>. А значит — все равно отбоксится.


    Не отбоксится. ))
    Re[13]: Минутка WTF-19: Catch me if you can
    От: vdimas Россия  
    Дата: 20.03.17 21:05
    Оценка:
    Здравствуйте, Sinix, Вы писали:

    S>И при этом чужой опыт хождения по граблям ты отметаешь не глядя


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


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


    Не надо ничего расписывать словами.
    Я тебе показал в листинге, что твой тест не тестирует того, что ты планировал тестировать.
    Остальное — лирика.
    Re[13]: Минутка WTF-19: Catch me if you can
    От: vdimas Россия  
    Дата: 20.03.17 21:26
    Оценка: 26 (1)
    Здравствуйте, Sinix, Вы писали:

    S>Смысл в следующем: в RyuJit одно поломали другое починили здорово подкрутили эвристики для loop hoisting, т.е. то самое вытаскивание обращения к полю за тело цикла.

    S>И, как результат, для вещей, которые JIT умеет оптимизировать (обсуждаемый случай — как раз из них)

    В общем случае такие оптимизации невозможны.
    Они как раз, сцуко, только в синтетических тестах и происходят.
    Это по опыту ежедневной работы с куда как более мощным офлайн-оптимизатором С++, который еще больше кода выкидывает нафик.

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

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


    S>смысла заморачиваться с структурой нет — получаем 5%-10% выигрыша


    Выигрыш там в 3-4 раза, это на реальных дотнетных проектах (VoIP, HFT).
    В синтетических тестах выигрыш всегда чуть меньше выходит — примерно в 2.5 раза.
    Потому что синтетический тест измеряет профит лишь от одного уровня вложенности.
    Если таких уровней вложенности более одного, то профит умножается для каждого уровня.
    В среднем проекте обычно присутствует некий высокоуровневый-объект-обертка над неким List<>, тот в свою очередь тоже является оберткой над Array. Т.е. уровень косвенности минимум 2, теоретический прирост должен быть в ~6.25 раз, но фактически помимо беготни по графу происходят еще вычисления, поэтому реальный эффект на боевых проектах где-то 3-4 раза.
    В нейтиве такие же цифры получаются, что характерно.
    Т.е. дотнет тут не на уникальной позиции.


    S>в лучшем случае в обмен на лишний боксинг в типовом случае работы с linq.


    Лишний боксинг тоже зло, ес-но.
    Именно поэтому я предлагал рисовать свои "точки входа" для классов-расширений типа Enumerable.
    Что там будет происходить с value-типами-коллекциями в Linq-выражениях в этом его expression-синтаксисе, если честно, не в курсе, еще не экспериментировал. Что-то мне подсказывает, что там, где производительность важна, там этот синтаксис не используют.
    Re[14]: Не понимая и половины
    От: vdimas Россия  
    Дата: 20.03.17 22:54
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>>Без возможности вызывать методы перчислителя напрямую из метода sum, эта чушь не стоит обсуждения.


    S>как-то так

    S>
    S>        static int Sum<TColl, TEnumerator>(TColl coll)
    S>            where TColl : IMyEnumerable<TEnumerator>
    S>            where TEnumerator : IEnumerator<int>
    S>


    А рядом для static double Sum<...> where TEnumerator : IEnumerator<double>? ))
    Re[14]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 21.03.17 03:56
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Перечислитель будет доступен лишь через методы интерфейса IEnumerator<int>. А значит — все равно отбоксится.


    V>Не отбоксится. ))


    Ты прав, не отбоксится. Метод, возвращающий struct Enumerator из твоего Sum даже не будет вызван. Так чего ради нужна такая "оптимизация"?
    Re[15]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 21.03.17 04:00
    Оценка: +1
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    V>А рядом для static double Sum<...> where TEnumerator : IEnumerator<double>? ))

    Если мне понадобится такая фигня ради того что бы ехать, то я не побрезгую назвать методы SumInt и SumDouble.
    Re[9]: "Небезопасный" код
    От: Privalov  
    Дата: 21.03.17 06:55
    Оценка: +1
    Здравствуйте, Sinix, Вы писали:

    S>Не, ну сколько можно на эти грабли наступать-то??? Басика с весёлой троицей null, empty и nothing мало?


    В Васике существовало мощнейшее средство: расставить в программе on error resume next и уволиться. Правда, Васик от помоечки оно не спасло.
    Re[16]: Не понимая и половины
    От: vdimas Россия  
    Дата: 21.03.17 13:02
    Оценка:
    Здравствуйте, samius, Вы писали:

    V>>А рядом для static double Sum<...> where TEnumerator : IEnumerator<double>? ))

    S>Если мне понадобится такая фигня ради того что бы ехать, то я не побрезгую назвать методы SumInt и SumDouble.

    +100500
    Re[15]: Не понимая и половины
    От: vdimas Россия  
    Дата: 21.03.17 13:05
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>>>Перечислитель будет доступен лишь через методы интерфейса IEnumerator<int>. А значит — все равно отбоксится.

    V>>Не отбоксится. ))
    S>Ты прав, не отбоксится. Метод, возвращающий struct Enumerator из твоего Sum даже не будет вызван. Так чего ради нужна такая "оптимизация"?

    Ради уменьшения уровня косвенности:
                Method |          Mean |    StdDev | Scaled | Scaled-StdDev |
    ------------------ |-------------- |---------- |------- |-------------- |
        TestRefWrapper | 2,345.5918 us | 7.4546 us |   1.00 |          0.00 |
     TestStructWrapper |   961.5590 us | 6.0217 us |   0.41 |          0.00 |

    Разница ~2.5 раза.
    Re[16]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 21.03.17 13:52
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>>>Перечислитель будет доступен лишь через методы интерфейса IEnumerator<int>. А значит — все равно отбоксится.

    V>>>Не отбоксится. ))
    S>>Ты прав, не отбоксится. Метод, возвращающий struct Enumerator из твоего Sum даже не будет вызван. Так чего ради нужна такая "оптимизация"?

    V>Ради уменьшения уровня косвенности:

    V>
    V>            Method |          Mean |    StdDev | Scaled | Scaled-StdDev |
    V>------------------ |-------------- |---------- |------- |-------------- |
    V>    TestRefWrapper | 2,345.5918 us | 7.4546 us |   1.00 |          0.00 |
    V> TestStructWrapper |   961.5590 us | 6.0217 us |   0.41 |          0.00 |
    V>

    V>Разница ~2.5 раза.

    При этом у тебя для 64

    Результаты для x64 :
    Method | Mean | StdDev | Scaled | Scaled-StdDev |
    ------------------ |-------------- |---------- |------- |-------------- |
    TestRefWrapper | 816.8624 us | 3.9044 us | 1.00 | 0.00 |
    TestStructWrapper | 774.8507 us | 1.6223 us | 0.95 | 0.00 |


    Никаких ~2.5 раза.

    Попробуй .Net Core
    и солнце б утром не вставало, когда бы не было меня
    Re[17]: Не понимая и половины
    От: vdimas Россия  
    Дата: 21.03.17 14:15
    Оценка:
    Здравствуйте, Serginio1, Вы писали:

    S>При этом у тебя для 64

    S>Результаты для x64 :
    S> Method | Mean | StdDev | Scaled | Scaled-StdDev |
    S>------------------ |-------------- |---------- |------- |-------------- |
    S> TestRefWrapper | 816.8624 us | 3.9044 us | 1.00 | 0.00 |
    S> TestStructWrapper | 774.8507 us | 1.6223 us | 0.95 | 0.00 |

    Садись, два.
    Для x64 исходный тест не работоспособен.


    S>Никаких ~2.5 раза.


    Такие же точно.


    S>Попробуй .Net Core


    Попробуй сам.
    Re[18]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 21.03.17 14:26
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, Serginio1, Вы писали:


    S>>При этом у тебя для 64

    S>>Результаты для x64 :
    S>> Method | Mean | StdDev | Scaled | Scaled-StdDev |
    S>>------------------ |-------------- |---------- |------- |-------------- |
    S>> TestRefWrapper | 816.8624 us | 3.9044 us | 1.00 | 0.00 |
    S>> TestStructWrapper | 774.8507 us | 1.6223 us | 0.95 | 0.00 |

    V>Садись, два.

    V>Для x64 исходный тест не работоспособен.

    Так это твои результаты http://rsdn.org/forum/dotnet/6731352.1
    Автор: vdimas
    Дата: 20.03.17

    Ты самокритичен оказывается

    S>>Никаких ~2.5 раза.


    V>Такие же точно.


    По таблице чего то не видно.
    S>>Попробуй .Net Core

    V>Попробуй сам.

    То есть тебе в лом самому разобраться? ОК
    и солнце б утром не вставало, когда бы не было меня
    Re[19]: Не понимая и половины
    От: vdimas Россия  
    Дата: 21.03.17 15:50
    Оценка:
    Здравствуйте, Serginio1, Вы писали:

    V>>Садись, два.

    V>>Для x64 исходный тест не работоспособен.
    S>Так это твои результаты http://rsdn.org/forum/dotnet/6731352.1
    Автор: vdimas
    Дата: 20.03.17


    Но ты дальше таблицы с результатами не читал, надо понимать?


    S>Ты самокритичен оказывается


    Сначала дочитай пост по ссылке до конца, потом возвращайся.


    S>По таблице чего то не видно.


    По таблице невалидные результаты.
    Потому что сам тест невалиден.


    V>>Попробуй сам.

    S>То есть тебе в лом самому разобраться? ОК

    Я как раз дал ассембленый листинг с подробнейшим разбором происходящего.
    А ты опять хамишь игнором аргументов оппонента.
    Re[16]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 21.03.17 16:26
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Ты прав, не отбоксится. Метод, возвращающий struct Enumerator из твоего Sum даже не будет вызван. Так чего ради нужна такая "оптимизация"?


    V>Ради уменьшения уровня косвенности:

    V>
    V>            Method |          Mean |    StdDev | Scaled | Scaled-StdDev |
    V>------------------ |-------------- |---------- |------- |-------------- |
    V>    TestRefWrapper | 2,345.5918 us | 7.4546 us |   1.00 |          0.00 |
    V> TestStructWrapper |   961.5590 us | 6.0217 us |   0.41 |          0.00 |
    V>

    V>Разница ~2.5 раза.

    За счет чего разница будет в 2.5 раза, если ты уменьшил уровень косвенности лишь у одного вызова на забег по коллекции, в то время как 2*n вызовов осталось ровно с той же косвенностью, как и при передаче интерфейса?
    Re[20]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 21.03.17 16:30
    Оценка: :)
    Здравствуйте, vdimas, Вы писали:


    V>Я как раз дал ассембленый листинг с подробнейшим разбором происходящего.

    V>А ты опять хамишь игнором аргументов оппонента.

    Это я хамлю???
    Все таки иногда нужно смотреть в зеркало.
    Зачем приводишь невалидные данные?
    и солнце б утром не вставало, когда бы не было меня
    Re[14]: Минутка WTF-19: Catch me if you can
    От: Sinix  
    Дата: 21.03.17 16:35
    Оценка: +2
    Здравствуйте, vdimas, Вы писали:

    V>В общем случае такие оптимизации невозможны.

    V>В среднем проекте обычно присутствует некий высокоуровневый-объект-обертка над неким List<>, тот в свою очередь тоже является оберткой над Array. Т.е. уровень косвенности минимум 2, теоретический прирост должен быть в ~6.25 раз, но фактически помимо беготни по графу происходят еще вычисления, поэтому реальный эффект на боевых проектах где-то 3-4 раза.

    Угу, кажись, я наконец понял про что ты говоришь.
    Речь про попытку совместить обработку кучи однородных данных в hotpath и сложные структуры данных над plain-массивом, так?
    Как-то совсем частный сценарий получается. Если честно, я бы такое вытаскивал на плюсы сразу. По крайней мере пока нормальный оптимизатор не завезут.
    В дотнете как правило в hotpath используется массив напрямую. Именно чтоб не напарываться на "а сегодня JIT не смог".

    Но это справедливо именно для тшательно вылызанного perf-critical кода. Для всего остального никто с тонной вложенных структур заморачиваться не будет, возвращаемся к результатам выше
    Re[17]: Не понимая и половины
    От: vdimas Россия  
    Дата: 21.03.17 21:33
    Оценка:
    Здравствуйте, samius, Вы писали:

    V>>Ради уменьшения уровня косвенности:

    V>>
    V>>            Method |          Mean |    StdDev | Scaled | Scaled-StdDev |
    V>>------------------ |-------------- |---------- |------- |-------------- |
    V>>    TestRefWrapper | 2,345.5918 us | 7.4546 us |   1.00 |          0.00 |
    V>> TestStructWrapper |   961.5590 us | 6.0217 us |   0.41 |          0.00 |
    V>>

    V>>Разница ~2.5 раза.

    S>За счет чего разница будет в 2.5 раза, если ты уменьшил уровень косвенности лишь у одного вызова на забег по коллекции, в то время как 2*n вызовов осталось ровно с той же косвенностью, как и при передаче интерфейса?


    В этой таблице результаты джита x86. Очевидно, что этот джит не провел для тестового цикла оптимизацию, т.е. не выкинул из его тела обращение к полям объекта "честным образом", т.е. через переменную, указанную в исходнике теста.
    Re[15]: Минутка WTF-19: Catch me if you can
    От: vdimas Россия  
    Дата: 21.03.17 21:47
    Оценка: 52 (1)
    Здравствуйте, Sinix, Вы писали:

    S>Угу, кажись, я наконец понял про что ты говоришь.

    S>Речь про попытку совместить обработку кучи однородных данных в hotpath и сложные структуры данных над plain-массивом, так?

    Почти так. Дело не только в массиве.
    Если объекты агрегируют друг друга, то в плюсах это обычно происходит естественным образом — по значению.
    Отсюда "бесплатность" абстракций.

    По указателю/ссылке обычно описывается отношение ассоциации, когда некий объект расшарен м/у несколькими.
    Ну или когда некий объект предназначен для передачи в другой поток.


    S>Как-то совсем частный сценарий получается.


    Это общий сценарий. Я как-то специально подсчитывал, у меня на сотни прикладных классов было только 5 объектов верхнего уровня, создаваемых явно по new и всего два объекта для передачи их м/у потоками.

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


    S>В дотнете как правило в hotpath используется массив напрямую. Именно чтоб не напарываться на "а сегодня JIT не смог".


    Угу, в Джава тоже.
    Причем, в дотнете хоть сопряжение с нейтивом, считай, бесплатное, поэтому такой код, действительно, часто пишут как внешний или линкуемый модуль на плюсах. А в Джаве осилить JNI — тот еще трах и не всегда даёт профит, особенно при частых вызовах. Поэтому берут массив байт и ручным образом в нем рисуют сложные структуры (для HFT, например), т.е. ручками выполняют работу некоего нейтивного компилятора. ))


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


    Ну да. Считанные единицы объектов верхнего уровня могут быть не заморочены. ))
    Re[12]: Junk in — junk out
    От: Qbit86 Кипр
    Дата: 21.03.17 22:50
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>1. Методы-расширения, как и обычные методы, не включают в свою сигнатуру ограничения.


    Ок, но это не относится к тезису «вообще таких алгоритмов мало, которые можно выразить в дотнете для value и ref-типов в генериках и они будут корректно работать в обоих случаях.»

    Давай рассмотрим конкретную (в меру извращённую) реализацию упомянутого суммирования, с дженериком и констрейнтами:
    internal static class Foo<TElement>
    {
        internal static TElement Sum<TEnumerable, TMonoid>(TEnumerable items, TMonoid monoid)
            where TEnumerable : IEnumerable<TElement>
            where TMonoid : IMonoid<TElement>
        {
            // При сравнении объекта value-типа с литералом `null`
            // не будет боксинга в рантайме согласно C# Specification, section 7.10.6.
    
            if (items == null)
                throw new ArgumentNullException(nameof(items));
    
            if (monoid == null)
                throw new ArgumentNullException(nameof(monoid));
    
            TElement result = monoid.Identity;
            foreach (var item in items)
                result = monoid.Combine(result, item);
    
            return result;
        }
    }


    Почему этот алгоритм будет работать для reference-типов и не будет для value-типов, или наоборот?

    V>3. Из-за п.2. в теле обычных и генерик-методов может случиться неявный боксинг структуры, а программист и не обратит внимания. Но результат боксинга структуры никогда не равен null, даже если структура не инициализирована.


    Если алгоритму передают некорректный экземпляр value-типа, то алгоритм поведёт себя так же, как если бы ему подали по не-null ссылке некорректный экземпляр reference-типа.

    Алгоритму могут передать объект значимого типа, который бросит `System.InvalidOperationException` в методе `GetEnumerator()`. Например, по причине неинициализированности экземпляра `ImmutableArray<decimal>`, или по какой-то другой причине для экземпляра какой-то другой структуры. Но точно так же алгоритму могут передать не-null ссылку на объект ссылочного типа, который тоже бросит `System.InvalidOperationException` в методе `GetEnumerator()`. Junk in — junk out.

    V>Все эти вещи насчет инициализаций структур дефолтными конструкторами надо решать в комплексе


    Напомню, что изначально вопрос обобщённых алгоритмов поднимался вне контекста дефолтной инициализации. Просто как утверждение «собаку съел, в поисках классов таких алгоритмов, которые, таки, работают в обоих случаях и в попытках сформулировать ограничения на такие алгоритмы».

    Я же утверждаю, что вполне можно придумывать такие обобщённые алгоритмы с констрейнтами, которым безразлично, передают ли им объекты структур или классов. Вот как суммирование в примере выше.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[13]: Junk in — junk out
    От: vdimas Россия  
    Дата: 22.03.17 00:00
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    Q>Почему этот алгоритм будет работать для reference-типов и не будет для value-типов, или наоборот?


    Ты опять мне пытаешься доказать, что такие алгоритмы, корректно работающие для вэлью и реф типов, всё-таки, есть?
    Т.е., пытаешься мне навязать некую удобную для оспаривания позицию?
    Не взлетит... ))


    Q>Если алгоритму передают некорректный экземпляр value-типа, то алгоритм поведёт себя так же, как если бы ему подали по не-null ссылке некорректный экземпляр reference-типа.


    Не туда ты опять смотришь.
    Я могу в прикладном виде описать гарантии, которые не дадут мне привести ref-объект в невалидное состояние.
    Для value-типов ср-в обеспечения таких гарантий нет.
    Однакое, есть сценарии, когда такие гарантии "почти есть".
    Например — value-тип являться приватным полем ref-типа и инициализируется в конструкторе.
    Помимо этого у обрабатывающих ф-ий стоит модификатор ref в аргументах, что тоже уменьшает вероятность "просто случайно подать".
    Помимо этого такие структуры у меня НЕ реализуют известные библиотекам фреймворка интерфейсы, чтобы, опять же, защититься на уровне системы типов от "случайно не туда подали через неявный боксинг".
    И т.д. и т.п.
    Т.е. я насобирал много правил, помогающих уменьшать вероятность ошибок при проектировании в value-типах, но не смог найти 100% работающей защиты от неправильного использования, т.е. не смог поручить контроль за правильностью использования компилятору. Т.е. только ручками подобную корректность обеспечиваем в любом случае.


    Q>Алгоритму могут передать объект значимого типа, который бросит `System.InvalidOperationException` в методе `GetEnumerator()`.


    Исключения "not implemented" или "invalid operation" — это первые признаки плохой архитектуры.

    Простой пример. Вот есть класс TcpClient.

    У него 80% методов выбрасывают "invalid operation".
    Почему? Потому что для этого типа оставили пустой конструктор и еще несколько "глупых" конструкторов.
    В итоге, объект может находиться в невалидном (неинициализированном) состоянии.
    Если убрать пустой конструктор, то у нас пропадает надобность в св-ве Active, а так же уходят все места в его внутреннем коде, которые проверяют это св-во.

    В классе TcpClient необходимо было оставить только конструктор от аргументов host+port, а так же добавить конструктор от Socket.
    В случае конструирования от Socket можно проверить инвариант сразу, проверив RemoteEndPoint или Connected, т.е. запретить конструировать объект TcpClient в невалидном состоянии.

    На самом деле класс TcpClient вообще является убожеством мысли как таковым. Он не даёт над классом Socket никакой другой дополнительной функциональности кроме как одного метода GetStream(). Что мешало добавить GetStream() в сокет — загадка великая есть.


    Q>Но точно так же алгоритму могут передать не-null ссылку на объект ссылочного типа, который тоже бросит `System.InvalidOperationException` в методе `GetEnumerator()`.


    Могут. Но это будут проблемы того ССЗБ, которому нравится InvalidOperationException.
    Мне же нужна возможность конструировать всегда валидные объекты.


    V>>Все эти вещи насчет инициализаций структур дефолтными конструкторами надо решать в комплексе

    Q>Напомню, что изначально вопрос обобщённых алгоритмов поднимался вне контекста дефолтной инициализации.

    Не отмажешься, не пытайся. ))
    Структуры принципиально отличаются от ref-типов буквально двумя побочными эффектами:
    — дефолтной инициализацией;
    — передачей копий значений, в т.ч. через неявный боксинг.
    В последнем случае происходят свои приколы — например, в некоем алгоритме будет изменяться содержимое копии, а не целевого экземпляра структуры.

    Т.е. контекст был один — дотнет.

    Тут ведь как? — ну не понял и не понял, хосподя.
    Было бы тебе интересно — переспросил бы.


    Q>Просто как утверждение «собаку съел, в поисках классов таких алгоритмов, которые, таки, работают в обоих случаях и в попытках сформулировать ограничения на такие алгоритмы».


    Ну? Что не так-то?
    Ты ведь не единственный тут такой, которые вместо того, чтобы уточнить подробности (если вообще охота дальше продолжать обсуждение) всегда первым делом переходят в несознанку/отрицалово и требуется десятки постов, чтобы разгрести все эти нагромождения "нет, нет и еще раз нет!".

    А чего "нет", куда "нет" и почему вообще "нет"-то?... а ХЗ. Так надо! ))
    Законы жанра?


    Q>Я же утверждаю, что вполне можно придумывать такие обобщённые алгоритмы с констрейнтами, которым безразлично, передают ли им объекты структур или классов. Вот как суммирование в примере выше.


    Ну так и я тебе ровно этот случай указал — это когда целевая структура не передаётся сама, рядом с ней надо передавать "словарь операций".

    Причем, тут мало того, что таким образом можно хоть как-то задействовать арифметику.
    Тут еще чисто вероятностный момент работает — когда надо передать два связанных строгим типовым отношением объекта куда-то, то на порядки сложнее допустить ошибку случайно, в этом месте просыпается внимательность программиста, ЧТД. Потому что есть такая фишка — основная доля ошибок делается в простых местах, в сложных местах ошибок обычно меньше.

    Но все-равно, в отуствии ср-в обеспечения гарантий валидности объектов вероятность споткнуться об это остаётся ненулевой, даже для случая оперрования парой типов.

    Собсно, моё разочарование в дотнете больше было не от его тормознуточти, а от непоследовательной системы типов, которая больно бъёт по рукам аккурат тогда, когда я пытаюсь с этой тормознутостью бороться, ы-ы-ы! )))
    Нифига себе!
    "Ах вот вы как? Ну ОК, не больно-то и хотелось..."
    Отредактировано 22.03.2017 0:00 vdimas . Предыдущая версия .
    Re[18]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 22.03.17 03:45
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>За счет чего разница будет в 2.5 раза, если ты уменьшил уровень косвенности лишь у одного вызова на забег по коллекции, в то время как 2*n вызовов осталось ровно с той же косвенностью, как и при передаче интерфейса?


    V>В этой таблице результаты джита x86. Очевидно, что этот джит не провел для тестового цикла оптимизацию, т.е. не выкинул из его тела обращение к полям объекта "честным образом", т.е. через переменную, указанную в исходнике теста.


    Причем тут вообще эта таблица и джит? Я пытаюсь выяснить у тебя, за счет чего ты ожидаешь уменьшение косвенности в методах типа
    int Sum<T>(T coll) where T : IEnumerable<int>

    Никакая джит оптимизация цикла в этом конкретном Sum не может отменить тот факт, что цикл будет дергать MoveNext/Current через интерфейс. Так чем же он лучше, чем обычный
    int Sum(IEnumerable<int>)


    ?
    Re[19]: Не понимая и половины
    От: vdimas Россия  
    Дата: 22.03.17 08:26
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>Никакая джит оптимизация цикла в этом конкретном Sum не может отменить тот факт, что цикл будет дергать MoveNext/Current через интерфейс.


    Для вэлью типов не факт, если в теле генерик метода обычный foreach.
    Re[20]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 22.03.17 09:16
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Никакая джит оптимизация цикла в этом конкретном Sum не может отменить тот факт, что цикл будет дергать MoveNext/Current через интерфейс.


    V>Для вэлью типов не факт, если в теле генерик метода обычный foreach.


    Так продемонстрируй сей "не факт", раз по-твоему джит способен подменять типы локальных переменных и вызываемые методы. Это очень серьезный "не факт". Я полагаю что ему место в багтрэкере.
    Re[21]: Не понимая и половины
    От: vdimas Россия  
    Дата: 22.03.17 10:49
    Оценка:
    Здравствуйте, samius, Вы писали:

    V>>Для вэлью типов не факт, если в теле генерик метода обычный foreach.

    S>Так продемонстрируй сей "не факт", раз по-твоему джит способен подменять типы локальных переменных и вызываемые методы. Это очень серьезный "не факт". Я полагаю что ему место в багтрэкере.

    А почему только я что-то демонстрирую?
    Почему бы тебе не продемонстрировать, что там порождает джит?
    Re[16]: Минутка WTF-19: Catch me if you can
    От: Sinix  
    Дата: 22.03.17 14:45
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Почти так. Дело не только в массиве.

    V>Если объекты агрегируют друг друга, то в плюсах это обычно происходит естественным образом — по значению.
    V>Отсюда "бесплатность" абстракций.
    +1 тогда.
    Увы, к сегодняшнему дотнету это явно не относится. Не, _принципиальных_ проблем исправить очень многие вещи нет, но существенных инвестиций в перфоманс от команды .core пока не предвидится — им и без того работы хватает. Ну а "взрослый" фреймворк на сегодня полузаброшен, все ушли на фронт .net core.

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



    V>Ну да. Считанные единицы объектов верхнего уровня могут быть не заморочены. ))


    Скорее, наоборот. По крайней мере, для энтерпрайза. Когда бизнес-логика занимает 5-10% общего времени, а всё остальное io|запросы|orm, никто инвестировать в поголовную оптимизацию всего кода не будет. Наоборот, отдельные овероптимизации, бывает, переписываются на более простой и поддерживаемый код. -15k строк в обмен на +5-10 лишних мс к запросу? Как по мне — чистый win.
    Re[22]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 22.03.17 15:25
    Оценка: -1
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    V>>>Для вэлью типов не факт, если в теле генерик метода обычный foreach.

    S>>Так продемонстрируй сей "не факт", раз по-твоему джит способен подменять типы локальных переменных и вызываемые методы. Это очень серьезный "не факт". Я полагаю что ему место в багтрэкере.

    V>А почему только я что-то демонстрирую?

    V>Почему бы тебе не продемонстрировать, что там порождает джит?

    Дык я ж могу продемонстрировать, но только обратное. Т.е. то, что цикл foreach в теле генерик метода, параметризованного типом T с констрейнтом IEnumerablt<int> производит вызов именно метода, реализующего IEnumerable<int>.GetEnumerator() и возвращающего ссылочный IEnumerator<int>. Метод, возвращающий value-enumerator при этом не вызывается. Тут даже не надо смотреть, что именно порождает джит, т.к. все это видно по побочным эффектам.
    Но ведь именно ты заявляешь, что это не факт? И при этом не приводишь аргументы, на чем основан твой "не факт". Ни примера, ни документации. Т.е. никаких оснований полагать что твоя оптимизация на что-либо способна, кроме того что бы в пределе n->(U+221E) избавиться от 1-го боксинга. Если с этим согласен, то не надо ничего демонстрировать. И так все понятно.
    Re[20]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 22.03.17 15:46
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Никакая джит оптимизация цикла в этом конкретном Sum не может отменить тот факт, что цикл будет дергать MoveNext/Current через интерфейс.


    V>Для вэлью типов не факт, если в теле генерик метода обычный foreach.

    Там заранее известен тип к чему он обращается. Для массивов никаких MoveNext/Current.
    Для Linq операторов может оптимизирован до roslyn-linq-rewrite


    Но вот тебе и C++ не с оптимизирует если на вход подается пользовательская функция с IEnumerable вместо массива.
    Просто итераторы в С++ по другому устроены.
    Кстати а вот кстати с появлением ref return

    public static ref int Find3(int[,] matrix, Func<int, bool> predicate)
    {
        for (int i = 0; i < matrix.GetLength(0); i++)
            for (int j = 0; j < matrix.GetLength(1); j++)
                if (predicate(matrix[i, j]))
                    return ref matrix[i, j];
        throw new InvalidOperationException("Not found");
    }

    Now that the method returns a reference to the integer value in the matrix, you need to modify where it's called. The var declaration means that valItem is now an int rather than a tuple:

    var valItem = MatrixSearch.Find3(matrix, (val) => val == 42);
    Console.WriteLine(valItem);
    valItem = 24;
    Console.WriteLine(matrix[4, 2]);


    Можно и на аналог С++ итераторов замахнуться. Только это особо не нужно.
    и солнце б утром не вставало, когда бы не было меня
    Re[19]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 24.03.17 08:44
    Оценка:
    Здравствуйте, samius, Вы писали:


    S>Причем тут вообще эта таблица и джит? Я пытаюсь выяснить у тебя, за счет чего ты ожидаешь уменьшение косвенности в методах типа

    S>
    S>int Sum<T>(T coll) where T : IEnumerable<int>
    S>

    S>Никакая джит оптимизация цикла в этом конкретном Sum не может отменить тот факт, что цикл будет дергать MoveNext/Current через интерфейс. Так чем же он лучше, чем обычный
    S>
    S>int Sum(IEnumerable<int>)
    S>


    Кстати интересная статья
    C# value type boxing under the hood

    interface IAdd
    {
        void Add(int val);
    }
    
    struct Foo : IAdd
    {
        public int value;
    
        void IAdd.Add(int val)
        {
            value += val;
        }
    
        public void AddValue(int val)
        {
            value += val;
        }
    
        public void Print(string msg)
        {
            Console.WriteLine(msg + ":" + value);
        }
    }




    Adding constraints
    Now the interesting case that I’d like to talk about earlier in the article (thanks for staying with me so far!). Let’s create a generic method with a generic constraint where the T is an IAdd interface:

     static void Add_WithConstraints<T>(ref T foo, int val) where T : IAdd
        {
            foo.Add(val);
        }
    
        Add_WithConstraints<Foo>(ref f, 10);
        f.Print("After Add_WithConstraints");

    Perhaps it isn’t entirely obvious to everyone — foo.Add(val) is an interface call using callvirt instruction: callvirt instance void IAdd::Add(int32), because that’s the only way compiler knows how to make the call.
    The interesting part is, when we call Add_WithConstraints, the call happens exactly in the same manner, except the code we are calling into looks drastically different:
    0:000> !u 00007ff8`cfa707d0
    Normal JIT generated code

    Program.Add_WithConstraints[[Foo, value]](Foo ByRef, Int32)
    Begin 00007ff8cfa707d0, size 3a
    >>> push    rbp
    sub     rsp,20h
    lea     rbp,[rsp+20h]
    mov     qword ptr [rbp+10h],rcx           ; this pointer
    mov     dword ptr [rbp+18h],edx           ; val
    mov rax,7FF8CF964560h                     ; debugger gibberish 
                                              ; but you probably guessed it's for Just My Code
    cmp     dword ptr [rax],0
    je      00007ff8`cfa707f5
    call    clr!JIT_DbgIsJustMyCode (00007ff9`2f534eb0)
    nop
    mov     rcx,qword ptr [rbp+10h]                  ; this pointer
    mov     edx,dword ptr [rbp+18h]                  ; val
    call    00007ff8`cfa706c0 (Foo.IAdd.Add(Int32)   ; calls the method without boxing!
    nop
    nop
    lea     rsp,[rbp]
    pop     rbp
    ret

    As you can see, the code is surprisingly simple. No boxing, no interface cast, and a direct call to Foo.IAdd.Add method. No value is lost. And you can observe the side effect:
    After Add_WithConstraints:30
    The reason is compiler now has enough information to figure out the code is for Foo and the interface call will land exactly on Foo.IAdd.Add, so it skips the formality and calls the function directly. This is both a performance optimization but also comes with observable side-effect.
    Conclusion
    When you are working with interface on value types, be aware of the potential performance cost of boxing and correctness problem of not observing changes in the callee. If you’d like to avoid that, you can use generic constraints to constraint the interface call so that compiler can optimize out the boxing and interface call altogether and go straight to the right function.
    You can find the full code in this gist.

    и солнце б утром не вставало, когда бы не было меня
    Re[14]: Junk in — junk out
    От: Qbit86 Кипр
    Дата: 24.03.17 09:38
    Оценка:
    > Ты опять мне пытаешься доказать, что такие алгоритмы, корректно работающие для вэлью и реф типов, всё-таки, есть?

    Ты же писал про «поиски классов таких алгоритмов, которые, таки, работают в обоих случаях»? Вот я тебе такой класс предоставляю.

    > Я могу в прикладном виде описать гарантии, которые не дадут мне привести ref-объект в невалидное состояние.

    > Для value-типов ср-в обеспечения таких гарантий нет.

    Это всё находится снаружи обобщённой библиотеки; на её код не влияет, сможешь ли ты вне библиотеки выразить те или иные гарантии. Если бы язык позволял выразить требование non-zero-initialized переданных аргументов, функция `Sum` всё равно не ограничивала бы свои аргументы этим требованием, потому что в общем случае передача zero-initialized-объекта того же `TMonoid` — just fine. Конечно, автор может реализовать констрейнты конкретными структурами так, что zero-initialized окажется невалидным. Но библиотека не должна отвергать все zero-initialized экземпляры на этом основании.

    > Собсно, моё разочарование в дотнете больше было не от его тормознуточти, а от непоследовательной системы типов


    Поэтому ты вернулся в «лоно C++», где получить невалидный объект не то что с нулями, а просто с рандомным мусором — проще простого? Независимо от того, что там у него за конструкторы пытаются обеспечить какие-то там гарантии.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[15]: Junk in — junk out
    От: vdimas Россия  
    Дата: 24.03.17 11:08
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    >> Ты опять мне пытаешься доказать, что такие алгоритмы, корректно работающие для вэлью и реф типов, всё-таки, есть?

    Q>Ты же писал про «поиски классов таких алгоритмов, которые, таки, работают в обоих случаях»? Вот я тебе такой класс предоставляю.

    Это надо так понимать, что упорно продолжаешь делать попытка переформулировать моё исходное утверждение?
    Хорошо себя чувствуешь?


    Q>Это всё находится снаружи обобщённой библиотеки; на её код не влияет, сможешь ли ты вне библиотеки выразить те или иные гарантии.


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


    Q>Поэтому ты вернулся в «лоно C++», где получить невалидный объект не то что с нулями, а просто с рандомным мусором — проще простого?


    1. чем нули отличаются от рандомного мусора?
    2. получить невалидный объект в С++ не просто — для этого надо "кое-что" сделать, например, специально реинтерпретировать память.

    Т.е., опять банальная банальность в том, что речь идёт о неявных вещах. Проблему в C# я получаю неявно, а в С++ — только явно.
    Прочувствуйте разницу, как грится.


    Q>Независимо от того, что там у него за конструкторы пытаются обеспечить какие-то там гарантии.


    Это ты лишь показываешь невладение языком.
    Re[16]: Владелец языка
    От: Qbit86 Кипр
    Дата: 24.03.17 13:19
    Оценка: -1
    Здравствуйте, vdimas, Вы писали:

    V>Это надо так понимать, что упорно продолжаешь делать попытка переформулировать моё исходное утверждение? :)))


    Это называется не переформулировать, а процитировать. Это ведь ты его вытащил в эту ветку, никто тебя за язык не тянул.

    V>Хорошо себя чувствуешь?


    Сонливо.

    Q>>Поэтому ты вернулся в «лоно C++», где получить невалидный объект не то что с нулями, а просто с рандомным мусором — проще простого?


    V>1. чем нули отличаются от рандомного мусора?


    Нули предсказуемы и ожидаемы. Это ни undefined behavior, ни unspecified behavior.

    V>2. получить невалидный объект в С++ не просто — для этого надо "кое-что" сделать, например, специально реинтерпретировать память.


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

    V>Это ты лишь показываешь невладение языком.


    Это мы уже слышали. Давай лучше расскажи нам, насколько ты оцениваешь своё владение языком по шкале от одного до десяти.
    Глаза у меня добрые, но рубашка — смирительная!
    Re[20]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 24.03.17 18:26
    Оценка: :)
    Здравствуйте, Serginio1, Вы писали:

    S>Здравствуйте, samius, Вы писали:


    S>>Никакая джит оптимизация цикла в этом конкретном Sum не может отменить тот факт, что цикл будет дергать MoveNext/Current через интерфейс. Так чем же он лучше, чем обычный

    S>>
    S>>int Sum(IEnumerable<int>)
    S>>


    S> Кстати интересная статья

    S>C# value type boxing under the hood

    Если эту статью переносить на случай "int Sum<T>(T coll) where T :IEnumerable<int>", то статья нам указывает на то, что метод IEnumerable<T>.GetEnumerator() будет вызван действительно без боксинга. Однако, результат этого метода IEnumerator<T>. Интерфейс, не структура. И методы интерфейса IEnumerator<T> будут далее вызываться как методы интерфейса. Ведь что бы получить value-перечислитель, нужно вызвать метод
    MyValueEnumerator GetEnumerator()
    ,а этот метод не удовлетворяет сигнатуре
    IEnumarator<T> GetEnumerator()
    Т.е. совсем другой метод. Компилятор или джит (не суть важно) могут вызывать лишь метод, на который им указывает ограничение T. Других методов-то они вообще не видят так, как их видят шаблоны в C++. В итоге, компилятор или джит могут вызвать этот метод но как-то по-другому, т.е. не через интерфейс, а напрямую. Они не могут вызвать метод, который не обещан ограничением. И не могут изменить тип результата с IEnumerator<T> на MyValueEnumerator. В итоге лишь один вызов GetEnumerator() может быть вызван не через интерфейс. Все остальные методы при foreach забеге будут вызваны через IEnumerator<T>.
    Re[21]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 24.03.17 19:57
    Оценка:
    Здравствуйте, samius, Вы писали:


    Я всего на всего привел интересный прием для вызова без боксинга.
    Только и всего.
    и солнце б утром не вставало, когда бы не было меня
    Re[17]: Владелец языка
    От: vdimas Россия  
    Дата: 24.03.17 21:41
    Оценка:
    Здравствуйте, Qbit86, Вы писали:

    V>>Это надо так понимать, что упорно продолжаешь делать попытка переформулировать моё исходное утверждение?

    Q>Это называется не переформулировать, а процитировать. Это ведь ты его вытащил в эту ветку, никто тебя за язык не тянул.

    Ну так процитируй и устыдись.
    Re[21]: Не понимая и половины
    От: vdimas Россия  
    Дата: 24.03.17 23:55
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>Если эту статью переносить на случай "int Sum<T>(T coll) where T :IEnumerable<int>", то статья нам указывает на то, что метод IEnumerable<T>.GetEnumerator() будет вызван действительно без боксинга. Однако, результат этого метода IEnumerator<T>.


    Может и так, а может и нет.
    Ты уже присоединился отладчиком в релизному бинарнику и проверил?

    Там же происходит парная операция:
    — боксинг структуры, реализующей IEnumerator;
    — обращение к полям этой структуры.

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

    Я просто хочу, чтобы ты проверил — будет ли там вызов метода интерфейса или прямой.


    S>Интерфейс, не структура.


    Это в терминах опкодов VM там интерфейс, что не интресует от слова совсем.
    Напомню, что вэлью типов джит генерит уникальную версию метода под каждый такой тип.


    S>И методы интерфейса IEnumerator<T> будут далее вызываться как методы интерфейса.


    Не надо ничего объяснять, как оно там в опкодах это и так понятно. ))


    S>Т.е. совсем другой метод. Компилятор или джит (не суть важно) могут вызывать лишь метод, на который им указывает ограничение T. Других методов-то они вообще не видят так, как их видят шаблоны в C++.


    Как раз для вэлью типов это не совсем так. Интерфейсный метод в исходном вэльютипе может вернуть другой вэльютип, реализующий IEnumerator<>. Джит это прекрасно видит, т.е. такие оптимизации принципиально возможны. Мне лишь любопытно — они по-факту уже происходят или еще нет? Или мне опять смотреть ассемблерный листинг? ))


    S>Они не могут вызвать метод, который не обещан ограничением. И не могут изменить тип результата с IEnumerator<T> на MyValueEnumerator.


    Могут аж бегом. Именно теоретически есть такая возможность, т.е. у джита есть вся необходимая для этого информация.
    Ну ОК, если тебе не интересно проверить, будем считать, что это не принципиально.
    Re[22]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 10:54
    Оценка:
    Здравствуйте, Serginio1, Вы писали:

    S>Здравствуйте, samius, Вы писали:



    S> Я всего на всего привел интересный прием для вызова без боксинга.

    S>Только и всего.

    Мне кажется что он был интересным во время выхода C# 2, т.е. году в 2005-м.
    Re[22]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 11:20
    Оценка: +1 -1
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Если эту статью переносить на случай "int Sum<T>(T coll) where T :IEnumerable<int>", то статья нам указывает на то, что метод IEnumerable<T>.GetEnumerator() будет вызван действительно без боксинга. Однако, результат этого метода IEnumerator<T>.


    V>Может и так, а может и нет.

    V>Ты уже присоединился отладчиком в релизному бинарнику и проверил?

    V>Там же происходит парная операция:

    V>- боксинг структуры, реализующей IEnumerator;
    V>- обращение к полям этой структуры.

    Я уж не знаю, как тебе объяснить, что бы ты понял. Может, действительно отладчик по релизному бинарнику в этом поможет? Не уверен, попробую болдом донести.
    При твоем способе вызова нет никакой структуры, реализуюющей IEnumerator. foreach не видит метод, возвращающий структуру. Он может оперировать лишь методом, который указан в ограничении. А тот возвращает класс, реализующий интерфейс.

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

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

    V>Я просто хочу, чтобы ты проверил — будет ли там вызов метода интерфейса или прямой.

    А я хочу, что бы ты понял. В случае вызова метода GetEnumerator() структуры-параметра call, будет прямой метод. И этот прямой метод возвращает не структуру, а объект-перечислитель. Структура-перечислитель даже не создастся. И это проверено.
    Структура-перечислитель может создаться в таком случае лишь в том случае, если она будет возвращена методом, возвращающим IEnumerator<T>. Но она уже будет забокшенная и unbox в этом случае делать никто не будет.


    S>>Интерфейс, не структура.


    V>Это в терминах опкодов VM там интерфейс, что не интресует от слова совсем.

    V>Напомню, что вэлью типов джит генерит уникальную версию метода под каждый такой тип.
    Уникальная версия метода под каждый тип вызовет кошерно метод IEnumerator<T> GetEnumerator() и далее будет работать универсально с возвращенным перечислителем (через интерфейс).


    S>>И методы интерфейса IEnumerator<T> будут далее вызываться как методы интерфейса.


    V>Не надо ничего объяснять, как оно там в опкодах это и так понятно. ))

    Если не надо объяснять, то что за у тебя романтические фантазии?


    S>>Т.е. совсем другой метод. Компилятор или джит (не суть важно) могут вызывать лишь метод, на который им указывает ограничение T. Других методов-то они вообще не видят так, как их видят шаблоны в C++.


    V>Как раз для вэлью типов это не совсем так. Интерфейсный метод в исходном вэльютипе может вернуть другой вэльютип, реализующий IEnumerator<>. Джит это прекрасно видит, т.е. такие оптимизации принципиально возможны. Мне лишь любопытно — они по-факту уже происходят или еще нет? Или мне опять смотреть ассемблерный листинг? ))

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


    S>>Они не могут вызвать метод, который не обещан ограничением. И не могут изменить тип результата с IEnumerator<T> на MyValueEnumerator.


    V>Могут аж бегом. Именно теоретически есть такая возможность, т.е. у джита есть вся необходимая для этого информация.

    Да, у джита так же есть вся необходимая информация для доказательства эквивалентности наблюдаемого поведения при подмене метода, типа и т.п. А еще для решения проблемы останова. Теоретически мог бы решить.

    V>Ну ОК, если тебе не интересно проверить, будем считать, что это не принципиально.

    Я проверил. Метод GetEnumerator(), возвращающий структуру не вызывается внутри Sum(T coll) от слова "вообще".
    Re[23]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 25.03.17 11:51
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>Здравствуйте, Serginio1, Вы писали:


    S>>Здравствуйте, samius, Вы писали:



    S>> Я всего на всего привел интересный прием для вызова без боксинга.

    S>>Только и всего.

    S>Мне кажется что он был интересным во время выхода C# 2, т.е. году в 2005-м.

    ну вообще то статья C# value type boxing under the hood

    C#, boxing, and typesystem | Mar 19, 2017

    Ты её читал?
    и солнце б утром не вставало, когда бы не было меня
    Re[24]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 12:00
    Оценка:
    Здравствуйте, Serginio1, Вы писали:

    S>Здравствуйте, samius, Вы писали:


    S>>Мне кажется что он был интересным во время выхода C# 2, т.е. году в 2005-м.

    S> ну вообще то статья C# value type boxing under the hood

    S>C#, boxing, and typesystem | Mar 19, 2017


    S>Ты её читал?

    Теперь прочитал. Так что в ней нового, по твоему с 2005-го года?
    Re[25]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 25.03.17 12:15
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>>>Мне кажется что он был интересным во время выхода C# 2, т.е. году в 2005-м.

    S>> ну вообще то статья C# value type boxing under the hood

    S>>C#, boxing, and typesystem | Mar 19, 2017


    S>>Ты её читал?

    S>Теперь прочитал. Так что в ней нового, по твоему с 2005-го года?

    То что это не дженерик интерфейс

    interface IAdd
    {
        void Add(int val);
    }
    и солнце б утром не вставало, когда бы не было меня
    Re[26]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 12:25
    Оценка:
    Здравствуйте, Serginio1, Вы писали:

    S>Здравствуйте, samius, Вы писали:


    S>>>C#, boxing, and typesystem | Mar 19, 2017


    S>>>Ты её читал?

    S>>Теперь прочитал. Так что в ней нового, по твоему с 2005-го года?

    S>То что это не дженерик интерфейс


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

    Так что нового в этой статье-то?
    Re[23]: Не понимая и половины
    От: vdimas Россия  
    Дата: 25.03.17 13:49
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>Я уж не знаю, как тебе объяснить, что бы ты понял.


    Никак. Потому что ты выдаешь своё упрямство за якобы непонимание окружающих.

    Просто прими как факт — ты говоришь простые вещи, которые понятны сходу даже новичкам. Даже студентам.
    Значит, это не я тебя не понимаю, а ты меня.


    S>При твоем способе вызова нет никакой структуры, реализуюющей IEnumerator. foreach не видит метод, возвращающий структуру. Он может оперировать лишь методом, который указан в ограничении. А тот возвращает класс, реализующий интерфейс.


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


    S>Это ты мог видеть в тестах, где foreach оперирует явно методом GetEnumerator(), возвращающим структуру.


    Это я видел в тестах, где возвращается класс.

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


    S>В случае вызова метода GetEnumerator() структуры-параметра call, будет прямой метод.


    Сие означает, что джит может "проинлайнить" прямой метод. А значит он может "увидеть" реальный возвращаемый тип, еще ДО боксинга возвращаемого значения. Затем он увидит, что отбоксированное значение не покидает scope, поэтому операцию бокирования можно "сократить в уме". Но тебе такие "сложности" не даются, вестимо. ))

    И мне даже несколько пофик, умеет ли он это делать на сейчас или нет. Это вопрос десятый, потому что до RyuJit прошлые версии джита тоже много чего не делали. Я лишь пытался заставить тебя вместо тысячи слов дать сюда ассемблерный листинг, потому что вы, здешние дотнетовцы, ленивы как стадо ленивцев, отсюда имеете репутацию скучных собеседников на сайте. ))
    Re[27]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 25.03.17 16:45
    Оценка:
    Здравствуйте, samius, Вы писали:


    S>>То что это не дженерик интерфейс


    S>Не дженерик интерфейс появился еще раньше в первом дотнете. В 2005м появились констрейнты, с помощью которых можно было подавать в генерик методы реализации (в том числе структурами) интерфейсов (и не дженерик в том числе) в дженерик методы.


    S>Так что нового в этой статье-то?


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

    Причина в том, что компилятор теперь имеет достаточно информации, чтобы понять, что код для Foo, и вызов интерфейса попадет именно на Foo.IAdd.Add , поэтому он пропускает формальность и вызывает функцию напрямую. Это как оптимизация производительности, но также с заметным побочным эффектом.


    Возможно это было и предыдущих JIT ах.
    и солнце б утром не вставало, когда бы не было меня
    Re[28]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 18:03
    Оценка: 27 (1) +1
    Здравствуйте, Serginio1, Вы писали:

    S>Здравствуйте, samius, Вы писали:


    S>>Так что нового в этой статье-то?


    S> Для меня было открытием, что констрейнт типа интерфейса, позволяет вызывать метод не через таблицу интерфейсов, а сразу нужный метод.

    S>При этом для валуе типов нет боксинга.

    CSharp 2.0 Specification_Sept_2005.doc (20.7.3)

    When a struct type overrides a virtual method inherited from System.Object (such as Equals, GetHashCode, or ToString), invocation of the virtual method through an instance of the struct type doesn’t cause boxing to occur. This is true even when the struct is used as a type parameter and the invocation occurs through an instance of the type parameter type.
    ...

    Similarly, boxing never implicitly occurs when accessing a member on a constrained type parameter. For example, suppose an interface ICounter contains a method Increment which can be used to modify a value. If ICounter is used as a constraint, the implementation of the Increment method is called with a reference to the variable that Increment was called on, never a boxed copy.

    using System;
    interface ICounter
    {
        void Increment();
    }
    struct Counter: ICounter
    {
        int value;
        public override string ToString() {
            return value.ToString();
        }
        void ICounter.Increment() {
            value++;
        }
    }
    class Program
    {
        static void Test<T>() where T: ICounter, new() {
            T x = new T();
            Console.WriteLine(x);
            x.Increment();                        // Modify x
            Console.WriteLine(x);
            ((ICounter)x).Increment();        // Modify boxed copy of x
            Console.WriteLine(x);
        }
        static void Main() {
            Test<Counter>();
        }
    }


    The first call to Increment modifies the value in the variable x. This is not equivalent to the second call to Increment, which modifies the value in a boxed copy of x. Thus, the output of the program is:
    0
    1
    1
    Re[29]: Не понимая и половины
    От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
    Дата: 25.03.17 19:15
    Оценка:
    Здравствуйте, samius, Вы писали:

    Большое спасибо! Не знал
    и солнце б утром не вставало, когда бы не было меня
    Re[24]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 20:17
    Оценка: +1
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Я уж не знаю, как тебе объяснить, что бы ты понял.


    V>Никак. Потому что ты выдаешь своё упрямство за якобы непонимание окружающих.


    V>Просто прими как факт — ты говоришь простые вещи, которые понятны сходу даже новичкам. Даже студентам.

    V>Значит, это не я тебя не понимаю, а ты меня.
    Я же не скрываю, что тебя не понимаю. Я ведь спрашиваю.


    S>>При твоем способе вызова нет никакой структуры, реализуюющей IEnumerator. foreach не видит метод, возвращающий структуру. Он может оперировать лишь методом, который указан в ограничении. А тот возвращает класс, реализующий интерфейс.


    V>Это так написано в LI-коде метода-генерика, что НЕ является аргументом в этом споре.

    V>Собсно, положа руку на — это махровое нубство, аппелировать к таким вещам, когда про вэльютипы достоверно известно, что джит генерит уникальный код для каждого такого типа в рантайм.
    Ну вот только прочитав твое сообщение до конца, я понял, что ты говоришь не о сценарии с ImmutableArray<T>, а о каком-то другом сценарии. Т.е. ты не рассчитываешь что будет вызван метод, возвращающий структуру. Ты рассчитываешь на то, что структура будет возвращена обычным методом, реализующим IEnumerator<T> GetEnumerator(). Ты же об этом не написал нигде. Но я-то полагал что ты рассчитываешь на то что джит вызовет метод, возвращающий структуру явно.


    S>>Это ты мог видеть в тестах, где foreach оперирует явно методом GetEnumerator(), возвращающим структуру.


    V>Это я видел в тестах, где возвращается класс.


    V>Т.е., фишка в том, что джит оперировал знаниями о внутреннем устройстве типа, а так же "видел" время жизни неких данных, т.е. продемонстрировал способность делать некие предположения о данных и использовать эти предположения для оптимизации.

    Не вопрос. Я был против того что бы джит подменял метод.


    S>>В случае вызова метода GetEnumerator() структуры-параметра call, будет прямой метод.


    V>Сие означает, что джит может "проинлайнить" прямой метод. А значит он может "увидеть" реальный возвращаемый тип, еще ДО боксинга возвращаемого значения. Затем он увидит, что отбоксированное значение не покидает scope, поэтому операцию бокирования можно "сократить в уме". Но тебе такие "сложности" не даются, вестимо. ))


    Видишь ли, я даже подумать не мог, что ты отклонился от темы с ImmutableArray<T>, List<T> и других реализаций паттерна для foreach, которые возвращают структуру паблик методом, не имеющим отношение к интерфейсу. И подумать не мог о том, что ты решил замутить "оптимизаци" за счет пессимизации основного сценария использования перечисления, что у тебя метод, возвращающий IEnumerable<T> в качестве результата будет выдавать ValueType, который при прямом использовании через IEnumerable<T> неминуемо отбоксится. Откуда ж я мог знать? Предупреждать надо о таких вещах, а не подразумевать их.

    V>И мне даже несколько пофик, умеет ли он это делать на сейчас или нет. Это вопрос десятый, потому что до RyuJit прошлые версии джита тоже много чего не делали. Я лишь пытался заставить тебя вместо тысячи слов дать сюда ассемблерный листинг, потому что вы, здешние дотнетовцы, ленивы как стадо ленивцев, отсюда имеете репутацию скучных собеседников на сайте. ))

    Не ленивее тебя.
    Re[30]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 25.03.17 20:17
    Оценка:
    Здравствуйте, Serginio1, Вы писали:

    S>Здравствуйте, samius, Вы писали:


    S>Большое спасибо! Не знал


    Не за что.
    Re[25]: Не понимая и половины
    От: vdimas Россия  
    Дата: 26.03.17 08:04
    Оценка:
    Здравствуйте, samius, Вы писали:

    S>Ну вот только прочитав твое сообщение до конца, я понял, что ты говоришь не о сценарии с ImmutableArray<T>, а о каком-то другом сценарии. Т.е. ты не рассчитываешь что будет вызван метод, возвращающий структуру.


    Ну да.


    S>Ты рассчитываешь на то, что структура будет возвращена обычным методом, реализующим IEnumerator<T> GetEnumerator(). Ты же об этом не написал нигде. Но я-то полагал что ты рассчитываешь на то что джит вызовет метод, возвращающий структуру явно.


    У нас тут происходит путаница м/у "джит вызовет" и "написано в IL".
    В IL будет написан вызов интерфейса.

    Мой поинт в том, что если джит инлайнит вызов GetEnumerator() у некоего T : IEnumerable<T>, где, в свою очередь, опять возвращается структура, то ему "видна" операция боксинга этой структуры, реализующей IEnumerator<>. Т.е. джиту виден полный цикл создаваемой через боксинг копии объекта, т.к. эта копия живет аккурат внутри цикла и далее никуда не передаётся и никуда не возвращается. Для уверенности в этом нужно заинлайнить методы такой структуры. Там есть ограничение на сложность инлайна, но в сценарии с итераторами там код из единиц строк обычно, т.е. самый востребованный для таких вещей сценарий одновременно является и самым простым..


    S>Видишь ли, я даже подумать не мог, что ты отклонился от темы с ImmutableArray<T>, List<T> и других реализаций паттерна для foreach, которые возвращают структуру паблик методом, не имеющим отношение к интерфейсу.


    А какая разница?
    Даже взять такую цепочку методов:
    struct MyList<T> : IEnumerable<T> {
      struct MyEnumerator : IEnumerator<T> {
      ...
      }
    
      public MyEnumerator GetEnumerator() {
        return new MyEnumerator(_myPrivateData);
      }
     
      IEnumerator<T> IEnumerable<T>.GetEnumerator() {
        return GetEnumerator();
      }
      ...
    }

    Я так думаю, что вот эту цепочку из двух методов вполне можно заинлайнить, ведь класс MyList — он final, то бишь sealed.

    Т.е. при генерации джитом уникального тела для нашего T=MyList<int>:
    static int SumOfInts<T>(T collection) : where T : IEnumerable<T> {
      int s = 0;
      foreach(var i in collection)
        s += i;
      return s;
    }

    у джита достоверно есть вся информация о том, что именно будет возвращено при вызове collection.Ienumerable<T>.GetEnumerator().

    Другое дело — воспользовались ли такими знаниями разработчики джита? Вот в чем был вопрос.
    В общем проверил — нихрена не воспользовались.
    Создаётся боксированная копия структуры и в теле цикла идёт косвенный вызов методов MoveNext и get_Current.


    S>И подумать не мог о том, что ты решил замутить "оптимизаци" за счет пессимизации основного сценария использования перечисления, что у тебя метод, возвращающий IEnumerable<T> в качестве результата будет выдавать ValueType, который при прямом использовании через IEnumerable<T> неминуемо отбоксится.


    Почему "неминуемо-то"?
    Я прекрасно вижу, как на 2-х if расписать логику джита в этой ситуации, чтобы он не боксил ничего.
    Re[26]: Не понимая и половины
    От: samius Япония http://sams-tricks.blogspot.com
    Дата: 28.03.17 18:08
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Здравствуйте, samius, Вы писали:


    S>>Видишь ли, я даже подумать не мог, что ты отклонился от темы с ImmutableArray<T>, List<T> и других реализаций паттерна для foreach, которые возвращают структуру паблик методом, не имеющим отношение к интерфейсу.


    V>А какая разница?

    V>Даже взять такую цепочку методов:
    V>
    V>struct MyList<T> : IEnumerable<T> {
    V>  struct MyEnumerator : IEnumerator<T> {
    V>  ...
    V>  }
    
    V>  public MyEnumerator GetEnumerator() {
    V>    return new MyEnumerator(_myPrivateData);
    V>  }
     
    V>  IEnumerator<T> IEnumerable<T>.GetEnumerator() {
    V>    return GetEnumerator();
    V>  }
    V>  ...
    V>}
    V>


    Разница в том, что ты значительно ухудшил производительность основного сценария, т.е. работы с перечислителем через интерфейс.

    V>Я так думаю, что вот эту цепочку из двух методов вполне можно заинлайнить, ведь класс MyList — он final, то бишь sealed.


    V>у джита достоверно есть вся информация о том, что именно будет возвращено при вызове collection.Ienumerable<T>.GetEnumerator().


    В 99% случаев у него будет информация что это IEnumerator<T>, т.к. TColl будет в точности IEnumerable<T>.

    V>Другое дело — воспользовались ли такими знаниями разработчики джита? Вот в чем был вопрос.

    V>В общем проверил — нихрена не воспользовались.
    V>Создаётся боксированная копия структуры и в теле цикла идёт косвенный вызов методов MoveNext и get_Current.


    S>>И подумать не мог о том, что ты решил замутить "оптимизаци" за счет пессимизации основного сценария использования перечисления, что у тебя метод, возвращающий IEnumerable<T> в качестве результата будет выдавать ValueType, который при прямом использовании через IEnumerable<T> неминуемо отбоксится.


    V>Почему "неминуемо-то"?

    Потому что там где нужен интерфейс, структура напрямую не пролезет.

    V>Я прекрасно вижу, как на 2-х if расписать логику джита в этой ситуации, чтобы он не боксил ничего.


    from x in myColl select x*x

    ы?
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.