Когда в C # эффективнее передавать структуры по значению, а когда по ссылке?

Я немного исследовал, и кажется, что здравый смысл гласит, что структуры должны быть меньше 16 байт, потому что в противном случае они будут снижать производительность при копировании. С C # 7 и ref return стало довольно легко полностью избежать копирования структур. Я предполагаю, что по мере уменьшения размера структуры передача по ref имеет больше накладных расходов, чем просто копирование значения.

Есть ли практическое правило, когда передача структур по значению выполняется быстрее, чем по ссылке? Какие факторы на это влияют? (Размер структуры, разрядность процесса и т. Д.)

Больше контекста

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

Также обратите внимание, что я не спрашиваю о передовых методах или разумности передавать все по исх. Мне известны «лучшие практики» и их последствия, и я сознательно предпочитаю им не следовать.

Обращение к "повторяющемуся" тегу

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

В .Net, когда, если когда-либо, мне следует передавать структуры по ссылке из соображений производительности? - Второй вопрос немного затрагивает тему, но он касается конкретного размера структуры.

Чтобы ответить на вопросы из статьи Эрика Липперта:

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

Это действительно узкое место? Скорее всего, нет. Но я все же хотел бы знать, поскольку это шаблон доступа к данным для 99% программы. На мой взгляд, это похоже на выбор правильной структуры данных.

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

О чем вы говорите «быстрее»? Например, когда ЦП меньше выполняет ту же задачу.

Вы смотрите на картину в целом? Да. Как уже говорилось ранее, это влияет на то, как я все это пишу.

Я знаю, что могу измерить множество различных комбинаций. И что это мне говорит? Этот X быстрее, чем Y, в моей комбинации [версия .NET, разрядность процесса, ОС, ЦП]. А как насчет Linux? А как насчет Android? А как насчет iOS? Должен ли я тестировать все перестановки на всех возможных комбинациях аппаратного и программного обеспечения?

Я не думаю, что это жизнеспособная стратегия. Поэтому я спрашиваю здесь, где, надеюсь, кто-то, кто много знает о CLR / JIT / ASM / CPU, может рассказать мне, как это работает, чтобы я мог принимать обоснованные решения при написании кода.

Ответ, который я ищу, аналогичен вышеупомянутому 16-байтовому руководству для размеров структур с объяснением почему.


