Hash performance improvements

Chunking backends plus aggressive subs-ing in compute traversals recreate the same expressions over and over and then run these expressions through dictionaries forcing hash calls.  This happens often enough to be a significant performance hit (30% in the benchmark I was running).  We resolve this in two ways:

1.  We cache the hash on the expression as a `._hash` field
2.  We cache `Symbol` objects with the `symbol` function.  I.e.

        Symbol('i', 'int')  # before
        symbol('i', 'int')  # after

        symbol('i', 'int') is symbol('i', 'int')

These two steps *do* increase code complexity a bit.  However they reduce Blaze's expression overhead to below noticeable levels (on the benchmark that I was running).