Использование встроенных функций C и сложностей выравнивания памяти с классами - PullRequest
2 голосов
/ 28 октября 2009

Хорошо, я только начинаю использовать встроенные функции C в своем коде, и я создал класс, упрощенный вид которого выглядит следующим образом:

class _Vector3D
{
public:
_Vector3D() 
{
    aVals[0] = _mm_setzero_ps();
    aVals[1] = _mm_setzero_ps();
    aVals[2] = _mm_setzero_ps();
}
~_Vector3D() {}
private:
__m128 aVals[3];
};

Пока все хорошо. Но когда я создаю второй класс с членами _Vector3D, у меня возникают проблемы:

class RayPacket
{
public:
RayPacket() {orig = _Vector3D(); dir = _Vector3D(); power = _mm_setzero_ps();}
~RayPacket() {}

RayPacket(_Vector3D origins, _Vector3D directions, float pow)
{
    orig = origins;
    dir = directions;
    power = _mm_set_ps1(pow);
}

_Vector3D orig;
_Vector3D dir;
__m128 power;
};

Я получаю следующую ошибку:

ошибка C2719: «происхождение»: формальный параметр с __declspec (align ('16 ')) не будет выровнен

указывает на перегрузку конструктора:

RayPacket(_Vector3D origins, _Vector3D directions, float pow)

Так я поступаю неправильно? Должен ли я использовать структуры вместо этого или я могу заставить его работать с классами?

Ответы [ 3 ]

4 голосов
/ 28 октября 2009

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

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

Обратите внимание, что malloc() и друзья, гарантированное выравнивание, возвращаемое для блока, иногда не может обрабатывать специальные типы, подобные этому. В этом случае платформа будет иметь специальную функцию выделения для распределения этих объектов.

Подробнее о MSVC см. Ниже (http://msdn.microsoft.com/en-us/library/aa290049.aspx):

Выравнивание стека

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

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

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

Примечание. Недостатком динамического 8-байтового выравнивания стека с точки зрения производительности является то, что отсутствие указателя кадра (/ Oy) эффективно отключается. Регистр EBP должен использоваться для ссылки на стек с динамическим 8-байтовым стеком, и поэтому он не может использоваться в качестве общего регистра в функции.

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

4 голосов
/ 28 октября 2009

Этот ответ основан на документации и догадках, а не на реальных знаниях. Осторожно!

Документация для __m128 гласит:

Переменные типа _m128 [sic] автоматически выравниваются по 16-байтовым границам.

Итак, используя член __m128 в вашем классе, это заставляет компилятор выравнивать экземпляры вашего класса по 16-байтовым границам. Неявно, __declspec(align(16)) добавляется в ваш класс, но это не разрешено для параметров функции , потому что компилятору трудно (невозможно?) Принудительно выполнить выравнивание внутри фреймов стека.

В качестве обходного пути попробуйте передать аргументы конструктора по ссылке:

RayPacket(_Vector3D const &origins, _Vector3D const &directions, float pow)
0 голосов
/ 28 октября 2009

Попробуйте передать _Vector3D по константной ссылке, как в: RayPacket( const _Vector3D& origins, const _Vector3D& directions, float pow ); Это поместит указатели вместо значений в стек вызовов.

...