person loodakrawa    schedule 19.09.2017    source источник
comment
Что быстрее?   -  person Peter Duniho    schedule 19.09.2017
comment
@PeterDuniho - другой вопрос касается передачи ссылочного типа по ссылке, и мой вопрос строго о размере структур. Кроме того, поскольку это влияет на 90% моих шаблонов доступа к данным, я не могу профилировать все перестановки различных вариантов преобразования данных.   -  person loodakrawa    schedule 19.09.2017
comment
Есть два отмеченных дубликата. Первый включает некоторое обсуждение сценария типа значения, а второй полностью посвящен этому. Поскольку я не могу профилировать все перестановки данных, вы можете профилировать важные перестановки. Сценарии, которые возникают нечасто, не стоит оптимизировать.   -  person Peter Duniho    schedule 19.09.2017
comment
передача структур довольно распространена в таком сценарии - почему? Помещение вещей в массив помогает только в том случае, если вы получаете доступ к вещам из массива. Как только вы копируете его в переменную, вы больше не пользуетесь данными, которые массив заполнял кешем. В любом случае ваш вопрос чисто умозрительный и слишком общий. Слишком много вещей, которые могут повлиять на производительность, чтобы кто-либо мог окончательно сказать вам, насколько велики могут быть ваши структуры, без необходимости передавать по ссылке, тем более что выбор дизайна повлияет на вещи, кроме вызовов методов.   -  person Peter Duniho    schedule 19.09.2017
comment
Ответы на второй вопрос сводятся к тому, когда использовать структуру, а когда нет. У меня есть реальный сценарий, в котором почти ВСЕ мои данные представлены в виде структур самых разных размеров.   -  person loodakrawa    schedule 19.09.2017
comment
Вы, очевидно, полностью упускаете суть. Я ничего НЕ копирую, потому что передаю и возвращаюсь по исх.   -  person loodakrawa    schedule 19.09.2017
comment
Ответы на второй вопрос сводятся к тому, когда использовать структуру, а когда нет - и ваш вопрос тоже. Если вы всегда проходите мимо рефери, почему вы задаете вопрос? Если вы пытаетесь сравнить передачу по ссылке с отсутствием передачи по ссылке, то сценарий, в котором вы не передаете по ссылке, включает копирование данных. В любом случае ваш вопрос остается слишком общим.   -  person Peter Duniho    schedule 19.09.2017
comment
Что общего в части, выделенной жирным шрифтом в моем вопросе? Я уверен, что на него можно однозначно ответить двумя предложениями тем, кто понимает внутреннюю работу CLR и JITter. Остальная часть вопроса была предназначена для описания того, чем мой сценарий отличается от сценариев в вопросах, которые вы, например, связали.   -  person loodakrawa    schedule 19.09.2017
comment
Как вы думаете, какие факторы влияют на это, можно сказать в двух предложениях? Извините, если вы действительно в это верите, вы серьезно недооценили сложность настройки производительности. Еще раз рекомендую вам Что быстрее?. Никакой ответ в рамках предполагаемого объема Stack Overflow не сможет адекватно ответить на ваш вопрос.   -  person Peter Duniho    schedule 19.09.2017
comment
Это только ваше мнение. Поскольку я все еще хочу получить свой ответ, какие у меня варианты? Как я могу заставить человека, который разбирается в теме больше, увидеть этот закрытый вопрос? Редактировать? Отметить это?   -  person loodakrawa    schedule 19.09.2017
comment
Вот мой мыслительный процесс при подходе к вопросу: на производительность влияют два фактора. Во-первых, где выделить память. Во-вторых, как вы передаете данные в метод. Если рассматривать только производительность передачи параметров, передача по значению никогда не будет быстрее, чем передача по ссылке, если только структура, которую вы передаете, меньше размера ссылки. Вы теряете производительность при передаче по значению, но вы увеличиваете производительность, выделяя память в стеке. Сколько вы можете потерять, зависит от того, сколько вы можете получить. Однако вопрос о том, сколько вы можете получить, гораздо важнее.   -  person Xiaoguo Ge    schedule 19.09.2017
comment
@PeterDuniho: передача элемента массива в качестве параметра ref позволит получателю действовать с ним на месте, даже если элемент является структурным типом. Это одно из больших преимуществ использования массивов структурных типов.   -  person supercat    schedule 20.09.2017
comment
@supercat: Передача элемента массива в качестве параметра ref позволит получателю действовать с ним на месте - я это хорошо знаю. Ну и что? Поскольку вопрос заключается в сравнении передачи по значению с передачей по ссылке, следует предположить, что нет необходимости изменять значение. В противном случае это даже не было бы вопросом, потому что передача по ссылке была бы единственным вариантом.   -  person Peter Duniho    schedule 20.09.2017
comment
@PeterDuniho: Я имел в виду ваше утверждение о том, что наличие структур в массиве полезно только при доступе к вещам из массива. Было непонятно, считали ли вы доступы через byref как обращения из массива.   -  person supercat    schedule 20.09.2017
comment
@PeterDuniho: надо полагать, что нет необходимости изменять значение - о чем вы? Вы можете скопировать его из массива, изменить и скопировать обратно.   -  person loodakrawa    schedule 20.09.2017
comment
@loodakrawa: Вы можете скопировать его из массива, изменить и скопировать обратно - вы могли. Но это было бы довольно глупо, если бы передача по ссылке уже рассматривалась. Особенно, если кто-то не потрудился провести какое-либо фактическое тестирование производительности, чтобы увидеть, есть ли какая-то польза от полного игнорирования семантики операции как основы дизайна.   -  person Peter Duniho    schedule 20.09.2017
comment
В этом весь смысл. Когда структуры достаточно малы, копирование происходит быстрее. Я пытаюсь понять, что определяет этот предел - вероятно, способ обработки ссылок CLR / JIT. Тестирование производительности на самом деле не говорит мне, ПОЧЕМУ, что, по сути, является моим вопросом. См. Правки.   -  person loodakrawa    schedule 20.09.2017
comment
почему на вопросы трудно ответить. Если ваш вопрос в том, какой машинный код генерируется джиттером для копии по ссылке или копии по значению? затем используйте отладчик, чтобы просмотреть машинный код, созданный для вашего конкретного сценария.   -  person Eric Lippert    schedule 21.09.2017
comment
Это действительно актуальный вопрос. Я считаю это совершенно идиотским для 99% всех программ, но если вы выполняете внутренний игровой цикл или тикер-завод для торгового бэкэнда, это именно тот тип проблемы, который действительно возникает и ИМЕЕТ РАЗНИЦУ. Это станет еще более актуальным, если вы примете во внимание диапазон, чтобы вы могли перемещаться по представлениям / частям массива без копирования. У меня лично есть программы, в которых основной цикл составляет около 3 страниц кода, используя 95% времени обработки и - запускает циклы, обновляя значения в массиве, которые представлены в виде структур по соображениям производительности. Хороший вопрос.   -  person TomTom    schedule 07.03.2020
comment
Это не тот ответ, который вы ищете, но вас может заинтересовать следующий вопрос: stackoverflow.com/questions/2437925/ плюс мое собственное базовое исследование здесь: forum.unity.com/threads/opinions-about-tokenizing.362531 / Вывод для меня заключался в том, что этот вопрос лучше оставить без ответа. Не потому, что никто не знает, а потому, что это деталь реализации, которая может меняться со временем и в окружающей среде.   -  person eisenpony    schedule 09.03.2020


