optimizations ... mainly by refactoring some recursions

The motivation of this pull request is to improve the speed of converting quantities to different units.  Profiling reveals that the `isinstance` calls in the methods of UnitsContainer are not particularly fast and are called very often.  I considered a PR which removes those `isinstance` calls since they are arguably valuable only for debugging, but they are very nice to have so that code fails early.  Instead I think this PR is a more enlightened version which (principally) rewrites get_base_units and get_dimensionality with a helper function for the recursion and constructs the new UnitsContainer once at the end.

The appears to obviate the need for `_base_units_cache` and so I've removed it.  A few other easy items presented themselves as well which are clear in the commits.

I illustrate the performance improvements here:

```
>>> import pint
>>> import time
>>> ureg = pint.UnitRegistry()
```

```
>>> q = 34.*ureg.cm
>>> m = ureg.m
>>> s = time.time()
>>> x = [q.to(m) for _ in range(100)]
>>> time.time() - s # doctest: +ELLIPSIS
0....
```

New:  0.0089;  Old:  0.0121

```
>>> q = 34.*ureg.cm**3*ureg.g
>>> m = ureg.m**3*ureg.mg
>>> s = time.time()
>>> x = [q.to(m) for _ in range(100)]
>>> time.time() - s # doctest: +ELLIPSIS
0....
```

New:  0.0144;  Old:  0.0310

```
>>> q = 34.*ureg.V
>>> m = ureg.mV
>>> s = time.time()
>>> x = [q.to(m) for _ in range(100)]
>>> time.time() - s # doctest: +ELLIPSIS
0....
```

New:  0.0205;  Old:  0.0746

As you can see the performance improvement grows as the structure of the unit becomes more complex, but even the base case has substantial improvement.