Jump to content

How to Use the Android NDK to Improve Performance

+ 2
  sarahkim's Photo
Posted May 09 2011 01:23 PM

The Android Native Development Kit, or NDK, is an add-on to SDK that helps you integrate native code—code that uses platform-specific features, generally exposed through C or C++ language APIs—within your Android application. The main motivation for developing parts of your app in native code is performance. The following excerpt from Learning Android uses the Fibonacci algorithm to demonstrate the performance improvements you can achieve with the NDK.
Because the NDK is well-suited for computationally intensive applications, I wanted to find an example where we can implement a relatively simple algorithm in both native code and Java to compare their relative speeds.

So I picked a Fibonacci algorithm as the example. It’s a fairly simple algorithm that can be implemented easily in both C and Java. Additionally, we can implement it recursively as well as iteratively.

As a quick refresher, the Fibonacci series is defined as:

fib(0)=0
fib(1)=1
fib(n)=fib(n-1)+fib(n-2)


So the Fibonacci sequence looks like this: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, and so on.

In this example, we are going to:

  • Create the Java class representing the Fibonacci library.
  • Create the native code header file.
  • Implement the native code by writing C code.
  • Compile everything and build a shared library.
  • Use this native code inside an Android activity.


FibLib

FibLib is where we declare our algorithms for computing the Fibonacci sequence. We have a total of four versions of the Fibonacci algorithm:

  • Java recursive version
  • Java iterative version
  • Native recursive version
  • Native iterative version


We’ll write the Java implementation in the example below and do the native ones in C later.

FibLib.java

package com.marakana;

public class FibLib {

  // Java implementation - recursive
  public static long fibJ(long n) {  // 
    if (n <= 0)
      return 0;
    if (n == 1)
      return 1;
    return fibJ(n - 1) + fibJ(n - 2);
  }

  // Java implementation - iterative
  public static long fibJI(long n) { // 
    long previous = -1;
    long result = 1;
    for (long i = 0; i <= n; i++) {
      long sum = result + previous;
      previous = result;
      result = sum;
    }
    return result;
  }

  // Native implementation
  static {
    System.loadLibrary("fib"); // 
  }

  // Native implementation - recursive
  public static native long fibN(int n); // 

  // Native implementation - iterative
  public static native long fibNI(int n);  // 
}


At this point, our FibLib is complete, but we still need to back the native methods with their C implementations. To do that, first we need to create the appropriate JNI header file.

The JNI Header File

The next step is to create the C header file based on our FibLib Java file. To do that, we use Java’s standard javah tool. Note that you must have the Java Development Kit (JDK) installed in order to find this tool in the JDK/bin directory.

Now, to create the C header, go to your project’s bin directory and execute:

[Fibonacci/bin]> javah -jni com.marakana.FibLib


javah -jni takes a Java class as the parameter. Not all the classes are in the Java classpath by default, so it is easiest to just change directory to your project’s bin directory. Here, we assume that the current working directory is part of your Java classpath and thus that javah -jni com.marakana.FibLib at this location will work.

The result should be a new file named com_marakana_FibLib.h. This is the C header file that we need to implement next.

Before implementing our native files, let’s organize our project a little bit. Although Eclipse did a lot to set up our Android application directories in a meaningful way thus far, it doesn’t yet offer that level of support and automation for NDK development. We are going to do a couple of steps manually here.

For one, create a directory named jni inside your Eclipse Fibonacci project. This will be the place where you’ll store all your native code and related files. You can create this directory from within Eclipse by selecting the Fibonacci project in Package Explorer, right-clicking on it, and choosing New→Folder.

Next, move this new header file into that folder:

[Fibonacci/bin]> mv com_marakana_FibLib.h ../jni/


You can look into this file:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_marakana_FibLib */

#ifndef _Included_com_marakana_FibLib
#define _Included_com_marakana_FibLib
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_marakana_FibLib
 * Method:    fibN
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibN
  (JNIEnv *, jclass, jint);

