Is sorted using SIMD instructions
0x80.pl
0x80.pl
// the for block
<source>:10:24: note: ===== analyze_loop_nest =====
<source>:10:24: note: === vect_analyze_loop_form ===
<source>:10:24: note: not vectorized: control flow in loop.
<source>:10:24: note: bad loop form.
<source>:5:6: note: vectorized 0 loops in function.
// the if block inside the for block
<source>:11:9: note: got vectype for stmt: _4 = *_3;
const vector(16) int
<source>:11:9: note: got vectype for stmt: _8 = *_7;
const vector(16) int
<source>:11:9: note: === vect_analyze_data_ref_accesses ===
<source>:11:9: note: not vectorized: no grouped stores in basic block.
<source>:11:9: note: ===vect_slp_analyze_bb===
<source>:11:9: note: ===vect_slp_analyze_bb===
<source>:11:9: note: === vect_analyze_data_refs ===
<source>:11:9: note: not vectorized: not enough data-refs in basic block.
edit: with intel compiler using `-qopt-report=5 -qopt-report-phase=vec -qopt-report-file=stdout` Begin optimization report for: is_sorted(const int32_t *, size_t)
Report from: Vector optimizations [vec]
LOOP BEGIN at <source>(12,5)
remark #15324: loop was not vectorized: unsigned types for induction
variable and/or for lower/upper iteration bounds make
loop uncountable
LOOP END bool is_sorted(const int32_t* input, size_t n) {
int32_t sorted = true;
for (size_t i = 1; i < n; ++i) {
sorted &= input[i - 1] <= input[i];
}
return sorted;
}
And the performance is similar to the AVX version (benchmarked on a MacBook Air early 2015): $ ./benchmark_avx2 1048576
input size 1048576, iterations 10
scalar : 6379 us
SSE (generic) : 3544 us
SSE : 3704 us
My example : 2769 us
AVX2 (generic) : 2679 us
AVX2 : 3360 us
So I'm getting 2769us with the above 5 simple lines of code. It's just 3% slower (that might be noise).GCC cannot vectorise because the loop terminates early in case of a non-sorted pair. It thereby contains control flow.
I guess that's kind of logical. Even if the compiler recognised the early termination of the loop as the optimisation it is, it would still have to make the decision to give up on it in favour of vectorisation (?)
Similar annoying problems arise when people try to be clever with C strings, and read past the null. You can write optimisations which work, but they require care.
Is there a way to tell the compiler this? I've been trying with std::vector and std::array instead of raw pointer but without any luck. std::array also constrains you to static length.
"A declaration of a parameter as ''array of type'' shall be adjusted to ''qualified pointer to type'', where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression."
According to this, the following syntax could be used for optimization
void foo(size_t n, int array[static n]) {...}
pointers to array could be used too void foo(size_t n, int (*array)[n])
{
printf("array %zu, *array %zu\n", sizeof(array), sizeof(*array));
}
foo( 10, null); // array 8, *array 40
foo(100, null); // array 8, *array 400This sort of optimization reasoning can result in quite surprising behavior: http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
If I write a routine which walks an array one element at a time, in order, up to some max index N, and also stops early when it reaches some other condition (like an element equal to zero), then I am allowed to pass in memory which is only 3 elements long, and an N greater than 3, if I know that there is a zero in the first 3 elements. The function must not be optimized to read past the zero element, regardless of the N passed in, so removing the early exit would be an invalid optimization.
There's no undefined behavior in the above that justifies that optimization.
On this basis another way of approaching the problem is to loop over the whole array and return the result at the end, but this of course would take N/2 loops on average, whereas the nested if can exit early. A compromise might be to loop over short sub-spans of the array, and do an exit early test at the end of each sub-span.
A good sub-span length for scalar code might be around 16, for one because we hit the law of diminishing returns for longer spans.
Also I think is_sorted_asc() or is_sorted_ascending() might be a better name if this were for a function in a general purpose library.
Another good use of Duff's device!
If you introduce a result variable and set it during the loop, the compiler can vectorize the loop. At least icc does, but I didn't play with the compiler settings of the other compilers too much.
Also compilers can still exit the loop early and at least MSVC does, because a segfault is UB and can be "optimized away".
gcc manages it too, but emits a huge amount of code, though. and setting -Os seems to stop it from vectorising the code. shame.
edit: replaced with more correct version..
Wouldn't this cause a sizable performance hit due to being misaligned most of the time?
> Load 128-bits of integer data from memory into dst. mem_addr does not need to be aligned on any particular boundary.
I can imagine that running AVX is_sorted (or any other AVX procedure) in multiple threads would be actually slower than running non-vectorized procedure.
Of course, that's my purely anecdotal opinion.
Typically I see about 2 SIMD instruction throughput per cycle per core on Intel CPUs. SIMD execution units are not shared between cores in any way.
Clock throttling might happen, but SIMD is usually still a pretty huge net win.
AMD Bulldozer processors have a shared floating point unit and some early pipeline stages like the instruction decoder per pair of cores. AMD Zen processors have since reverted to a more conventional design.
Or are you trying to make some claim about hyperthreading?
[0]: https://www.anandtech.com/show/11544/intel-skylake-ep-vs-amd...
AFAIK the only difference between AVX performance (ignoring clock speed) is gold/platinum(5000+) Xeons have 2x512 FMA ports available but everything else only supports FMA on port 1/2 and not 5. Stabbing a bit in the dark here, it's been a bit since I was looking at this stuff.
Lesson learned, sometimes the easiest performance gains are found by not being naive about memory access. The extra instructions were inconsequential.
Impressive, but definitely beatable given sufficient free time.
There's no chance you could achieve same by using scalar instructions. SIMD can access memory a lot faster than scalar.
Random access is another matter. The trick is of course to avoid non-sequential access patterns.
IOW: Are the scalar instructions slower than memory bandwidth?
I work with image processing, >1GiB/s. We optimize for each platform for hand, many embedded platforms but also x86_64. Most of our work is detectors and loss-less compression and we could not do these things in realtime if it weren't for SIMD/MIMD.
If your bottleneck isn't memory, then yeah, SIMD is a real boon.
Especially since high performance software is glad to have even a 1% boost in speed.