SSE Assembly vs GCC Compiler - Dot Product
While harold reviewed your assembly code, I'll just comment on how you are compiling your C code:

Increase the duration of the benchmark

Your code runs for a very short amount of time, only tens of microseconds. This is too short to get accurate measurements:


clock() only has a resolution of a microsecond, so this is not insignificant compared to the duration.
the processor's caches, branch predictors and prefetchers might not have had time to fully warm up.
interrupts and other processes running on your system add noise.


Aim to run your benchmarks for at least a second. That might be easier said than done: if you increase the size of the arrays you might become memory bandwidth bound instead of CPU bound. And if you naively just repeat the computations with the small array, the compiler might notice and just calculate the sum once and the multiply it by the number of repeats, depending on the level of optimization used.

Use a proper optimization level

There is no such thing as -O7. The highest supported optimization level for GCC is -O3. However, even that does not enable all possible optimzations. In particular, GCC is a bit careful when it comes to floating point math, and tries to ensure the code is correct even if there are infinities, NaNs and denormals. It also knows that floating point math is not strictly associative and commutative, and so will try to keep operations in the same order as you specified them, which prevents it from using some vectorization tricks. If you don't care about that, you can enable -ffast-math, or use -Ofast.

Consider using -mtune=... and/or -march=...

If you don't specify any specific CPU, then on an x86-64 platform, GCC will output code that can run on any 64-bit Intel or AMD CPU, and might not be able to use certain SSE instructions that are not available in the x86-64 baseline. Also, the compiler will assume a certain CPU for instruction timing, delay slots, and other micro-architecural optimizations, which might not be ideal for the Xeon CPU you are running the code on. Typically you would use -march=native to ensure the compiler will provide code using all features of the CPU you are compiling on, but that might cause it to use AVX instructions if your CPU supports those.

Consider using SSE intrinsics

Instead of having a pure assembly version and a pure C version, you can have something inbetween by using SSE intrinsics. These are functions that are compiled into specific CPU instructions. However, the function arguments and return values are just variables (either regular ones or special vector type variables), not registers. The compiler will pick registers as it sees fit, and will also be able to reorder the intrinsics calls if possible, using its knowledge about the CPU's micro-architecture, and if you use the intrinsics in a loop then the compiler can unroll the loop for you.

While GCC and Clang are able to vectorize certain loops, they are quite bad at using horizontal operations such as haddps, so if you help it by using intrinsics you might get code comparable to the best hand-optimized assembly.

Some results

If I increase the size of the array to 25600000, and compile with -O7 (which will effectively be -O3), I get the following results on an AMD Ryzen 9 3900X:

0.008444000000000
Solution 16.000000000000000
0.018092000000000
Solution 15.403682708740234


With -Ofast I get:

0.008399000000000
Solution 16.000000000000000
0.006617000000000
Solution 16.419670104980469


So clearly there is a speed-up when going to -Ofast, but the resulting solution is also different.