Comments 32
Миф развенчали, но есть вопросы.
Почему не 16 а 64?
Справедливо ли ваше утверждение для 32-битных процессоров?
Что дороже скопировать ссылку или структуру такого же размера?
Справедливо ли ваше утверждение для 32-битных процессоров?
И для ARM.
Почему не 16 а 64?
Признаться, я не копал в эту сторону. Для себя решил, что это текущие детали реализации JIT-компилятора.
Справедливо ли ваше утверждение для 32-битных процессоров?
Вот это, как и другие вопросы в комментариях, как говорится, совсем другая история *Леонид Каневский.жпг* Я не просто так в начале статьи написал дисклеймер. :) На каждый из вопросов, по-сути, надо делать отдельный бенчмарк и сравнивать. Когда комментарии настоятся, то, возможно, сделаю ещё один пост с ответами на вопросы.
Суть использования ссылок, это многократное использование объекта. Объект 1 раз создался и многократно может быть передан ссылкой. То есть на системе х64 ссылка весит 8 байт. Копирование ссылки в 8 байт не может быть дороже копирования структуры 64 байта. Хотелось бы понять, что вы замерили? От прочтения статьи у меня сложилось впечатление, что использовать структуры до 64 байты выгоднее, но так ли это?
У 16 байт есть вполне адекватное объяснение, это 8 байт ссылки + накладные расходы на обращение к какому-то полю, как обосновать 64?
Копирование ссылки в 8 байт не может быть дороже копирования структуры 64 байта. Хотелось бы понять, что вы замерили?
Я не утверждаю, что копирование 64 байт быстрее копирования 8 байт. Я показал, как использование структур определённого размера не ухудшает производительность. Достигается это за счёт оптимизаций. Это разные вещи.
Можете попробовать запустить бенчмарк у себя и поизучать. Я приложил ссылку на GitHub с кодом.
Вы правы в том, что копировать 8 байт быстрее, но как быть, если вам надо прочитать объект из памяти по адресу, осуществить обработку и таких объектов у вас миллионы. Если это будет храниться в структурах, то платформа разместит это все рядом, на соседних страницах памяти. Значит в кэш процессора будут попадать данные, которые, скорее всего вам понадобятся на следующих итерациях, значит процессор не будет лезть в RAM и возьмет данные из кэша. Доступ к кэшу мгновенный. Это и есть основное преимущество значимых типов в .net. Если использовать ссылочный типы, они могут быть разбросаны по разным блокам памяти, и доступ к ним существенно медленнее, и будет вести к фрагментации памяти и вызовам GC. Тем не менее, среда довольно умная и может эта исправить с помощью сжатия (дефрагментации) памяти.
Ссылки берутся из тн "кучи", а значения из стэка. (если не занудствовать)
Предположительно, в примере структуры до 64 байт могут работать быстрее, т.к. используемый dotnet runtime умеет все значения этих структур размещать в регистрах данного процессора, а достаточно простые функции при этом встраивать в вызывающий код.
При параметрах функции более 64 байт, очевидно, их передачу уже приходится осуществлять через стек, что гораздо медленнее.
Вы копали вообще не в ту сторону. В вашем тесте смешано и заменено вообще всё, что можно.
1) в нем тестируется аллокация тоже, что в корне не верно. Надо тестировать лишь передачу параметров. Хотя, кстати, в оригинальных guidelines от MS вообще ничего не сказано про передачу параметров. Но раз уж вы захотели тестировать именно это, то надо исключить создание объекта.
2) в тесте используются лямбды, которые транслируются инлайн почти всегда (или вообще всегда?). Для чистого теста надо было объявить внешние функции (в другом файле?).
3) но в чем, собственно, вопрос? Даже структуру на 128 байт выгоднее использовать, если она количество передач ее в параметре не сильно больше количества созданий собственно структуры. То есть, она выигрывает у 128-байтного класса на создании, потому что не лезет/меняет хип при создании/удалении (а ваш тест удаление классов вообще не затрагивает, наверное).
Вангую: 64 байта - CPU cache entry size (cache line).
Разница в производительности - цена (время) копирования из кеша в память.
Если кто то сможет запустить тест на CPU c 32 байт cache line - подтвердим гипотезу.
Благодарю. Было интересно посмотреть на аткульные "лимиты" структур.
Но данные, вероятно, будут разными под разные платформы (win/linux/arm/x86/x64) в силу различий VM.
каков будет результат если принудительно отключить инлайн методов? в реальной жизни далеко не все методы инлайнятся.
Но если структуру передавать в более сложные методы, которые в свою очередь могут ее куда-то еще передавать, она не заинлайнится и тогда каждый раз будет производится копирование. И как раз в этом случае передача по ссылке должна смотреться выигрышнее.
Кроме того они ещё и как record помечены, то бишь иммутабельные записи, так что там ещё некоторый простор с оптимизацией во время компиляции существует с такой границей .
Структуры-record'сы не помечены readonly, так что иммутабельны здесь только классы. Но мне вот стало интересно на что использование record'сов влияет под капотом.
Не знаток C#, но мне казалось, что record завели для как раз таки компайл-тайм инварианта, позволяя как оптимизацию исполнения так и расположения в памяти. Несколько странно, конечно, что их разрешают делать мутабельными.
While records can be mutable, they're primarily intended for supporting immutable data models
По умолчанию, при передаче в метод или при возврате из метода, экземпляры значимых типов копируются, когда как экземпляры ссылочных типов передаются по ссылке.
По-умолчанию, копируются оба. Я понимаю, что автор хотел сказать, но не могу не позанудствовать.
А какую гипотезу-то проверяли? Пока выглядит будто проверяли скорость работы вектора на разных размерах данных.
А рекомендации относятся к копированию при вызове метода.
То есть бенчмарки не согласуются с гипотезой от слова никак.
А как на вызов метода это влияет кроме миниатюрного выигрыша на загрузке памяти в кэш, если метод обрабатывает всю структупу?
Ну хоть кто-то здесь адекватный.
Интересно. Но всё таки си шар используют для повышения скорости разработки и такие тонкие вопросы оптимизации в нём возникают крайне редко. Обычно оптимизируется то, что даёт прирост на порядок.
В сравнении CIL кода показаны методы GetClass16/64, а не GetStruct16/64
Эта оптимизация известна под названием function inlining или method inlining. Благодаря этой оптимизации, CLR не пришлось копировать экземпляр структуры при возврате из метода.
Ну так и ваш тест тогда некорректен, потому что он не тестирует собственно правило 16 байт, т.е. разницу в скорости копирования возвращаемого значения, потому что метод заинлайнился и ничего не возвращается. Оно так же и в 2008 работало скорее всего. Необходимо пометить методы атрибутом [MethodImpl(MethodImplOptions.NoInlining)]
Я не утверждал в статье, что 64 байта копируется быстрее. Я написал, что структуры до 64 байт не ухудшают производительность. Да, это происходит при определённых условиях (смотрим дисклеймер), да за счёт оптимизаций, но это всё же происходит. Но, спасибо за рекомендацию. В комментах уже встречался совет проверить без инлайна. Пожалуй, проверю и сделаю отдельную публикацию, т.к. самому интересно. :)
public record struct Class04(int Param);
Похоже что ппечатка.
Правило 16 байт: развенчиваем миф о производительности структур в C#