/*
 * Class:     com_marakana_FibLib
 * Method:    fibNI
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibNI
  (JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif


As you can see, this file is automatically generated and is not to be modified by the programmer directly. You may observe signatures for two of our native functions that we’re yet to implement:

...
JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibN
  (JNIEnv *, jclass, jlong);
...
JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibNI
  (JNIEnv *, jclass, jlong);
...


These are standard JNI signatures. They are generated by a naming convention indicating that the function contains code defined in Java as part of the com.marakana.FibLib class for the native methods fibN and fibNI. You can also see that both methods return jlong, a JNI-standardized integer value.

Their input parameters are also interesting: JNIEnv, jclass, and jlong. The first two are always part of a Java class, created to interface with native code. The JNIEnv points back to the virtual machine environment, and the next parameter points back to the class or object where this method is from; the parameter is jclass for a class method or jobject for an instance method. The third parameter, jlong, is just our input into the Fibonacci algorithm, or our n.

Now that we have this header file, it is time to provide its implementation in C.

C Implementation

We are going to create a C file that will implement our native algorithms. For simplicity’s sake, we’ll call it fib.c. Like the header file we looked at earlier, this file will reside in the jni folder. To create it, right-click on the jni folder and choose New→File. Save it as fib.c.

Note: When you open the C file, it might open up in another editor outside of Eclipse. That’s because the Java version of Eclipse typically doesn’t have support for C development. You could extend your Eclipse with C development tools by opening Eclipse and going to Help→Install New Software. Alternatively, you can just open the file with the standard Eclipse text editor by selecting the file and choosing Open With→Text Editor.

Next, we provide the implementation of the Fibonacci algorithm in C in this fib.c file, as shown below. The C versions of our algorithms are almost identical to the Java versions.

jni/fib.c

#include "com_marakana_FibLib.h" /*  */

/* Recursive Fibonacci Algorithm  */
long fibN(long n) {
  if(n<=0) return 0;
  if(n==1) return 1;
  return fibN(n-1) + fibN(n-2);
}

/* Iterative Fibonacci Algorithm  */
long fibNI(long n) {
  long previous = -1;
  long result = 1;
  long i=0;
  int sum=0;
  for (i = 0; i <= n; i++) {
    sum = result + previous;
    previous = result;
    result = sum;
  }
  return result;
}

/* Signature of the JNI method as generated in header file  */
JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibN
  (JNIEnv *env, jclass obj, jlong  n) {
  return fibN(n);
}
/* Signature of the JNI method as generated in header file  */
JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibNI
  (JNIEnv *env, jclass obj, jlong  n) {
  return fibNI(n);
}


Now that we have implemented C versions of Fibonacci, we want to build the shared library. To do that, we need an appropriate makefile.

The Makefile

To build the native library, the Android.mk makefile must describe our files. The file is shown below.

jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := fib
LOCAL_SRC_FILES := fib.c

include $(BUILD_SHARED_LIBRARY)


The makefile is a part of the standard Android make system. All we are adding here is our specific input (fib.c) and our specific output (the fib module). The name of the module we specify is important and will determine the name of the library based on the operating system convention. For example, on ARM-based systems, the output will be a libfib.so file.

Once we have this makefile, we’re ready to initiate the build.

Building the Shared Library

Assuming you have the NDK installed properly, you can now build the native shared library by running ndk-build in your project directory. Here, ndk-build is a tool in the directory where your NDK is installed. We assume you put this directory into your environment PATH.

At this point, you should have a subdirectory named lib containing your shared library. When you deploy the Fibonacci application in the next section, this library is packaged as part of the APK.

Note: The shared library is compiled to run on the emulator by default, so it’s based on ARM architecture.

Finally, we need an application to put this library to good use.

The Fibonacci Activity

The Fibonacci Activity asks the user to input a number. Then, it runs the four algorithms to compute the Fibonacci value of that number. It also times the computation and prints the results to the screen. This activity basically uses the FibLib class that in turn uses libfib.so for its native part. The example below shows the code.

FibActivity.java

