Micro optimise Symbol#fullName
Micro optimise Symbol#fullName

The old approach of recursively calling `fullNameAsName`
creates a lot of garbage for intermediate results, in
addition to needless interning of those results into
the name table.

This commit instead creates a string buffer of the
correct capacity and writes the component names
directly into this.

I compared old and new approaches and this shows a 2x
speedup.

```
scala>  val th = ichi.bench.Thyme.warmed(verbose = print)
th: ichi.bench.Thyme = ichi.bench.Thyme@1643e817

scala> val w_old = th.Warm(sym.fullNameAsNameOld('.'))
w_old: th.Warm[$r.intp.global.Name] = ichi.bench.Thyme$Warm@7a8d001b

scala> val w_new = th.Warm(sym.fullNameAsName('.'))
w_new: th.Warm[$r.intp.global.Name] = ichi.bench.Thyme$Warm@1ec14586

scala> th.pbenchOffWarm("", x => println(x))(w_old, 10, "old")(w_new, 10, "new")
Benchmark comparison (in 4.084 s)
    old vs new
Significantly different (p ~= 0)
  Time ratio:    0.53572   95% CI 0.51618 - 0.55525   (n=20)
    old    64.54 ns   95% CI 62.41 ns - 66.67 ns
    new    34.57 ns   95% CI 34.04 ns - 35.11 ns
res3: $r.intp.global.Name = scala.collection.parallel.mutable.ParSeq
```

It is still expensive enough that we should still consider
caching. The call to full name in `classBTypeFromSymbol`
in the new backed is a prime candidate for optimization.

jvican/scala,martijnhoekstra/scala,lrytz/scala,scala/scala,shimib/scala,scala/scala,felixmulder/scala,jvican/scala,scala/scala,martijnhoekstra/scala,slothspot/scala,scala/scala,felixmulder/scala,slothspot/scala,slothspot/scala,felixmulder/scala,jvican/scala,jvican/scala,lrytz/scala,lrytz/scala,shimib/scala,lrytz/scala,martijnhoekstra/scala,lrytz/scala,martijnhoekstra/scala,slothspot/scala,shimib/scala,jvican/scala,felixmulder/scala,scala/scala,slothspot/scala,shimib/scala,shimib/scala,shimib/scala,martijnhoekstra/scala,slothspot/scala,slothspot/scala,jvican/scala,lrytz/scala,scala/scala,jvican/scala,felixmulder/scala,felixmulder/scala,martijnhoekstra/scala,felixmulder/scala