PERF: first try inplace setitem for .at indexer
This is from the commit that I originally also tested in the PR that caused the perf regression: https://github.com/pandas-dev/pandas/pull/47074/files#r878902106 (but eventually didn't include because it didn't seem needed for getting the correct behaviour, but didn't consider performance at the time)

Using the example from https://github.com/pandas-dev/pandas/issues/49729:

```
import pandas as pd

def foo(df):
    for idx in df.index:
        df.at[idx, "bar"] = 3

df = pd.DataFrame(range(10000))
df["bar"] = 0

%timeit foo(df)
# 442 ms ± 16.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  <-- main
# 202 ms ± 3.13 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)` <-- PR
```

This is not yet as fast as on 1.4.x, but https://github.com/pandas-dev/pandas/pull/49771 further helps with this as well. With both together, I get to down to 119 ms (vs 50 ms on 1.4.3). 
The remaining overhead is mostly from Manager.iget to get the SingleBlockManager


- [x] closes https://github.com/pandas-dev/pandas/issues/49729
- [ ] [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/vX.X.X.rst` file if fixing a bug or adding a new feature.

@jbrockmendel any other comment, or OK to merge? Good point. Unless I am missing something, it seems we actually don't have _any_ benchmarks at all for `.at` .. 
(the underlying `_get_value` was previously still benchmarked through the `lookup` benchmarks, but those have been removed since that method had been removed) https://github.com/pandas-dev/pandas/pull/49772/files#r1026928855 Added a benchmark case for at (both getitem and setitem) to the existing DataFrame indexing benchmark class. Using a code snippet mimicking the benchmark setup:

```
import pandas._testing as tm

index = tm.makeStringIndex(1000)
columns = tm.makeStringIndex(30)
df = DataFrame(np.random.randn(1000, 30), index=index, columns=columns)
idx_scalar = index[100]
col_scalar = columns[10]

In [3]: %timeit df.at[idx_scalar, col_scalar] = 0.0
100 µs ± 18.8 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)  # <-- main
25.7 µs ± 2.54 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)  # <-- PR
```