2

Sorry for the previous non-descriptive question. Please allow me to rephrase the question again:

The setup:

I need to do ADD and some bit wise operations of 4 32-bit values from 4 arrays at the same time using SSE. All the element in these 4 arrays are integer size (32 bit). The result goes to the 5th array.

So my question is:

  1. What header files and compiler flags do I need to include such that I can run the SSE using C?
  2. Does the example code provide by Paul still work?

Another question, if I need to read last bit from integer A, and first bit from integer B, and replace the last bit and first bit in integer C by the values I just read, can I use SSE here? Or is there any fast way to do it? Instead of 3 access in normal case?

Code provided by Paul

#include <stdint.h>
#include <emmintrin.h>

const size_t N = 4096;  // size of input/output arrays

int32_t array0[N];      // 4 x input arrays
int32_t array1[N];
int32_t array2[N];
int32_t array3[N];
int32_t array_sum[N];   // output array

for (size_t i = 0; i < N; i += 4)
{
    __m128i v0 = _mm_load_si128(&array0[i]); // load 4 x vectors of 4 x int
    __m128i v1 = _mm_load_si128(&array1[i]);
    __m128i v2 = _mm_load_si128(&array2[i]);
    __m128i v3 = _mm_load_si128(&array3[i]);
    __m128i vsum = _mm_add_epi32(v0, v1);    // sum vectors
    __m128i vsum = _mm_add_epi32(vsum, v2);
    __m128i vsum = _mm_add_epi32(vsum, v3);
    _mm_store_si128(&array_out[i], vsum);    // store sum
}
Alex I
  • 19,689
  • 9
  • 86
  • 158
fiftyplus
  • 561
  • 10
  • 18
  • 2
    Note that there is no guarantee that the arrays are aligned correctly for `_mm_load_si128()`, which requires that its operand be 16-byte aligned. Most compilers will align `int32_t` to only 4-byte boundaries. – Dietrich Epp Nov 29 '12 at 22:08
  • `_mm_loadu_si128` allows you to load from unaligned memory. You wan't need `__declspec(align(16))` or `__attribute__((aligned(16))` on the arrays. – jww Jun 15 '18 at 21:51

2 Answers2

9

The code you have will indeed work, with some slight changes.

#include <stdint.h>
#include <emmintrin.h>

const size_t N = 4096;  // size of input/output arrays

__declspec(align(16)) int32_t array0[N];      // 4 x input arrays
__declspec(align(16)) int32_t array1[N];
__declspec(align(16)) int32_t array2[N];
__declspec(align(16)) int32_t array3[N];
__declspec(align(16)) int32_t array_sum[N];   // output array

for (size_t i = 0; i < N; i += 4)
{
    __m128i v0 = _mm_load_si128(&array0[i]); // load 4 x vectors of 4 x int
    __m128i v1 = _mm_load_si128(&array1[i]);
    __m128i v2 = _mm_load_si128(&array2[i]);
    __m128i v3 = _mm_load_si128(&array3[i]);
    __m128i vsum = _mm_add_epi32(v0, v1);    // sum vectors
    __m128i vsum = _mm_add_epi32(vsum, v2);
    __m128i vsum = _mm_add_epi32(vsum, v3);
    _mm_store_si128(&array_sum[i], vsum);    // store sum
}

When it comes to working with intrinsics, I like including <immintrin.h> and compiling with gcc -march=native. This gives access to all the instruction set extensions actually available on the current hardware.

On the other question, yes you can certainly do that, but it is only efficient if you do it on arrays of integers A, B, and C.

Example:

__declspec(align(16)) int32_t A[N]; // input arrays
__declspec(align(16)) int32_t B[N];
__declspec(align(16)) int32_t C[N];
__declspec(align(16)) int32_t R[N]; // output array

__m128i* As = (__m128i*)A; // cast them to SSE type, avoids separate load/store calls later
__m128i* Bs = (__m128i*)B;
__m128i* Cs = (__m128i*)C;
__m128i* Rs = (__m128i*)R;

__m128i A_mask = _mm_set1_epi32(1<<31); // select these bits from A, B, and C
__m128i B_mask = _mm_set1_epi32(1);
__m128i C_mask = _mm_set1_epi32(0xffffffff ^ ( 1<<31 | 1 ));

for (size_t i = 0; i < N / 4; i ++)
{
    __m128i a = _mm_and_si128(A_mask, As[i]);
    __m128i b = _mm_and_si128(B_mask, Bs[i]);
    __m128i c = _mm_and_si128(C_mask, Cs[i]);
    Rs[i] = _mm_or_si128( _mm_or_si128(a, b), c );
}

Aliasing the int32_t arrays to __m128i as I have done above is not more efficient, it should compile to the exact same code if the compiler is decent, but it results in much less verbose code. Recommended :)

Alex I
  • 19,689
  • 9
  • 86
  • 158
  • 1
    Your first code block is missing `(const __m128i*)&array0[i]` casts on the loads, and a similar cast on the stores. Also, C11 provides a portable `_Alignas(16)` equivalent for MSVC `_declspec` / GNU C `__attribute__`. (`#include ` to allow `alignas(16)`.) Otherwise yes, good examples. – Peter Cordes Jul 27 '21 at 09:24
2

To compile SSE code with gcc you just need the -msse2 flag, e.g.

$ gcc -Wall -O3 -msse2 foo.c -o foo

As for the other requirements, it would probably be best if you could post working (scalar) example code for what you want to vectorize, as it's not very clear from the above description.

Paul R
  • 208,748
  • 37
  • 389
  • 560