GO! Golang and Gomobile vs Android+Kotlin and IOS+Swift.

Igor Steblii
5 min readApr 4, 2020

--

Pron, cons, and performance.

Recently I started looking for a cross-platform solution with a good set of features that could be used in mobile development. I don’t want to share UI: only business logic, networking, maybe work with DB.

Gomobile I find especially interesting. There are several benefits of using Go: it has a simple and lightweight gorutines (threads), widely used on the backend, has simple and readable code (comparing to C++), support of SQLite, has a Reactive library, supported in many IDEs and last but not least — good performance.

Let's dig more into Golang's performance itself. It is slightly faster than Java, but of course, it is slower than C++ or Swift. IMHO, all mobile cross-platform solutions are slower than Swift, except C++ (Android NDK), but considering problems that C++ can bring to our life (memory leaks, security, etc.) we don’t want to use it for a big project.

You can find more examples here.

Another con that it is not as popular as Kotlin/Swift, at least on the StackOverflow.

Information form Stackoverflow trends

Integration

When I started investigating Gomobile, I found out that it has two strategies you can follow to integrate it into the project:

*Writing all-Go native mobile applications.
*Writing SDK applications by generating bindings from a Go package and invoke them from Java/Kotlin (on Android) and Objective-C/Swift (on iOS)

Since I wanted to share business logic, I focused on the second solution — the library. But it has a few limitations and one of them worried me a lot:

Current limitations are listed below.
* Only a subset of Go types are currently supported.
* Language bindings have a performance overhead.
* There are a few limitations on how the exported APIs should look due to the limitations of the target language.

Performance overhead — what does it mean? I decided to do some benchmarking to test performance. For that, I decided to pick some heavy math computation — factorial. Actually, the task itself doesn’t matter, I just need to understand the difference between native execution and Go.

GO

I used next code snipper for Go factorial function:

Factorial function in Golang

Here are results of the computation in the Go environment:

Golang Factorial: Elapsed Time for 10000 is 10
Golang Factorial: Elapsed Time for 50000 is 228
Golang Factorial: Elapsed Time for 100000 is 869
Golang Factorial: Elapsed Time for 500000 is 23978
Golang Factorial: Elapsed Time for 1000000 is 101687

Generated libraries

Gomobile plugin allows us to generate .aar for Android and .framework for IOS. What’s interesting here is the size of the generated libraries for this small project (one class as you see above):

  • Android: 5.5 MB (.arr and .jar with sources for binding)
  • IOS: 15.7MB (.framework folder)

Test

On each platform, I did two tests:

  1. Call Golang factorial library function and measure duration of execution in Go and on the caller side (Swift or Kotlin)
  2. Create and call the same factorial function in Swift/Kotlin to compare with Golang execution time

Input data: 10000, 50000, 100000, 500000, 1000000

Kotlin

Kotlin factorial function:

Execution of this function on the Android MainThread (high priority):

Kotlin: For 10000; elapsed Time: 108
Kotlin: For 50000; elapsed Time: 1226
Kotlin: For 100000; elapsed Time: 4070
Kotlin: For 500000; elapsed Time: 100010
Kotlin: For 1000000; elapsed Time: 432013

And this is binding with Goland. The first log from Golang followed by the same from Kotlin's side. It is much slower than standalone Golang execution but similar to Kotlin (out of many tests I run, usually it was slightly faster)

Golang Factorial: Elapsed Time for 10000 is 25
Kotlin: For 10000; elapsed Time: 26
Golang Factorial: Elapsed Time for 50000 is 709
Kotlin: For 50000; elapsed Time: 709
Golang Factorial: Elapsed Time for 100000 is 3019
Kotlin: For 100000; elapsed Time: 3019
Golang Factorial: Elapsed Time for 500000 is 102840
Kotlin: For 500000; elapsed Time: 102841
Golang Factorial: Elapsed Time for 1000000 is 437768
Kotlin: For 1000000; elapsed Time: 437768

Swift

Unfortunately, here I faced a problem with the creation of a factorial function due to language limitations (If you know how to bypass it, please let me know) I had to use BigInt library to implement this solution and performance was terrible.

I didn’t even finish all tests because it was very slow:

For 10000 time is 6.123746991157532
For 50000 time is 173.4142849445343
...

Here is the result of binding calls to Golang from Swift, which compared to the execution on the Android is much faster — it is even faster than the execution of standalone Go solution:

Golang Factorial: Elapsed Time for 10000 is 7
Swift: for 10000 time is 0.007142066955566406
Golang Factorial: Elapsed Time for 50000 is 153
Swift: for 50000 time is 0.15248596668243408
Golang Factorial: Elapsed Time for 100000 is 638
Swift: for 100000 time is 0.6384819746017456
Golang Factorial: Elapsed Time for 500000 is 18540
Swift: for 500000 time is 18.540263056755066
Golang Factorial: Elapsed Time for 1000000 is 85565
Swift: for 1000000 time is 85.56504201889038

Results

Summary

Even if Gomobile has a performance overhead I don’t think it is critical. According to the results we got from this test, it will not impact your application performance significantly.

Note: This example is very simple and it doesn’t take into account device memory and CPU profiling. The proper way to test Gomobile performance is to create a real-life Mobile use case with multithreading, data serialization, DB storage and Rx.

Environment

  • Golang: go1.13.5 darwin/amd64
  • Android: Google Pixel 2, AS 3.6
  • IOS device: iPhone XS, Xcode 11.3.1

--

--

Responses (1)