С # структуры / классы стека / управления кучей?

так что в C ++ это очень просто. вы хотите, чтобы какой-либо класс / структура был размещен в куче, используйте new. если вы хотите, чтобы он был в стеке, не используйте новый.

в C # мы всегда используем ключевое слово new, и в зависимости от того, является ли это структурой или классом, он выделен либо в стеке, либо в куче (структуры переходят в стек, классы в кучу), а в некоторых приложениях может быть ОГРОМНАЯ разница в производительности при изменении дизайна, когда в кучу попадают только те объекты, которые действительно ей принадлежат.

Интересно, есть ли прямой способ контролировать, где выделяется объект, независимо от того, объявлен ли он как структура или класс? Я знаю, что типы значений (структуры) могут быть помещены в коробку, чтобы перейти в кучу (но упаковка / распаковка происходит за счет производительности). есть ли способ разместить классы в стеке?

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

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

Любые советы / подсказки приветствуются :)

изменить: пример производительности:

struct Foo1
{
    public int i;
    public float f;
    public double d;
}

struct Foo2
{
   public Foo1[] bar;

   public void Init(){
        bar = new Foo1[100];
        for (int i = 0; i < 100; i++)
            bar[i] = new Foo1();
    }
}

class Program
{
    static void Main(string[] args)
    {
        DateTime time = DateTime.Now;
        Foo2[] arr = new Foo2[1000000];
        for (int i = 0; i < 1000000; i++)
        {
            arr[i] = new Foo2();
            arr[i].Init();
        }

        Console.WriteLine((DateTime.Now - time).TotalMilliseconds);
    }
}

На моем компьютере это занимает 1,8 секунды (обратите внимание, что на самом деле происходит только выделение - без передачи параметров)

если Foo1 изменяется со структуры на класс, выполнение занимает 8,9 секунды! это в пять раз медленнее


person Mat    schedule 08.01.2010    source источник
comment
Что заставляет вас поверить в огромную разницу в производительности между размещением объектов в стеке и в куче?   -  person LBushkin    schedule 09.01.2010
comment
Я заметил, когда реализовал физический решатель для 3D-игры - я мог резко оптимизировать производительность, только осторожно меняя вещи, связанные с тем, где размещаются объекты и как объекты передаются в функциях.   -  person Mat    schedule 09.01.2010
comment
Вы знаете, что стек и куча - это, по сути, один и тот же блок памяти (не считая кеширования), распределение зависит от того, на какой указатель вы ссылаетесь как на базовый. Извините, чтобы было ясно, классический стек / куча, по сути, один и тот же блок памяти   -  person GrayWizardx    schedule 09.01.2010
comment
вы хотите сказать, что выделение памяти в куче так же дешево, как и выделение памяти в стеке? это не правда   -  person Mat    schedule 09.01.2010
comment
Для C # семантика распределения для скорости менее важна: stackoverflow.com/questions/477101/   -  person GrayWizardx    schedule 09.01.2010
comment
он пришел к выводу, что разработчикам все равно. однако - во время разработки игры XNA для XBox я заметил, что это может иметь большое значение, если вам действительно не все равно.   -  person Mat    schedule 09.01.2010
comment
Я добавил пример, показывающий разницу в производительности   -  person Mat    schedule 09.01.2010
comment
+1 @Mat: Хотя я думаю, что ваш страх неуместен, я многому научился из ответов в этой ветке.   -  person Jim G.    schedule 09.01.2010
comment
Мэт, вчера проглядел, что вы используете XNA. Возможно, вы захотите прочитать этот вопрос: http://stackoverflow.com/questions/1582754/does-using-a-delegate-create-garbage   -  person Henk Holterman    schedule 09.01.2010
comment
ну, проблема не связана с тем, что сборщик мусора по-другому реализован на Xbox, поскольку эти различия в производительности применимы также к XNA / обычному коду C # в Windows   -  person Mat    schedule 09.01.2010


Ответы (5)


