Является ли структура, оборачивающая примитивный тип значения, абстракцией с нулевой стоимостью в C#?

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

struct AngleRadians {
  public readonly double Value;
  /* Constructor, casting operator to AngleDegrees, etc omitted for brevity... */
}

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

Любое упоминание о преждевременной оптимизации будет опущено. Мне интересно знать правду о земле.

Изменить: чтобы сузить суть вопроса, вот несколько сценариев, представляющих особый интерес...

// 1. Is the value-copy constructor zero cost?
// Is...
var angleRadians = new AngleRadians(myDouble);
// The same as...
var myDouble2 = myDouble;

// 2. Is field access zero cost?
// Is...
var myDouble2 = angleRadians.Value;
// The same as...
var myDouble2 = myDouble;

// 3. Is function passing zero cost?
// Is calling...
static void DoNaught(AngleRadians angle){}
// The same as...
static void DoNaught(double angle){}
// (disregarding inlining reducing this to a noop

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

2 ответа

Решение

Могут быть небольшие и заметные различия из-за требований ABI. Например, для Windows x64, с плавающей запятой или двойником будет передан вызываемому объекту через регистр целых чисел, в то время как с плавающей запятой и двойники передаются через регистры XMM (аналогично для возвратов). Через регистры можно передать не более 4-х целых и 4-х чисел.

Фактическое влияние этого очень зависит от контекста.

Если вы расширите свой пример для передачи как минимум 5 целочисленных аргументов и аргументов struct-or-double, вы быстрее исчерпаете регистры целочисленных аргументов arg в двойном регистре struct, и вызовы и доступ к завершающему (не зарегистрированному регистру)) Арги в вызываемом вызове будут немного медленнее. Но эффект может быть незаметным, так как первый доступ вызываемого абонента обычно кэширует результат обратно в регистр.

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

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

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

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

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

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

Другие вопросы по тегам