2016-11-07 1 views
1
class Wrapper { 
public: 
    // some functions operating on the value_ 
    __m128i value_; 
}; 

int main() { 
    std::vector<Wrapper> a; 
    a.resize(100); 
} 

ли атрибут из Wrapper объектов в vector a всегда value_ занимает непрерывную память без каких-либо зазоров между __m128i values?Имеет ли std :: vector <Simd_wrapper> непрерывные данные в памяти?

Я имею в виду:

[128 bit for 1st Wrapper][no gap here][128bit for 2nd Wrapper] ... 

До сих пор, это, кажется, верно для г ++ и процессор Intel я использую, и GCC godbolt.

Поскольку существует только один атрибут __m128i в Wrapper объекта, то это значит компилятор всегда не нужно добавлять любые дополнения в памяти? (Memory layout of vector of POD objects)

код Тест 1:

#include <iostream> 
#include <vector> 
#include <x86intrin.h> 

int main() 
{ 
    static constexpr size_t N = 1000; 
    std::vector<__m128i> a; 
    a.resize(1000); 
    //__m128i a[1000]; 
    uint32_t* ptr_a = reinterpret_cast<uint32_t*>(a.data()); 
    for (size_t i = 0; i < 4*N; ++i) 
    ptr_a[i] = i; 
    for (size_t i = 1; i < N; ++i){ 
    a[i-1] = _mm_and_si128 (a[i], a[i-1]); 
    } 
    for (size_t i = 0; i < 4*N; ++i) 
    std::cout << ptr_a[i]; 
} 

Предупреждение:

warning: ignoring attributes on template argument 
'__m128i {aka __vector(2) long long int}' 
[-Wignored-attributes] 

Ассамблея (gcc god bolt):

.L9: 
     add  rax, 16 
     movdqa xmm1, XMMWORD PTR [rax] 
     pand xmm0, xmm1 
     movaps XMMWORD PTR [rax-16], xmm0 
     cmp  rax, rdx 
     movdqa xmm0, xmm1 
     jne  .L9 

Я предполагаю, что это означает, что данные смежный, так как петли просто добавьте 16 байт в адрес памяти, который он читает в каждом цикле loo п. Он использует pand для поразрядного и.

код Тест 2:

#include <iostream> 
#include <vector> 
#include <x86intrin.h> 
class Wrapper { 
public: 
    __m128i value_; 
    inline Wrapper& operator &= (const Wrapper& rhs) 
    { 
     value_ = _mm_and_si128(value_, rhs.value_); 
    } 
}; // Wrapper 
int main() 
{ 
    static constexpr size_t N = 1000; 
    std::vector<Wrapper> a; 
    a.resize(N); 
    //__m128i a[1000]; 
    uint32_t* ptr_a = reinterpret_cast<uint32_t*>(a.data()); 
    for (size_t i = 0; i < 4*N; ++i) ptr_a[i] = i; 
    for (size_t i = 1; i < N; ++i){ 
    a[i-1] &=a[i]; 
    //std::cout << ptr_a[i]; 
    } 
    for (size_t i = 0; i < 4*N; ++i) 
    std::cout << ptr_a[i]; 
} 

Ассамблея (gcc god bolt)

.L9: 
     add  rdx, 2 
     add  rax, 32 
     movdqa xmm1, XMMWORD PTR [rax-16] 
     pand xmm0, xmm1 
     movaps XMMWORD PTR [rax-32], xmm0 
     movdqa xmm0, XMMWORD PTR [rax] 
     pand xmm1, xmm0 
     movaps XMMWORD PTR [rax-16], xmm1 
     cmp  rdx, 999 
     jne  .L9 

Похоже, без заполнения тоже. rax увеличивается на 32 в каждом шаге, и что 2 х 16. Это дополнительный add rdx,2, безусловно, не так хорошо, как цикл из тестового кода 1.

Тест авто-векторизации

#include <iostream> 
#include <vector> 
#include <x86intrin.h> 

int main() 
{ 
    static constexpr size_t N = 1000; 
    std::vector<__m128i> a; 
    a.resize(1000); 
    //__m128i a[1000]; 
    uint32_t* ptr_a = reinterpret_cast<uint32_t*>(a.data()); 
    for (size_t i = 0; i < 4*N; ++i) 
    ptr_a[i] = i; 
    for (size_t i = 1; i < N; ++i){ 
    a[i-1] = _mm_and_si128 (a[i], a[i-1]); 
    } 
    for (size_t i = 0; i < 4*N; ++i) 
    std::cout << ptr_a[i]; 
} 

Ассамблея (god bolt) :

.L21: 
     movdqu xmm0, XMMWORD PTR [r10+rax] 
     add  rdi, 1 
     pand xmm0, XMMWORD PTR [r8+rax] 
     movaps XMMWORD PTR [r8+rax], xmm0 
     add  rax, 16 
     cmp  rsi, rdi 
     ja  .L21 

... Я просто не знаю, если это всегда верно для процессоров Intel и г ++/Intel C++ компиляторы/(вставить имя компилятора здесь) ...

+1

Да, но не гарантировано правильное выравнивание – MikeMB

+0

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

+0

