Introduce SIMD intrinsics for `_dist_metrics.pyx`

# Context
Pairwise distance computation is an essential part of many estimators in scikit-learn, and can take up a significant portion of run time in certain workflows. I believe that we may achieve significant performance gains in several (perhaps most) distance metric implementations by leveraging SIMD intrinsics.

# Proof of Concept

I built a quick proof of concept just to see what kinds of performance gains we could observe with a potentially-naive implementation of SIMD intrinsics. I chose to optimize the `ManhattanDistance.dist` function. This implementation uses intrinsics found in `SSE{1,2,3}`. To ensure that the instructions are supported, it checks for the presence of the `SSE3`  instruction set (`SSE3` implies `SSE{1,2}`) and provides the optimized implementation if so. Otherwise it provides a dummy implementation just to appease Cython, and the main function falls back to the current implementation on `main`. Note that on most modern hardware, support for `SSE3` is a reasonable expectation (indeed numpy assumes it is always present when optimization is enabled). For the specific implementation referred to here, please take a look at this PR: https://github.com/Micky774/scikit-learn/pull/11

Note that the full benefit of the intrinsics are gained when compiling with `-march="native"`, however the benefit is still significant when compiling with `-march="nocona"`, as is often default (e.g when following the scikit-learn development instructions on linux).

# Benchmarks
The following benchmarks were produced by this gist: https://gist.github.com/Micky774/567a5fa199c05d90c4c08625b077840e

### **Summary: The SIMD implementations are ~2x faster than the current implementation for `float32` and 1.5x faster for `float64`.**

<details>
<summary>Plots</summary>

![f2b1f1e8-59b0-4ec5-b91c-fe1d19abd9ec](https://user-images.githubusercontent.com/34613774/228374474-99ee13b3-228d-4c53-9dcc-3a8f8b639a9f.png)
</details>

# Discussion
I haven't looked too deeply into this yet, as first I wanted to see whether there was interest in the venture. I would love to hear what the other maintainers' thoughts are regarding exploring this route in a bit more detail. Obviously SIMD implementations will bring with them added complexity, but the performance gains are pretty compelling. In my opinion, the tradeoff is worth it.

CC: @scikit-learn/core-devs

COMMENT:
> To ensure that the instructions are supported, it checks for the presence of the SSE3 instruction set (SSE3 implies SSE{1,2}) and provides the optimized implementation if so

But these are compile-time checks right? So if the binaries are compiled on a machine that supports SSE3, the binary will try to execute the intrinsics instructions, even if the end-users' machine doesn't support it?

Perhaps this is less of an issue for SSE3 as @Micky774 mentioned they should be widely available. But FWIW SIMD support and packaging is a hairy issue and that might be a challenge for scikit-learn. Here's what [PIL-SIMD](https://github.com/uploadcare/pillow-simd#why-do-not-contribute-simd-to-the-original-pillow) has to say about why it's not upstreamed in PIL directly:

> Why do not contribute SIMD to the original Pillow
> Well, it's not that simple. First of all, the original Pillow supports a large number of architectures, not just x86. But even for x86 platforms, Pillow is often distributed via precompiled binaries. In order for us to integrate SIMD into the precompiled binaries we'd need to execute runtime CPU capabilities checks. To compile the code this way we need to pass the -mavx2 option to the compiler. But with the option included, a compiler will inject AVX instructions even for SSE functions (i.e. interchange them) since every SSE instruction has its AVX equivalent. So there is no easy way to compile such library, especially with setuptools.


[In PyTorch](https://github.com/pytorch/pytorch/tree/master/aten/src/ATen/native/cpu#readme), they work around this problem by compiling the optimized code with multiple instruction sets, and then leverage the pytorch dispatcher *at run time* to figure out which code-path to use depending on the users machine support. But this is probably way too complex for scikit-learn.

EDIT: as far as I can tell the pytorch setup is very similar to that of `numpy` that Tim linked to below https://github.com/scikit-learn/scikit-learn/issues/26010#issuecomment-1488548448

Labels: Enhancement, Needs Decision, cython