Выравнивание памяти классов в С#?

(кстати, это относится к 32-разрядной ОС)

НЕКОТОРЫЕ ОБНОВЛЕНИЯ:

  • Это определенно проблема выравнивания

  • Иногда выравнивание (по какой-либо причине?) настолько плохо, что доступ к двойнику более чем на 50 раз медленнее, чем его быстрый доступ.

  • Запуск кода на 64-битной машине разрешает проблему, но я думаю, что он все еще чередовался между двумя моментами (из которых я мог получить аналогичные результаты, изменив double на float на 32-битной машине)

  • Запуск кода под моно не вызывает проблем - Microsoft, любой шанс, что вы можете скопировать что-то из тех ребят Novell???


Есть ли способ памяти выровнять распределение классов в С#?

Следующее демонстрирует (я думаю!), что плохое совпадение удваивается правильно. Он выполняет некоторую простую математику в двойном хранилище, хранящуюся в классе, каждый раз при каждом запуске запускает 5 временных циклов переменной, прежде чем выделять новую и снова ее выполнять.

В основном результаты выглядят так, как если бы вы имели быструю, среднюю или медленную позицию памяти (на моем старом процессоре, они заканчиваются примерно 40, 80 или 120 мс за каждый ход)

Я пробовал играть с StructLayoutAttribute, но не имел радости - может быть, что-то еще происходит?

class Sample
{
    class Variable { public double Value; }

    static void Main()
    {
        const int COUNT = 10000000;
        while (true)
        {
            var x = new Variable();
            for (int inner = 0; inner < 5; ++inner)
            {
                // move allocation here to allocate more often so more probably to get 50x slowdown problem
                var stopwatch = Stopwatch.StartNew();

                var total = 0.0;
                for (int i = 1; i <= COUNT; ++i)
                {
                    x.Value = i;
                    total += x.Value;
                }
                if (Math.Abs(total - 50000005000000.0) > 1)
                    throw new ApplicationException(total.ToString());

                Console.Write("{0}, ", stopwatch.ElapsedMilliseconds);
            }
            Console.WriteLine();
        }
    }
}

Итак, я вижу много веб-страниц о выравнивании структур для взаимодействия, так что насчет выравнивания классов?

(Или мои предположения ошибочны, и есть еще одна проблема с этим?)

Спасибо, Пол.

Ответы

Ответ 1

Интересный взгляд на механизмы, которые запускают машину. У меня есть небольшая проблема, объясняющая, почему существует несколько различных значений (я получил 4), когда двойной можно выровнять только двумя способами. Я думаю, что выравнивание к линии кэша CPU также играет роль, хотя это добавляет до 3 возможных таймингов.

Ну, вы ничего не можете с этим сделать, выравнивание CLR только promises для 4 байтовых значений, чтобы гарантировать, что атомные обновления на 32-битных машинах гарантированы. Это не просто проблема с управляемым кодом, У C/С++ тоже есть эта проблема. Похоже, производители чипов должны решить эту проблему.

Если это важно, вы можете выделить неуправляемую память с помощью Marshal.AllocCoTaskMem() и использовать небезопасный указатель, который вы можете выровнять в нужном направлении. То же самое, что вам нужно сделать, если вы выделяете память для кода, который использует инструкции SIMD, для этого требуется выравнивание по 16 байт. Подумайте, что это отчаяние-движение.

Ответ 2

Чтобы доказать концепцию несоосности объектов в куче в .NET, вы можете запустить следующий код, и вы увидите, что теперь он всегда работает быстро. Пожалуйста, не стреляйте в меня, это просто PoC, но если вас действительно беспокоит производительность, вы можете подумать об использовании его;)

