Почему Visual Studio не оптимизирует структуры для лучшего использования памяти?
Мой вопрос: почему компилятор Visual Studio 2012 не автоматически переупорядочивает элементы структуры для лучшего использования памяти? Компилятор, похоже, хранит элементы в том порядке, в котором они объявлены в определении структуры, с некоторыми пустыми отступами, необходимыми для выравнивания элементов. Кажется, что переупорядочение было бы более желательным способом выравнивания элементов, чем заполнение, когда это возможно. Есть ли причина, по которой он должен храниться в памяти в порядке объявления?
Соответствующие детали следуют;
У меня есть структура, которая представляет один элемент в том, что будет большой массив. Элемент имеет несколько членов, некоторые 32-битные, некоторые 64-битные. Я настроил выравнивание членов структуры по умолчанию для лучшей производительности.
Я исследовал память в режиме отладки и обнаружил, что значительный процент памяти теряется. Я проследил проблему до того, как члены Stuct были выровнены в памяти. Я знаю, что 32-битные члены должны быть выровнены по границам DWORD для лучшей производительности, и, похоже, 64-битные члены должны быть выровнены по границам QWORD (я бы подумал, что границы DWORD были бы адекватными)
Я смог решить проблему, изменив порядок, в котором я перечислял членов в определении структуры. Я старался по возможности последовательно размещать 2 32-битных члена, чтобы не требовалось заполнения для запуска следующего 64-битного члена на границе QWORD.
4 ответа
Данные, которые находятся в стандартной структуре или классе макета, должны давать определенные гарантии макета. Среди прочего, если есть другая стандартная структура или класс макета, который является префиксом первого, вы должны иметь возможность интерпретировать одну структуру как другую, и общий префикс должен согласиться.
Это в основном заставляет порядок памяти стандартных структур размещения быть в том порядке, в котором вы их объявляете.
Это похоже на то, что C требует в плане структуры структуры, как описано здесь.
Теперь в C++ есть некоторая свобода для нестандартных структур размещения.
[expr.rel] / 3 подпункт 3:
Если два указателя указывают на разные нестатические элементы данных одного и того же объекта или субобъекты таких элементов, рекурсивно, указатель на более поздний объявленный элемент сравнивается больше при условии, что оба элемента имеют одинаковый контроль доступа (пункт 11) и предоставляют их класс это не союз.
Порядок элементов должен поддерживаться в публичных / частных / защищенных доменах контроля доступа. Пространство между элементами может быть добавлено практически произвольным образом.
Это означает, что вы можете знать, что &this->x
больше или меньше чем &this->y
, которые могут использовать некоторые программисты.
Согласно правилу "как будто", если никто никогда не получит адрес таких данных, компилятор может изменить их порядок. Это трудно доказать в обычных моделях компиляции.
Интервал между элементами в MSVC совпадает с интервалом в простых старых структурах данных, по моему опыту, за исключением наследования виртуальной игры. Совместимость компоновки (вне стандарта) важна для стабильного ABI, и код, скомпилированный с одной версией компилятора, предпочтительнее для работы с другой. Нарушение, которое имеет цену.
Программисты C++ могут переупорядочивать структуры данных по мере необходимости, а Visual Studio предоставляет #pragma
s, чтобы изменить правила упаковки структуры, так что если вам действительно нужен последний бит производительности, вы можете его получить.
Вы даже можете написать tuple
-подобная структура данных, которая гарантирует оптимальную упаковку, если вам это нужно. (Я бы не стал полагаться на std::tuple
так как не имеет гарантий упаковки)
Это стандарт C++, компиляторы не могут изменять порядок полей, возможно, потому, что программисты могут захотеть получить доступ к полям через указатель на первое поле. Посмотрите на эту статью, если вам нужно сделать переупорядочение самостоятельно
Раздел 9.2.13:
Нестатические члены данных (не объединяющего) класса с одинаковым контролем доступа (раздел 11) распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных с различным контролем доступа не определен (раздел 11). Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями (10.3) и виртуальными базовыми классами (10.1).
Без #pragma
s, память не упакована C++ и не переупорядочена, потому что язык гарантирует компоновку, совместимую с кодом. Представьте себе хаос, который может быть вызван - отображение структур на файлы (файлы с отображением в памяти) или на оборудование никогда не сработает.
Чтобы получить представление о макете класса или структуры, Visual C++ предоставляет недокументированный параметр командной строки /d1reportSingleClassLayout
которая нарисует вам ASCII-арт-схему структуры памяти вашего класса / структуры, включая все члены, базовые члены и vtable. Если у вас есть класс под названием foo
например добавить /d1reportSingleClassLayoutfoo
в командной строке вашего компилятора.
Я подозреваю, что это на пересечении накладывающихся требований.
- Макет для одной и той же структуры POD в C и C++ должен быть двоично-совместимым. (Требуется ли это стандартом, я не знаю, но большинство поставщиков компиляторов, вероятно, отдают ему приоритет, потому что от него зависит большое количество существующего кода.)
- Структуры и классы в C++ - это одно и то же, за исключением видимости по умолчанию.
- Члены данных класса создаются в порядке их объявления и уничтожаются в обратном порядке.
Если компилятор должен был переупорядочить элементы данных для лучшего выравнивания и / или более плотной упаковки, должно ли это изменить порядок, в котором строится / уничтожается порядок? Нет, это сломало бы много кода, который полагается на RAII. Но теперь доступ к памяти во время построения менее упорядочен, что может фактически быть пессимизацией, в зависимости от поведения кэша, размера структуры и частоты, с которой эти структуры создаются.
Можно утверждать, что эти проблемы не относятся к структурам POD, но требования 1 и 2 говорят, что компилятор C++ должен планировать структуры POD так же, как классы (и наоборот).