package com.marakana;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class Fibonacci extends Activity implements OnClickListener {
  TextView textResult;
  Button buttonGo;
  EditText editInput;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Find UI views
    editInput = (EditText) findViewById(R.id.editInput);
    textResult = (TextView) findViewById(R.id.textResult);
    buttonGo = (Button) findViewById(R.id.buttonGo);
    buttonGo.setOnClickListener(this);
  }

  public void onClick(View view) {

    int input = Integer.parseInt(editInput.getText().toString()); // 

    long start, stop;
    long result;
    String out = "";

    // Dalvik - Recursive
    start = System.currentTimeMillis(); // 
    result = FibLib.fibJ(input);  // 
    stop = System.currentTimeMillis();  // 
    out += String.format("Dalvik recur  sive: %d (%d msec)", result, 
                         stop - start);

    // Dalvik - Iterative
    start = System.currentTimeMillis();
    result = FibLib.fibJI(input); // 
    stop = System.currentTimeMillis();
    out += String.format("\nDalvik iterative: %d (%d msec)", result, 
                         stop - start);

    // Native - Recursive
    start = System.currentTimeMillis();
    result = FibLib.fibN(input); // 
    stop = System.currentTimeMillis();
    out += String.format("\nNative recursive: %d (%d msec)", result, 
                         stop - start);

    // Native - Iterative
    start = System.currentTimeMillis();
    result = FibLib.fibNI(input); // 
    stop = System.currentTimeMillis();
    out += String.format("\nNative iterative: %d (%d msec)", result, 
                         stop - start);

    textResult.setText(out); // 
  }
}


Testing That It All Works

At this point, we can fire up the Fibonacci application and run some tests on it. Keep in mind that larger values for n take quite a bit longer to process, especially using the recursive algorithms. One suggestion would be to keep n in the 25–30 range. Also keep in mind that we are doing all this processing on Activity’s main UI thread, and blocking that thread for a long period of time will lead to the Application Not Responding (ANR) error (see image below). As an exercise, you might want to move the actual calculation into an AsyncTask to prevent blocking the main thread.

Application Not Responding dialog
Attached Image

As you run some tests, you might notice that the native version of the algorithm runs about one order of magnitude faster than the Java implementation (see below).

Fibonacci of 33
Attached Image

These results alone should provide enough motivation to consider moving some of your computationally intensive code into native code. NDK makes the job of integrating native code into your app much simpler.

Learning Android

Learn more about this topic from Learning Android.

If you're new to the Android mobile operating system, Learning Android is the perfect way to master the fundamentals. This gentle introduction shows you how to use Android's basic building blocks to develop user interfaces, store data, and more. You'll build an example application throughout the course of book, adding new features with each chapter. You'll also build your own toolbox of code patterns that will help you program any type of Android application with ease.

See what you'll learn


Tags:
1 Subscribe


4 Replies

 : Dec 21 2012 02:35 AM
REMOVED
 : Dec 22 2012 02:46 PM
Hello,

Thanks for the great post. I will really follow the steps and will contact you if need any further guidance.
Thanks and Regards,
Nishant Desai,
CEO & Founder
Kinsh Technologies

Professional Website Development | Professional SEO Services

http://www.kinsh.in
http://www.seoindianexperts.com
0
  bluezoorro's Photo
Posted Feb 19 2013 01:19 PM

Thats great post! Thanks.

http://www.sbwire.co...acts-208736.htm
0
  zekez23's Photo
Posted Jun 26 2013 11:47 AM

Hi sarahkim,

I know this thread is old/stale but I’m perplexed by one aspect of building the JNI Header file. Admit tingly I am a bit of a “newbie” when it comes to integrating Native Android development strategies and some of this is still “over my head”, but I thought my “discovery” was worth mentioning.

During the ndk-build process I kept on getting an error of the variety “conflicting types for…” (my fibN declaration) indicating a “previous declaration of …”(fibN) “was here”.

I was able to get passed this error by editing the (not to be edited) “machine generated file” and changing the JNI signature for the fibN method from :

JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibN
(JNIEnv *, jclass, jint);

To:

JNIEXPORT jlong JNICALL Java_com_marakana_FibLib_fibN
(JNIEnv *, jclass, jlong);

Clearly I’m not going to go around editing machine generated files all day (and I’m sure it is not a “best practice” by any means).
Is there any way to specify (in the build process perhaps) some way of mapping the correct/desired type(jlong instead of jint) ? Does it make more sense to modify the fib.c file instead ?

Thank you in advance.