Ответы (3)


как правило, передача по ссылке должна быть быстрее.
когда вы передаете структуру по ссылке, вы передаете только указатель на структуру, который является только 32- или 64-битным целым числом.
когда вы передаете структуру по значению , вам нужно скопировать всю структуру, а затем передать указатель на новую копию.
если структура не очень маленькая, например, int, передача по ссылке выполняется быстрее.

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

person GideonMax    schedule 11.03.2020
comment
Чтобы уточнить, в этом случае очень маленький = меньше, чем указатель на устройстве. - person LLSv2.0; 11.03.2020
comment
@ LLSv2.0 да, хотя в определенных обстоятельствах вы все равно захотите передать по значению, чтобы упростить работу с кодом, хотя это ДЕЙСТВИТЕЛЬНО зависит от ситуации. - person GideonMax; 11.03.2020
comment
Требуется больше эмпирических данных. Для передачи «указателя» требуется дополнительный косвенный вызов, поэтому (и в зависимости от реализации) есть некоторая точка отсечения, выходящая за рамки этой простой общности. Что это за точка перегиба и где? Как / почему он различается в зависимости от среды? - person user2864740; 11.03.2020
comment
Еще одно соображение: передача структуры по ссылке приводит к упаковке / распаковке структуры? Если так, это увеличит стоимость проезда по исх. Тогда порог большего размера может быть подходящим для передачи по ссылке, а не по значению. - person Riggy; 12.03.2020
comment
@Riggy Согласно Microsoft, существует нет упаковки типа значения, когда он передается по ссылке. - person Riggy; 12.03.2020
comment
@ user2864740 в стандартных реализациях вы передаете указатель на структуру, будь то ссылка или значение, вы не передаете указатель на указатель, поскольку это было бы ненужным косвенным обращением, и любой хороший компилятор не будет этого делать - person GideonMax; 16.03.2020
comment
передача по значению увеличит количество обращений к ОС для выделения и отмены выделения памяти - это звучит очень неправильно. У вас есть источник для этого? Я ожидал, что он будет использовать стековую память без каких-либо дополнительных выделений. - person loodakrawa; 16.03.2020
comment
@loodakrawa во многих случаях, например, структуры, которые являются элементами массива, структуры будут размещаться в куче, в этом случае структуры находятся в массивах. хотя я согласен, что это не всегда применимо. Кроме того, распределение стека / кучи зависит от реализации, поэтому на это лучше не полагаться. - person GideonMax; 16.03.2020
comment
Что ж, я согласен, но это не имеет ничего общего с прохождением структур. После того, как вы выделяете массив структур, больше не будет выделения кучи, независимо от того, как вы получаете доступ к данным в массиве - либо через ref, либо через копирование. И вы отвечаете, что при передаче по значению будет выделена дополнительная память. - person loodakrawa; 16.03.2020
comment
Кроме того, как сказал @ user2864740 - я спрашиваю о точке перегиба. 1 int быстрее, согласен. А как насчет 2-х целых? 3? 4? - person loodakrawa; 16.03.2020
comment
@loodakrawa точка перегиба зависит от реализации и оборудования, она зависит от эффективности сборщика мусора, скорости различных операций и т. д. в целом я бы сказал, что она должна быть ниже 12 int, но для точки перегиба вам нужно провести тесты . - person GideonMax; 16.03.2020