public static class AlignedNew
{
    public static T New<T>() where T : new()
    {
        LinkedList<T> candidates = new LinkedList<T>();
        IntPtr pointer = IntPtr.Zero;
        bool continue_ = true;

        int size = Marshal.SizeOf(typeof(T)) % 8;

        while( continue_ )
        {
            if (size == 0)
            {
                object gap = new object();
            }

            candidates.AddLast(new T());

            GCHandle handle = GCHandle.Alloc(candidates.Last.Value, GCHandleType.Pinned);
            pointer = handle.AddrOfPinnedObject();
            continue_ = (pointer.ToInt64() % 8) != 0 || (pointer.ToInt64() % 64) == 24;

            handle.Free();

            if (!continue_)
                return candidates.Last.Value;
        }

        return default(T);
    }
}

class Program
{

    [StructLayoutAttribute(LayoutKind.Sequential)]
    public class Variable
    {
        public double Value;
    }

    static void Main()
    {

        const int COUNT = 10000000;

        while (true)
        {

            var x = AlignedNew.New<Variable>();


            for (int inner = 0; inner < 5; ++inner)
            {

                var stopwatch = Stopwatch.StartNew();

                var total = 0.0;
                for (int i = 1; i <= COUNT; ++i)
                {
                    x.Value = i;
                    total += x.Value;
                }
                if (Math.Abs(total - 50000005000000.0) > 1)
                    throw new ApplicationException(total.ToString());


                Console.Write("{0}, ", stopwatch.ElapsedMilliseconds);
            }
            Console.WriteLine();
        }

    }
}

Ответ 4

Использование struct вместо класса делает постоянную времени. также рассмотрите использование StructLayoutAttribute. Это помогает указать точное расположение макетов структуры. Для CLASSES я не думаю, что у вас есть какие-то гарантии того, как они отображаются в памяти.

Ответ 5

У вас нет никакого контроля над тем, как .NET предоставляет ваш класс в памяти.

Как утверждают другие, StructLayoutAttribute может использоваться для принудительного создания определенного макета памяти для структуры НО > , что цель этого - для взаимодействия C/С++, а не для точной настройки производительность вашего приложения .NET.

Если вы беспокоитесь о проблемах выравнивания памяти, то С#, вероятно, является неправильным выбором языка.


EDIT - сломал WinDbg и посмотрел кучу, запускающую код выше на 32-битной Vista и .NET 2.0.

Примечание. Я не получаю изменения в таймингах, показанных выше.

0:003> !dumpheap -type Sample+Variable
 Address       MT     Size
01dc2fec 003f3c48       16     
01dc54a4 003f3c48       16     
01dc58b0 003f3c48       16     
01dc5cbc 003f3c48       16     
01dc60c8 003f3c48       16     
01dc64d4 003f3c48       16     
01dc68e0 003f3c48       16     
01dc6cd8 003f3c48       16     
01dc70e4 003f3c48       16     
01dc74f0 003f3c48       16     
01dc78e4 003f3c48       16     
01dc7cf0 003f3c48       16     
01dc80fc 003f3c48       16     
01dc8508 003f3c48       16     
01dc8914 003f3c48       16     
01dc8d20 003f3c48       16     
01dc912c 003f3c48       16     
01dc9538 003f3c48       16     
total 18 objects
Statistics:
      MT    Count    TotalSize Class Name
003f3c48       18          288 TestConsoleApplication.Sample+Variable
Total 18 objects
0:003> !do 01dc9538 
Name: TestConsoleApplication.Sample+Variable
MethodTable: 003f3c48
EEClass: 003f15d0
Size: 16(0x10) bytes
 (D:\testcode\TestConsoleApplication\bin\Debug\TestConsoleApplication.exe)
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
6f5746e4  4000001        4        System.Double  1 instance 1655149.000000 Value

Мне кажется, что адреса распределения классов, по-видимому, выровнены, если я не читаю это неправильно?

Ответ 6

Он будет правильно выровнен, иначе вы получите исключения выравнивания на x64. Я не знаю, что показывает ваш фрагмент, но я бы ничего не сказал о его выравнивании.