PERF: optimize storage type for codes in Categoricals (GH8453)

closes #8453

So easy enough to optimize the dtype of the codes array depending on the number of categories. Most
of the time will simply be ``int8``. So pretty good savings.

This doesn't completely solve the storage issues as currently ``factorize`` returns a ``int64``, but can deal with that later (as might also need some more cython definitions).

And of course in the extreme case (where len(categories) == len(codes) you are better off with an object dtype).

```
In [1]: s = Series(['foo','bar']*1000)

In [2]: s.nbytes
Out[2]: 16000

In [3]: s.astype('category').nbytes
Out[3]: 2016

In [5]: s.astype('category').values.codes
Out[5]: array([1, 0, 1, ..., 0, 1, 0], dtype=int8)
```
what is this naked except clause supposed to catch?