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>. А значит — все равно отбоксится.


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