Хотя в общем случае верно, что объекты всегда выделяются в куче, C # позволяет вам опуститься до уровня указателя для тяжелого взаимодействия или для очень важного кода с очень высокой производительностью.

В блоках unsafe можно использовать stackalloc, чтобы размещать объекты в стеке и использовать их в качестве указателей.

Процитирую их пример:

// cs_keyword_stackalloc.cs
// compile with: /unsafe
using System; 

class Test
{
   public static unsafe void Main() 
   {
      int* fib = stackalloc int[100];
      int* p = fib;
      *p++ = *p++ = 1;
      for (int i=2; i<100; ++i, ++p)
         *p = p[-1] + p[-2];
      for (int i=0; i<10; ++i)
         Console.WriteLine (fib[i]);
   }
}

Однако обратите внимание, что вам не нужно объявлять небезопасным весь метод, вы можете просто использовать для этого блок unsafe {...}.

person Blindy    schedule 08.01.2010
comment
Обратите внимание, что для этого требуются некоторые особые разрешения, поэтому, если вы запустите свой код в среде, не соответствующей условиям полного доверия, у вас могут возникнуть проблемы. Примером этого является Silverlight. - person Blindy; 09.01.2010
comment
Вы имели в виду, что в общем случае объекты всегда размещаются в куче? - person G-Wiz; 09.01.2010
comment
В качестве примечания: причина, по которой нет способа сделать это наоборот - своего рода размещение new для классов C # - заключается в том, что классы должны отслеживаться GC, а также все, на что они ссылаются, и это не Невозможно реализовать (во всяком случае, эффективно) с любым произвольным блоком памяти. Итак, с классами вы застряли в куче. Кроме того, трюк, описанный в этом ответе, не будет работать для структур, которые имеют поля ссылочных типов (опять же, потому что GC должен иметь возможность отслеживать их). Однако поля типов указателей хороши, поэтому они столь же выразительны, как и простой ANSI C. - person Pavel Minaev; 09.01.2010
comment
почему они должны отслеживаться GC? если я выделяю что-то в стеке, я нормально отношусь к тому, что он освобождается, когда он выходит за пределы области видимости - или если он используется в массиве, и, следовательно, объекты выделяются непрерывно в памяти, тогда я в порядке с объектами, которые удаляется, как только массив удаляется - person Mat; 09.01.2010
comment
@Blindy: как можно - используя этот небезопасный синтаксис - определить переменную массива CLASS Foo: - Foo [] myArr; - таким образом, чтобы он был эквивалентен следующему синтаксису C ++: Foo * myArr = new Foo [100]? (в отличие от Foo ** myArr = new Foo * [100], что C # делает по умолчанию) - person Mat; 09.01.2010
comment
Лучшее, что вы можете сделать, это Foo *myArr=stackalloc Foo[100]; - person Blindy; 09.01.2010
comment
Причина, по которой его необходимо отслеживать с помощью GC, заключается в том, что .NET гарантирует безопасность памяти, что, среди прочего, означает отсутствие зависших ссылок. В C ++, если вы создаете объект в стеке, получаете указатель на него, сохраняете его по истечении времени жизни объекта, а затем используете его - ну, это U.B., но среда выполнения вас не остановит. .NET специально разработан, чтобы избежать подобных вещей. Следовательно, он должен отслеживать ссылки, чтобы гарантировать, что объект не упоминается до его освобождения (в качестве оптимизации, если он может статически доказать, что объект не указан - анализ выхода - он может пропустить GC и использовать куча). - person Pavel Minaev; 11.01.2010
comment
stakalloc лучше всего оставить как JIT-оптимизацию. - person Henk Holterman; 21.01.2010

Ваше объяснение того, куда идут типы значений и ссылочные типы (стек против кучи), не совсем правильно.

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

Вам следует прочитать http://www.yoda.arachsys.com/csharp/memory.html, чтобы лучше понять, где на самом деле размещены разные типы.

Отдельно отметим, что в .Net вам действительно не нужно заботиться о том, где размещены типы - как пишет Эрик Липперт: стек - это деталь реализации. Вам лучше понять семантику того, как передаются типы (по значению, по ссылке и т. Д.).

Более того, вы, кажется, подразумеваете, что размещение объекта в куче дороже, чем в стеке. На самом деле, я бы сказал, что затраты на производительность при копировании типов значений перевешивают выгоду от любой экономии за счет немного более быстрого распределения в стеке. Самая большая разница между стеком и кучей заключается в том, что на большинстве архитектур ЦП стек с большей вероятностью будет сохраняться в кеше ЦП - и, таким образом, избежать промахов кеша.

Это не самая важная проблема, которую нужно решать. Вы должны решить, должен ли тип иметь семантику передачи по значению или нет. Если нет - то, возможно, это должен быть ссылочный тип.

person LBushkin    schedule 08.01.2010
comment
скажем, вы создаете большой массив некоторого объектного типа. Сам массив все равно попадет в кучу. однако, насколько я понимаю, если тип объекта является структурой, все это будет размещено в непрерывной памяти (один кластер данных - или, возможно, несколько разделений в зависимости от реализации) в куче. однако - если это класс, каждый элемент самого массива также будет размещен в куче, что займет больше времени для выделения и замедлит сборку мусора - person Mat; 09.01.2010
comment
@Mat: тот факт, что массив размещен в куче, является деталью реализации. Однако тот факт, что это массив ссылок (то есть указатели, для нас, ребята из C ++), не является. - person Pavel Minaev; 09.01.2010
comment
@Pavel - именно это я имею в виду (меня не волнует, находится ли сам массив в куче, поскольку одно выделение в куче не повредит так же сильно, как одно выделение для каждого элемента) - поэтому я думаю, что желательно определять поведение ссылочного типа / типа значения при использовании, а не при объявлении класса / структуры - person Mat; 09.01.2010
comment
Статья о памяти исчезла в 2019 году, теперь ее можно найти по адресу: https://web.archive.org/web/20190124144928/http://www.yoda.arachsys.com/csharp/memory.html. - person Loring; 01.07.2021

Пусть вас не вводит в заблуждение ключевое слово new, оно необязательно для структур.

В C # есть управляемый мир, в котором вы наслаждаетесь сборщиком мусора и безопасностью типов и не беспокоитесь (не должны) о многих деталях памяти. Разница в стеке и куче не имеет значения, дело в семантике копирования.

Для тех редких случаев, когда вам нужен контроль, есть небезопасная (неуправляемая) часть C # с реальными указателями и всем остальным.

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

person Henk Holterman    schedule 08.01.2010
comment
но есть заметная разница в производительности во времени выделения для структур и классов, поэтому было бы неплохо решить, где выделить во время выделения, а не при написании фактического класса - person Mat; 09.01.2010
comment
Что вы имеете в виду под _1 _... необязательно для структур? В каком контексте? - person Pavel Minaev; 09.01.2010
comment
Я думаю, он имеет в виду, что для структур автоматически вызывается конструктор по умолчанию, также без ключевого слова new. однако для классов переменная будет нулевым указателем до тех пор, пока конструктор не будет явно вызван (с использованием new) - person Mat; 09.01.2010
comment
Мэт: заметная разница в производительности ... не (в равной степени) верно для управляемого мира. Проверьте свои предположения. - person Henk Holterman; 09.01.2010
comment
Павел: не нужно строить структуру с новым. Но без него в игру вступает отслеживание назначений в C #. - person Henk Holterman; 09.01.2010
comment
Mat, re автоматически вызывается конструктор по умолчанию: Нет, это не так. - person Henk Holterman; 09.01.2010
comment
@Henk: правда не понимаю, что ты имеешь в виду? если одна вещь занимает в 5 раз больше времени, чем другая, я бы сказал, что это заметная разница в производительности, независимо от того, управляемый код это или нет. - person Mat; 09.01.2010
comment
@Henk. Нет? но структура автоматически инициализируется нулем, и это выполняется конструктором по умолчанию (без параметров) - person Mat; 09.01.2010
comment
Mat, инициализируются только field-of-struct, а не структурная переменная. И у структур есть преимущество при использовании в массивах, но ваш тест сравнивает яблоки и апельсины. - person Henk Holterman; 09.01.2010
comment
Думаю, вы правы насчет инициализации, но мой тест сравнивает структуры с классами, а не яблоки и апельсины. Допустим, вы бы написали структуру, содержащую несколько сотен байтов полей. если вы думаете только о семантике копирования, вы, скорее всего, объявите ее как класс. Но если вы знаете, что собираетесь использовать его в контексте, как в моем примере, использование структуры было бы гораздо более производительным. Проблема в том, что вы не всегда можете знать об этом, когда пишете свой класс / структуру. Вот почему желательно определять такое поведение, когда вы его используете. Нет? - person Mat; 09.01.2010

Не беспокойтесь об этом - ваша голова все еще находится в мире c / c ++, где имеет большое значение, где все идет. В команде CLR есть группа действительно умных людей, которые целыми днями беспокоятся о том, как сделать это волшебно быстро.

В C # есть некоторые ошибки, и использование памяти обычно связано с созданием большого количества крошечных объектов случайно (выполнение строки = строка + другая строка в цикле является классикой)

Существует мем-профиль, который покажет вам, что происходит, если вы действительно думаете, что у вас проблема с производительностью, вызванная управлением памятью.

Я написал много высокопроизводительного кода (клиенты графического рендеринга, сетевые серверы) на C #, и мне никогда не приходилось беспокоиться об этом.

person pm100    schedule 08.01.2010
comment
но особенно в графическом рендеринге, это имеет большое значение. например, система частиц будет намного медленнее, если каждая частица (даже если она хранится в массиве и повторно используется после смерти) выделяется сама по себе в куче, а не в одной группе памяти. - person Mat; 09.01.2010
comment
создавать большой массив структур частиц (как указывали другие плакаты, вы можете размещать структуры повсюду, а не только стек). Вы получите один большой непрерывный блок памяти. - person pm100; 09.01.2010
comment
точно - но дело в том, что это решение нужно принимать при написании кода для частицы. если вы хотите использовать какой-либо класс из библиотеки, у вас нет возможности разместить его в одном непрерывном блоке - person Mat; 09.01.2010

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

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

person Niall    schedule 08.01.2010
comment
у меня есть неплохой контроль над семантикой копирования независимо от того, структура это или класс. Я могу использовать deepCopy / shallowCopy для копирования для ссылочных типов, я могу использовать упаковку / распаковку, чтобы получить ссылочную семантику для valuetype, я могу использовать ключевое слово ref, чтобы принимать типы значений по ссылке в качестве параметра функции. Но решение о том, где разместить переменную, также может быть важно для производительности. - person Mat; 09.01.2010
comment
Я не говорю, что выбор между классом и структурой - единственный способ контролировать семантику копирования. Я говорю, что класс / структура касается только семантики копирования, а не политики распределения. Вы можете изменить производительность по сравнению с конкретной средой выполнения, убедив ее выделить другое, но это зависит от реализации. Что нормально, если вы оптимизируете производительность. Однако случаи, когда это действительно имеет значение, вероятно, реже, чем вы думаете. - person Niall; 09.01.2010
comment
но это был мой вопрос - моя проблема в том, что у меня есть инструменты для убеждения в семантике копирования, но у меня нет инструментов для убеждения в поведении распределения. И этот класс / структура не касается политики распределения, неверно - например, прочтите эту статью msdn: msdn.microsoft.com/en-us/library/aa288471 (VS.71) .aspx Когда вы вызываете оператор New для класса, он будет размещен в куче. Однако когда вы создаете экземпляр структуры, она создается в стеке. Это даст прирост производительности. - person Mat; 09.01.2010
comment
Независимо от того, является ли это верным советом, он никоим образом не пытается ответить на его вопрос. - person Chad Schouggins; 28.05.2014