Сообщение Re[11]: Минутка WTF-19: Catch me if you can от 20.03.2017 19:05
Изменено 20.03.2017 19:10 vdimas
Re[11]: Минутка WTF-19: Catch me if you can
Здравствуйте, 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
Проверил только что на дотнете 4.6.1, получил разницу в 2.3 раза и в 1.8 раз на x86 и x64 соответственно.
S>
Ох, и тебя самого ничего не настрожило разве? ))
А где же сомнения?
Без сомнений ведь не бывает здравого смысла.
Кароч, я добавил к длине массивов 3 нуля в твоём тесте и погонял.
Результаты для x86 :
Результаты для x64 :
Лично мне тут сходу всё стало ясно, бо чудес, увы, не бывает.
Например, в дебаге разница по-прежнему заметная.
Кароч, преодолевая свою лень, поясняю происходящее:
* Для простоты дизассемблированного кода сделал все поля статическими.
* Запустил релизный вариант как отдельный процесс, вставил в нужном месте ожидание с клавиатуры и присоединился отладчиком.
* Смотрим на дизассемблированный код:
* Обращаем внимание на кеширование в регистрах внутренних полей объектов по ссылке (чего-чего???):
Итого, в rdx у нас лежит указатель на "обернутый" массив, а вовсе не указатель на RefWrapper, который мы наивно собирались протестировать.
Далее в r9d помещается длина этого массива и в цикле происходит сравнение с ней счетчика i, под который используется регистр ecx.
* Итого, получилась профанация, а не тест. Через глобальный анализ джит (или кто там у них) "догадался", что обращения к полю _ref вне метода TestRefWrapper не наблюдается, поэтому он смело так предположил, что ему достаточно провести разыменование ссылки лишь в самом начале метода-теста, а в цикле уже не надо.
Но этого мало, смотри на цифры замеров еще раз.
Всего одна лишняя операция разыменования ДО начала основного цикла в тесте сделала результаты TestRefWrapper всё-равно хуже.
S>Остальное поскипал, т.к. там тоже надо сначала матчасть объяснять, а мне это уже влом.
Пару раз написал и стёр...
По грани ходишь, смотрю...
В общем, давай не будем спорить с тем, что лишняя косвенность — это жуткое зло, а то как-то некрасиво уже выходит.
Просто, в плюсах в тестах вообще творится черт его знает что — это порой сложная задачка, прямо-таки вызов (ы-ы-ы), так составить код, чтобы компилятор не выкинул нафиг лишнее в процессе оптимизации, где само это "лишнее" и является объектом тестирования, собсно! ))
Я с такими вещами сталкиваюсь ежедневно, поэтому результатам твоего "теста" не удивился ни разу, ес-но.
ИМХО, тут надо не "матчасть глупому сишнику объяснять" (С), а попробовать найти рекомендации для подобных практик.
Спустись с небес, кароч, а то воспарил, смотрю. ))
=====================
Итак, анализируем проблему:
раз неявный боксинг является источником проблем, то предлагаю НЕ реализовывать в обертках навроде ImmutableArray интерфейсы IEnumerable и Ко. Это будет защита на уровне системы типов.
Далее можно попытаться придумать какую-нить общую практику для таких оберток, выраженную в виде хелпера, например так:
Соответственно, методы-расширения в моём пруфе можно допилить до такого:
Итого, уходит зависимость методов-расширений от некоего конкретного типа ImmutableArray<T>.
Ну и помним, что методы-хелперы для синтаксиса foreachкушатьинтерфейсов не просят.
Ву а ля или еще нет?
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
Дата: 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.
* Итого, получилась профанация, а не тест. Через глобальный анализ джит (или кто там у них) "догадался", что обращения к полю _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
Ву а ля или еще нет?
Re[11]: Минутка WTF-19: Catch me if you can
Здравствуйте, 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
Проверил только что на дотнете 4.6.1, получил разницу в 2.3 раза и в 1.8 раз на x86 и x64 соответственно.
S>
Ох, и тебя самого ничего не настрожило разве? ))
А где же сомнения?
Без сомнений ведь не бывает здравого смысла.
Кароч, я добавил к длине массивов 3 нуля в твоём тесте и погонял.
Результаты для x86 :
Результаты для x64 :
Лично мне тут сходу всё стало ясно, бо чудес, увы, не бывает.
Например, в дебаге разница по-прежнему заметная.
Кароч, преодолевая свою лень, поясняю происходящее:
* Для простоты дизассемблированного кода сделал все поля статическими.
* Запустил релизный вариант как отдельный процесс, вставил в нужном месте ожидание с клавиатуры и присоединился отладчиком.
* Смотрим на дизассемблированный код:
* Обращаем внимание на кеширование в регистрах внутренних полей объектов по ссылке (чего-чего???):
Итого, в rdx у нас лежит указатель на "обернутый" массив, а вовсе не указатель на RefWrapper, который мы наивно собирались протестировать.
Далее в r9d помещается длина этого массива и в цикле происходит сравнение с ней счетчика i, под который используется регистр ecx, т.е. не происходит такого разыменования и при вызове метода Length.
* Итого, получилась профанация, а не тест. Через глобальный анализ джит (или кто там у них) "догадался", что обращения к полю _ref вне метода TestRefWrapper не наблюдается, поэтому он смело так предположил, что ему достаточно провести разыменование ссылки лишь в самом начале метода-теста, а в цикле уже не надо.
Но этого мало, смотри на цифры замеров еще раз.
Всего одна лишняя операция разыменования ДО начала основного цикла в тесте сделала результаты TestRefWrapper всё-равно хуже.
S>Остальное поскипал, т.к. там тоже надо сначала матчасть объяснять, а мне это уже влом.
Пару раз написал и стёр...
По грани ходишь, смотрю...
В общем, давай не будем спорить с тем, что лишняя косвенность — это жуткое зло, а то как-то некрасиво уже выходит.
Просто, в плюсах в тестах вообще творится черт его знает что — это порой сложная задачка, прямо-таки вызов (ы-ы-ы), так составить код, чтобы компилятор не выкинул нафиг лишнее в процессе оптимизации, где само это "лишнее" и является объектом тестирования, собсно! ))
Я с такими вещами сталкиваюсь ежедневно, поэтому результатам твоего "теста" не удивился ни разу, ес-но.
ИМХО, тут надо не "матчасть глупому сишнику объяснять" (С), а попробовать найти рекомендации для подобных практик.
Спустись с небес, кароч, а то воспарил, смотрю. ))
=====================
Итак, анализируем проблему:
раз неявный боксинг является источником проблем, то предлагаю НЕ реализовывать в обертках навроде ImmutableArray интерфейсы IEnumerable и Ко. Это будет защита на уровне системы типов.
Далее можно попытаться придумать какую-нить общую практику для таких оберток, выраженную в виде хелпера, например так:
Соответственно, методы-расширения в моём пруфе можно допилить до такого:
Итого, уходит зависимость методов-расширений от некоего конкретного типа ImmutableArray<T>.
Ну и помним, что методы-хелперы для синтаксиса foreachкушатьинтерфейсов не просят.
Ву а ля или еще нет?
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
Дата: 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
Ву а ля или еще нет?