Benchmarking Go Ethereum account creation in Android

0 3


I was wondering today about how Android faces some of the cryptographic challenges of current Blockchain implementations. In order to solve this question, I just tried the best way I know: code, benchmark and plot it.

Following the official Geth Ethereum implementation, an account creation is defined as follows in Go:

func GenerateKey() (*ecdsa.PrivateKey, error) {
return ecdsa.GenerateKey(S256(), rand.Reader)
}

To generate the so typical Ethereum address and private key encoded as hexadecimal string, we just need to call following methods over previously created ECDSA struct object:

To get the public address string

// Get the address
address := crypto.PubkeyToAddress(key.PublicKey).Hex()

To get the private key string

// Get the private key
privateKey := hex.EncodeToString(key.D.Bytes())

Since we are going to run benchmarks at runtime, we cannot make use of go benchmarking tools and we need to implement our own benchmarking method. For this purpose, we will design a wrapper function that takes two time snapshots and computes the difference, returning metrics results.

The data we are going to collect is wrapped in BenchResponse struct

type BenchResponse struct {
Ms int `json:"micros"`
Millis int `json:"millis"`
Size int `json:"size"`
Created int `json:"created"`
}

To measure the single core runtime execution time, we will make use of following wrapper function:

func measureFunc(size int, f func() bool) BenchResponse {
ts := time.Now()
i := 0
for i < size {
if f() {
break
}
i++
}
duration := time.Since(ts)
return BenchResponse{
Ms: int(duration.Microseconds()),
Millis: int(duration.Milliseconds()),
Size: size,
Created: i,
}
}

Our example test, will generate 1000 Ethereum addresses sequentially by running:

func AccountBenchSingleCore(size int) BenchResponse {
return measureFunc(size, func() bool {
pk, genErr := crypto.GenerateKey()
return genErr != nil || pk == nil
})
}

Prior executing this test in Android, we run a simple unit test to make sure it works as expected:

t.Run("single-core", func(t *testing.T) {
r := AccountBenchSingleCore(1000)

assert.NotNil(t, r)
assert.Equal(t, r.Created, 1000)

t.Log(r)
t.Log("time micros:", r.Ms)
t.Log("time millis: ", r.Millis)
})

In order to allow parallel creations of Ethereum accounts over multiple cores, we will modify previous code and implement both channels and wait groups to create the same amount of accounts.

func AccountBenchMultiCore(size int) BenchResponse {
var wg sync.WaitGroup
wg.Add(size)
ts := time.Now()

for i := 0; i < size; i++ {
go func() {
_,_ = crypto.GenerateKey()
wg.Done()
}()
}
wg.Wait()
duration := time.Since(ts)
return BenchResponse{
Ms: int(duration.Microseconds()),
Millis: int(duration.Milliseconds()),
Size: size,
Created: size,
}
}

Prior executing this test in Android, we run a simple unit test to make sure it works as expected with no data race conditions

t.Run("multi-core", func(t *testing.T) {
r := AccountBenchMultiCore(1000)
assert.NotNil(t, r)
assert.Equal(t, r.Created, 1000)
t.Log(r)
t.Log("time micros:", r.Ms)
t.Log("time millis: ", r.Millis)
})

Running unit test using go test give us an initial result and difference between single core version and concurrent version

go test -c -o /tmp/test
go tool test2json -t /tmp/test -test.v -test.run ^TestAccountBench$
=== RUN TestAccountBench
--- PASS: TestAccountBench (0.32s)
=== RUN TestAccountBench/single-core
--- PASS: TestAccountBench/single-core (0.21s)
api_bench_test.go:15: &{208782 208 1000 1000}
api_bench_test.go:16: time micros: 208782
api_bench_test.go:17: time millis: 208
=== RUN TestAccountBench/multi-core
--- PASS: TestAccountBench/multi-core (0.11s)
api_bench_test.go:23: &{114739 114 1000 1000}
api_bench_test.go:24: time micros: 114739
api_bench_test.go:25: time millis: 114
PASS
Process finished with exit code 0

After plotting results, we can view the huge difference that we can get by running the same code in single core or multi-core.

Benchmarking results: Concurrent vs Parallel

To test our Go code in Android, it is necessary to compile it to a shared object library (*.aar) using Gomobile. In this case, the command that triggers the compilation of our Go code is:

gomobile bind -o ./benchtest.aar -a -target=android projects/benchtest

The result is a pair of files: *aar library and source code:

rw-rw-r — 1 root root 9,5M dic 2 20:23 benchtest.aar
rw-rw-r — 1 root root 11K dic 2 20:23 benchtest-sources.jar

We created a demo application, include the shared library and its sources and create a dummy trigger from Activity setting view as shown below.

Let’s execute or Benchmarking function from AppCompatActivity

The “Run Benchmarking” button will call native Go code inside of the *.so file and return our benchmark data result to the user as Dialog popup.

Benchmark results running on Android Emulator.

Thanks for checking this out and I hope you found the info useful! This is a basic introduction to how to run Golang code in Android and measure its performance.

If you want to see more content on — hit the 👏 button as many times as you can and share with your colleges, co-workers, FFF, etc so I know you want more content like this!

You might also like

Pin It on Pinterest

Share This

Share this post with your friends!

WhatsApp chat