In this recipe, we will compare performance between Kotlin and C++ by implementing a Fibonacci calculator in both languages. This will introduce you to NDK performance optimizations and teach you how to pass values between Kotlin and C++.

Pre-requisites
This recipe builds upon the last one’s code. This saves time, and skips all the setup work needed for a new project. If you haven’t completed the previous recipe in this series on Android NDK, I recommend going through Building a Hello World App using Android NDK and C++ first, and then coming here once you’re through.
π What Youβll Learn
β How to pass primitive data types (int) between Kotlin and C++
β How to perform fast mathematical calculations in native code
β How to measure performance differences between Kotlin and C++
π Step 1: Modify Native Code to Implement Fibonacci in C++
1. Open native-lib.cpp (inside app/src/main/cpp/).
2. Modify it to include an iterative Fibonacci function:
#include <jni.h>
extern "C" JNIEXPORT jint JNICALL
Java_com_example_ndkhelloworld_MainActivity_fibonacciNative(
JNIEnv *env,
jobject,
jint n
) {
if (n <= 1) return n;
int a = 0, b = 1, temp;
for (int i = 2; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
return b;
}
π What This Code Does
β’ fibonacciNative() β Uses iteration (faster and better for large n) to figure out the Fibonacci value for a given limit.
π Step 2: Update CMakeLists.txt
Make sure your CMakeLists.txt includes:
add_library(native-lib SHARED native-lib.cpp)
No changes needed if youβve set it up from Recipe 1.
π Step 3: Expose Native Methods in Kotlin
1. Open MainActivity.kt.
2. Add one new native function declaration:
external fun fibonacciNative(n: Int): Int
π Step 4: Implement Fibonacci in Kotlin
Now, letβs implement the same Fibonacci logic in Kotlin for comparison.
1. Add the following Kotlin iterative Fibonacci function to the bottom of MainActivity.kt.
private fun fibonacciKotlin(n: Int): Int {
if (n <= 1) return n
var a = 0
var b = 1
for (i in 2..n) {
val temp = a + b
a = b
b = temp
}
return b
}
π Step 5: Benchmark Performance
1. Modify onCreate() in MainActivity.kt to measure execution time.
Start by adding a benchmarking function to measure average time for a number of iterations.
// Benchmarking utility to measure average execution time
private fun benchmark(iterations: Int, function: () -> Int): Long {
var totalTime = 0L
repeat(iterations) {
val start = System.nanoTime()
function()
totalTime += (System.nanoTime() - start)
}
return totalTime / iterations // Return the average time
}
2. Now update the onCreate
function to run our tests on both Kotlin and Native C++ executions.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val n = 40 // Change this value to test different scenarios
val iterations = 1000 // Running multiple times for better benchmarking
val resultText = findViewById<TextView>(R.id.textView1)
// Measure Kotlin Iterative Fibonacci
val kotlinIterTime = benchmark(iterations) { fibonacciKotlin(n) }
// Measure Native Iterative Fibonacci
val nativeIterTime = benchmark(iterations) { fibonacciNative(n) }
resultText.text = """
Kotlin Iterative: Processed in ${kotlinIterTime} ns
Native Iterative: Processed in ${nativeIterTime} ns
""".trimIndent()
}
π Step 6: Run the App & Compare
1. Run the app on a real device for accurate timing.
2. Observe the execution times for different Fibonacci implementations.
3. Increase n (e.g., 50-100) and notice the difference!
π Expected Output
On a mid-range phone, you might see something like:
Kotlin Iterative: Processed in 134346 ns
Native Iterative: Processed in 34146 ns
π There’s A Caveat
I will admit, I cheated a bit.
When calling the native function just once, Kotlin often appeared faster. However, when I ran it multiple times (100+ iterations), native code consistently outperformed Kotlin. This happens because the first JNI call has high overhead, including library loading, method lookup, and Java-Native context switching, making native execution seem slow. Meanwhile, ARTβs Just-In-Time (JIT) compilation optimizes Kotlin functions over time, improving their performance. But when I repeated the calls, the JNI overhead became negligible, and native codeβs direct execution and CPU caching advantages became apparent. This explains why single-call benchmarks can be misleading – NDK only shines when running intensive computations repeatedly. β‘οΈ
π Key Takeaways
β Kotlin code is much faster than native C++ if the number of calls to a function are few
β Native C++ is significantly faster than Kotlin if the number of calls to a function are many or if the computation is complex
π Bonus Challenge
β Challenge 1: Write a new native C++ function that calculates factorial and calls it from Kotlin to measure performance with factorial compute.
π Whatβs Next?
Now that we’ve understood NDK performance benefits, we’re ready for a juicy, real-world use case. Let’s dive into a scenario where computation is intense and repetitive: image and audio processing! This is where NDK is often used in real-world Android apps, so let’s dive into a sample app that processes images in real-time from a camera feed. This is a scenario where both speed and performance is critical to success. Let’s go!
Next Lesson: “Native Image Processing” (OpenCV + NDK)
Weβll be using C++ in the NDK to apply real-time image filters using OpenCV!
Closing Remarks
The source code for this recipe can be found at https://github.com/advaitsaravade/Android-NDK-2-Fast-Math. The source contains the answers to the Bonus Challenges above as well!
Happy coding!