PERF: DataFrame.first_valid_index/last_valid_index for EA dtypes
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/v2.1.0.rst` file if fixing a bug or adding a new feature.


ASV added:
```
       before                after    ratio
-      21.6±0.1ms          484±9μs     0.02  frame_methods.FindValidIndex.time_last_valid_index('float64[pyarrow]')
-      21.6±0.3ms          427±3μs     0.02  frame_methods.FindValidIndex.time_first_valid_index('float64[pyarrow]')
-      17.0±0.5ms          167±4μs     0.01  frame_methods.FindValidIndex.time_last_valid_index('Float64')
-      16.8±0.2ms          132±2μs     0.01  frame_methods.FindValidIndex.time_first_valid_index('Float64')
```
wow thats a huge difference.  is it all in the len(values) or something? > wow thats a huge difference. is it all in the len(values) or something?

`self._values` on a multi-column EA-backed frame converts to an object dtype ndarray. This avoids going through the object dtype conversion. > This is basically the same as with MultiIndex._values?

similar in the sense they're both expensive conversions through object dtype. `MultiIndex._values` has the additional overhead of wrapping in tuples. Yep, thx, that's what I was thinking Thx @lukemanley 