Если вы передаете структуры по ссылке, они могут быть любого размера. Вы все еще имеете дело с 8-байтовым указателем (предполагается, что x64). Для максимальной производительности вам необходим дизайн, дружественный к кеш-памяти ЦП, который называется Data Driven Design.

В играх часто используется особый дизайн, управляемый данными, который называется Entity Component System. См. Книгу Конрада Кокосы Pro .NET Memory Management, глава 14.

Основная идея заключается в том, что вы можете обновлять свои игровые объекты, например Movable, Car, Plane, ... имеют общие свойства, такие как позиция, которая предназначена для всех сущностей, хранящихся в непрерывном массиве. Если вам нужно увеличить позицию 1K сущностей, вам просто нужно найти индекс массива в массиве позиций всех сущностей и обновить их там. Это обеспечивает наилучшую возможную локальность данных. Если бы все было сохранено в классах, предварительная выборка ЦП была бы потеряна из-за множества указателей this для каждого экземпляра класса.

См. Это сообщение Intel о некоторой эталонной архитектуре: https://software.intel.com/en-us/articles/get-started-with-the-unity-entity-component-system-ecs-c-Sharp-job-system-and-burst-compiler

Существует множество систем Entity Component System, но до сих пор я не видел ни одной, использующей ссылки в качестве основной рабочей структуры данных. Причина в том, что все популярные существуют намного дольше, чем C # 7.2, в котором были введены реф-структуры.

person Alois Kraus    schedule 13.03.2020
comment
Внедрение ECS в качестве базовой архитектуры - это именно та причина, по которой я задал этот вопрос в первую очередь. В любом случае, если данные хранятся не как массив структур, а как массив классов, то вполне вероятно, что данные будут распределены по куче и никоим образом не будут смежными, потому что массив классов фактически представляет собой массив указателей на куча - person loodakrawa; 16.03.2020
comment
@loodakrawa: Вы дополнили свою систему ECS функциями C # 7.2? Это открытый исходный код? Было бы интересно, как ваш сравнивается с другими подобными Entitas. - person Alois Kraus; 17.03.2020
comment
Я реализовал это ~ 3 года назад, поэтому не использовал возможности 7.2. С тех пор я перестал пытаться делать игры, но сейчас я снова вхожу в это, так что, вероятно, я обновлю его новыми вкусностями. В любом случае, вы можете проверить это здесь: github.com/loodakrawa/ScatteredLogic - person loodakrawa; 17.03.2020
comment
Здесь фреймворк ECS с открытым исходным кодом со ссылками: LeoECS. Быстро и не только для игр. Наслаждайтесь этой скоростью =) - person picolino; 20.08.2020