Это другой вопрос. Непрерывное хранение гарантировано, чрезмерное выравнивание - нет. –

ответ

1

Без надписей можно безопасно принимать на практике, если вы не компилируете для нестандартного ABI.

Все компиляторы, предназначенные для одного и того же ABI, должны сделать тот же выбор в отношении размеров/макетов структуры/класса, а все стандартные соглашения об ABI/вызовах не будут иметь дополнения в вашей структуре. (т. е. x86-32 и x86-64 System V и Windows, см. ссылку на теги ). Ваши эксперименты с одним компилятором подтверждают это для всех компиляторов, нацеленных на ту же платформу/ABI.

Обратите внимание, что область применения этого вопроса ограничена компиляторами x86, которые поддерживают встроенные функции Intel и тип __m128i, что означает, что у нас есть гораздо более надежные гарантии, чем то, что вы получаете только от стандарта ISO C++, без каких-либо конкретных реализаций.


Как @zneak указывает, вы можете static_assert(std::is_standard_layout<Wrapper>::value) в классе опр, чтобы напомнить людям не добавлять каких-либо виртуальных методов, которые бы добавить в таблицу виртуальных указатель на каждый экземпляр.

2

Нет гарантии, что в конце class Wrapper не будет прокладки, чтобы не было прокладки на , начинающемся.

Согласно C++11 стандарт:

9,2 Члены класса [ класс.MEM ]

Указатель на объект структуры стандартной компоновки, соответствующим образом преобразованы с использованием reinterpret_cast, указывает на его начальном элементе (или, если этот член битовое поле, а затем в блок, в котором он проживает) и наоборот. [Примечание. Таким образом, в рамках объекта структуры стандартного макета может быть указано неназванное заполнение, но не в его начале, по мере необходимости, для достижения соответствующего выравнивания. - конец примечание]

Также под sizeof:

5.3.3 Sizeof [ expr.sizeof ]

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

+0

Из любопытства: существует ли ** реалистичная ** вероятность того, что структура данных с одним членом, размер которого равен 2, любое дополнение? – MikeMB

+0

@MikeMB Я не изучал достаточно архитектуры «CPU», чтобы угадать догадку tbh. – Galik

+0

@MikeMB, я не вижу, чтобы это произошло. Структуры дополняются таким образом, что если вы помещаете их в массив, то первый элемент структуры выравнивается. Любые дополнительные дополнения, кроме этого, не нужны. – zneak

1

Не гарантируется. Galik's answer цитирует стандарт, поэтому я сосредоточусь на некоторых рисках предполагать, что он будет смежным.

Я написал эту небольшую программу и скомпилирован с GCC, и это положить целые смежно:

#include <iostream> 
#include <vector> 

class A 
{ 
public: 
    int a; 
    int method() { return 1;} 
    float method2() { return 5.5; } 
}; 

int main() 
{ 
    std::vector<A> as; 
    for(int i = 0; i < 10; i++) 
    { 
    as.push_back(A()); 
    } 
    for(int i = 0; i < 10; i++) 
    { 
    std::cout << &as[i] << std::endl; 
    } 
} 

Однако с одним небольшим изменением, зазоры стали появляться:

#include <iostream> 
#include <vector> 

class A 
{ 
public: 
    int a; 
    int method() { return 1;} 
    float method2() { return 5.5; } 
    virtual double method3() { return 0.1; } //this is the only change 
}; 

int main() 
{ 
    std::vector<A> as; 
    for(int i = 0; i < 10; i++) 
    { 
    as.push_back(A()); 
    } 
    for(int i = 0; i < 10; i++) 
    { 
    std::cout << &as[i] << std::endl; 
    } 
} 

объектов с виртуальным методы (или наследуемые от объектов с виртуальными методами) должны хранить немного дополнительной информации, чтобы знать, где найти соответствующий метод, потому что он не знает, какой из базового класса или любого из переопределений до выполнения. Вот почему рекомендуется never use memset on a class. Как указывают другие ответы, там может быть и прокладка, которая не гарантируется согласованностью между компиляторами или даже разными версиями одного и того же компилятора.

В конце концов, вероятно, просто не стоит предполагать, что он будет непрерывным в данном компиляторе, и даже если вы его протестируете, и это будет работать, простые вещи, такие как добавление виртуального метода позже, Головная боль.

+0

[стандартный класс макета] (http://en.cppreference.com/w/cpp/concept/StandardLayoutType), такой как OP, не может иметь vtable. Это можно проверить во время компиляции, используя 'static_assert (std :: is_standard_layout :: значение)'. – zneak

+0

@zneak Я говорю, что, хотя это может быть предсказуемо сейчас, легко забыть об этом позже и привинтить его. Если вы используете 'static_assert' для предотвращения компиляции в случае его изменения, то вы знаете, когда вам нужно исправить проблему. Но разве не легче просто не полагаться на то, что теперь это правда, тогда нужно исправить это позже, если ситуация изменится? – Cody

+1

Я бы сказал, что это не так. OP может спрашивать, потому что он хочет передать эти данные функции, написанной на сборке (которая по-прежнему относительно распространена для расчетов SIMD), но все же имеет удобство методов для отдельных элементов, когда он не находится в плотном цикле. – zneak

Смежные вопросы