This code is broken
When unrolling you add all the Vector16b.
public static unsafe Vector16b operator + (Vector16b va, Vector16b vb)
Vector16b res = new Vector16b ();
byte *a = &va.v0;
byte *b = &vb.v0;
byte *c = &res.v0;
for (int i = 0; i < 16; ++i)
*c++ = (...
Restructure into a function to make it DRYer
Right now the structure of your code is pretty unpleasant, not very DRY, etc. The first thing I'd recommend is restructuring this to work in a function. I don't know for sure how your Mat4 structure is implemented, however you indicated in the comments that it is contiguous so I've based my assumptions off of ...
The biggest performance problem (for small matrices), and a common mistake, is doing this:
_mm256_extract_epi32(vec_multi, 0) + _mm256_extract_epi32(vec_multi, 1) +_mm256_extract_epi32(vec_multi, 2) +_mm256_extract_epi32(vec_multi, 3) +_mm256_extract_epi32(vec_multi, 4) +_mm256_extract_epi32(vec_multi, 5) +_mm256_extract_epi32(vec_multi, 6) +...
There are some & 0xff operations that are not necessary:
(aMask & (~iMask & 0xff)), because the bits reset by & 0xff are already zero in aMask, so they never survive the "main" &.
_mm256_movemask_ps(...) & 0xff, because vmovmskps can only set the low 8 bits, the upper bits are already zero.
// inc = -1 for (itr < max) & (...
I put your code up on the Godbolt Compiler Explorer to check out the asm from the intrinsics version. You're right that even with gcc 5.3 or clang 3.8, there are spills / reloads in the inner loop. So you may actually get a speedup from hand-written asm here, if those store-forwarding round trips aren't hidden by out-of-order execution.
The problem with ...
array equ rdi ; pointer to array
size equ rsi ; array size (count of elements)
value equ xmm0 ; value to process with
I don't particularly care for these. It seems like you're trying to make the code look more like a high-level language, but it seems to me that it ends up ...
save the registers we intend to alter, failure to do so causes problems when gcc -O3 is used
It should be possible to avoid the pushes and pops. If you are changing the values of the constraints, you cannot have them as just "inputs" (which is where this code currently has them). Quoting the docs:
Do not modify the contents of input-only operands (...
Probably not what you're expecting, but it's possible to do a 512-bit add directly with AVX512 registers. The _addcarryx_u64() intrinsic is not necessary nor do you need to break up the register into scalars.
Taken from my blog: http://www.numberworld.org/y-cruncher/internals/addition.html#ks_add
The following is a little-endian 512-bit full-adder:
if( array.Length <= 0 )
This piece of code is suspicious. Maybe you should return sbyte.MaxValue, maybe null, or maybe throw ArgumentException. (I can imagine that there were a Max method, both, together is used to find the range of values, then maybe 0 is a valid return value.) This is one of the rare times I'd appreciate a ...
I had to adapt the test program to get meaningful results using /usr/bin/time:
static const int codeSize = 4096;
cv::Mat1f mat1(1, codeSize);
cv::Mat1f mat2(1, codeSize);
float low = 0;
float high = 2.0;
cv::theRNG().state = cv::getTickCount() ;
randu(mat1, cv::Scalar(low), cv:...
The major compilers did not really auto-vectorize this, but it can be done manually. For example with AVX, we could do something like (not tested)
int indexOfMin(double pt_x, double pt_y, double pt_z, int n)
__m256d ptx = _mm256_set1_pd(pt_x);
__m256d pty = _mm256_set1_pd(pt_y);
__m256d ptz = _mm256_set1_pd(pt_z);
__m256d xdif = ...
I tried using the fused multiply-add and multiply-add-negate instructions, and they made the code significantly slower than using 2 or 3 separate instructions, unfortunately.
This is usually indicative of a latency bottleneck,
That's about a 33% increase, which is OK. Given that I'm calculating 8 times as many pixels at once, I was hoping for more.
A couple of thoughts:
Given that I'm calculating 8 times as many pixels at once, I was hoping for more.
Yes, simd delivers some pretty spectacular performance improvements when doing vector/matrix operations, but for the Mandelbrot, all you’re doing is elementwise addition and multiplication, so you should see improvements, but nothing ...
Harold's comment is correct.
Consider what happens for float inputs like 5000000000 * 1.0. Conversion to int32_t with cvtps2dq will give you -2147483648 from that out-of-range positive float. (2's complement integer bit-pattern 0x80000000 is the "indefinite integer value" described by Intel's documentation for this case.)
In that case, your vectorized ...
A straightforward transliterating to AVX2 intrinsics works, but I didn't like what the compilers made of it.
For example, an obvious approach is to load 8 bytes, widen them to 8 ints, etc. And that obvious way to do that, I think, is with _mm_loadl_epi64 to do the loading. Unfortunately, MSVC and even GCC refuse to merge a _mm_loadl_epi64 into the memory ...
An issue with that code is that while it tries to pack the data to make efficient use of arithmetic throughput, actually arithmetic throughput is high anyway and it's the shuffles (including horizontal addition which has two shuffles internally) that are relatively expensive. Shuffles don't all have a low latency either, cross-slice shuffles such as ...
the posted code does not compile! Please post code that compiles. When compiling, always enable the warnings, then fix those warnings. (for gcc, at a minimum use: -Wall -Wextra -Wconversion -pedantic -std=gnu17 ) Note, other compilers have a different set of options to accomplish the same thing
here is what the compiler outputs when given the posted ...
Small differences due to precision are expected and can usually be ignored.
12 shuffles like that are a bit much, though not necessarily avoidable, depending on whether there is AVX support. With AVX, it is better to literally broadcast from memory, rather than emulate broadcasting with a load and shuffles. Even though this means there will be more loads, ...
Thanks for all comments and tips! Especially @harold This is my final version with the main goal to eliminate cache misses by never loading columns into a vector.
It removes the costly set and load function by working with pointer loading and storing instead of element. By working row by row the cachemisses are few and memory blocks are used more fully.
Avoid sqrt if possible
One thing that can give a large speed improvement is if you can avoid computing the square root, and just work with squared distances instead. This works fine for things like finding which point is closest to a specified point, or all that within a given distance of a specified point.
This won't work if (for example) you're trying to ...
Here's my pass at it; without the rest of your source I can't verify it's integrity so I don't know if this will compile correctly with the rest of your code, so hopefully it can help:
// outside function since they're consts, decreases run time as no more construction each function call
const XMVECTOR NullVector = XMLoadFloat4A(&XMFLOAT4A(0.f, 0.f, 0.f,...