Saturday, February 24, 2018
Let's get going!
You might be asking if this is just one more of the many blog posts about go that can be found all over the internet. I don't want to duplicate what other people have written, so I'll mostly be crypto functions sha3/keccak in go.
Despite a brief experiment with go almost two years ago, I had not done any serious coding in go. That all changed when early this year I decided to write an ethereum miner from scratch. After maintaining and improving https://github.com/nerdralph/ethminer-nr, I decided I would like to try something other than C++. My first attempt was with D, and while it fixes some of the things I dislike about C++, 3rd-party library support is minimal. After working with it for about a week, I decided to move on. After some prototyping with python/cython, I settled on go.
After eight years of development, go is quite mature. As I'll explain later in this blog post, my concerns about code performance were proven to be unwarranted. Although it is quite mature, I've found it's still new enough that there is room for improvements to be made in go libraries.
Since I'm writing an ethereum miner, I need code that can perform keccak hashing. Keccak is the same as the official sha-3 standard with a different pad (aka domain separation) byte. The crypto/sha3 package internally supports the ability to use arbitrary domain separation bytes, but the functionality is not exported. Therefore I forked the repository and added functions for keccak-256 and keccak-512. A common operation in crypto is XOR, and the sha3 package includes an optimized XOR implemenation. This function is not exported either, so I added a fast XOR function as well.
Ethereum's proof-of-work uses a DAG of about 2GB that is generated from a 32MB cache. This cache and the DAG changes and grows slightly every 30,000 blocks (about 5 days). Using my modified sha3 library and based on the description from the ethereum wiki, I wrote a test program that connects to a mining pool, gets the current seed hash, and generates the DAG cache. The final hex string printed out is the last 32 bytes of the cache. I created an internal debug build of ethminer-nr that also outputs the last 32 bytes of the cache in order to verify that my code works correctly.
When it comes to performance, I had read some old benchmarks that show gcc-go generating much faster code than the stock go compiler (gc). Things have obviously changed, as the go compiler in my tests was much faster in my tests. My ETH cache generation test program takes about 3 seconds to run when using the standard go compiler versus 8 seconds with gcc-go using -O3 -march=native. This is on an Intel G1840 comparing go version go1.9.2 linux/amd64 with go1.6.1 gccgo. The versions chosen were the latest pre-packaged versions for Ubuntu 16 (golang-1.9 and gccgo-6). At least for compute-heavy crypto functions, I don't see any point in using gcc-go.