PERF: copy cached attributes on index shallow_copy
- [x] closes #28584
- [ ] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

The performance of the example in #28584 is:

```python
>>> idx = pd.Index(np.arange(100_000))
>>> %timeit idx.get_loc(99_999)
1.17 µs ± 80.6 ns per loop  # master and this PR
>>> %timeit idx._shallow_copy().get_loc(99_999)
3.57 ms ± 286 ns per loop  # master
3.67 µs ± 286 ns per loop  # this PR
```

The issue is still on the extension indexes, e.g. ``CategoricalIndex._shallow_copy``. I'd like to take them afterwards.
LGTM pending green Should engine be shared?  If so, maybe access it to ensure it is in _cache before coping _cache? If the ``_cache`` has been populated before the call to ``_shallow_copy``, we always copy. It it hasn't, it's a question of evaluating trade-offs:
1. If we access ``_engine`` before copying, we risk calculating unneccesarily
2. If we do not access it before copying, we risk having to do the same calculation several times.

My hunch is that it's mostly not that important, because either the cache has already been populated, or the original index will not be used again (e.g. in a pipe, often).

But in some given cases we'd prefer either 1 or 2, depending on context, but we'd have to choose. I prefer 2., because in that case ``_shallo_copy`` will never halt to create new ``_engine`` attributes, i.e. performance will be more predictable.
 Makes sense, thanks for explaining your thought process. thanks @topper-123 

if you think we need additional asvs's happy to have them (e.g. is_monotonic, is_unique), MI indexing, and .get_loc would all benefit; we might have enough, just checking in about this.