Performance optimizations and memory management

Added some benchmarks to secure channel and played around with improving its performance, also started using a sync.Pool for message buffers.
`trySend -> false -> context.Cancelled` is a hard-to-follow pattern. I know the `select` looks ugly, but a change up in `trySend` by someone who doesn't have the whole design fresh in their mind can create really confusing, hard-to-find bugs. The alternative is to do something like this:

```Go
// make trySend return an error
func (sp *SignedPipe) trySend(b []byte) error {
	select {
	case <-sp.ctx.Done():
		return sp.ctx.Err()
	case sp.insecure.Out <- b:
		return nil
	}
}

// and then here ...

if err := trySend(pkb); err != nil {
  return err
}

// this way we don't split the logical context across two functions. 
// right here, we see trySend can fail, and if it fails, we bail.
// why it fails is up to trySend.
```

A more general note in last few PRs: In writing code, correctness is half the battle. the other is making it easy to understand, use, maintain, and change. Important litmus tests are "if i was reading this for the first time, can i immediately understand it in one pass, or would i have to re-read?", or "is this the simplest thing we can do?"