С# structs/classes stack/heap control?

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

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

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

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

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

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

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

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 изменен с struct на класс, выполнение занимает 8,9 секунды! что в пять раз медленнее

Ответы

Ответ 1

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

В блоках 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 {...}.

Ответ 2

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

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

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

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

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

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

Ответ 3

Не обманывайтесь ключевым словом new, это необязательно для структур.

В С# есть управляемый мир, где вам нравится сборщик мусора и Type-Safety, и не нужно беспокоиться о многих деталях памяти. Разница между стеком/кучей не имеет значения, это о семантике копирования.

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

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

Ответ 4

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

Есть некоторые gotchas в С#, а использование памяти обычно связано с созданием большого количества мелких объектов по авариям (выполнение string = string + other string в цикле является классическим)

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

В С# я написал много интенсивного кода производительности (клиенты рендеринга графики, сетевые серверы) и никогда не беспокоился об этом

Ответ 5

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

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