Parallel processing in Android native code
For those who have been residing underneath particularly
large chunks of mineral deposits, it has become fashionable recently to build
games and other entertainment applications for mobile devices. In fact, a very
good argument can be made that the multi-billion (if not –trillion, by this
point) dollar industry that comprises the mobile computing space is largely
driven by the entertainment industry. The ease with which users could purchase
games for their devices, carry those devices around with them everywhere they
went, and play them in what my mother used to call the "quiet little moments of
life"—such as standing in lines, riding in vehicles, or waiting for one’s
dinner date to show up—is a huge part of why consumers are parting with large
amounts of cash in small amounts at a time.
But only if the games don’t suck.
Building smoothly-animated, responsive, well-performing
games on mobile devices is key to developers (and their companies) tapping in
to this lucrative revenue stream—the consumer patience for games that lag,
struggle, or simply run slowly is minimal. (That same ease by which consumers
can acquire your application also means it’s trivial for them to switch away
from your application and go buy somebody else’s.) And while this isn’t true
solely for games, most business applications simply don’t make use of the CPU
horsepower that a game often requires.
For developers on the Android platform, getting things to
run quickly will sometimes mean having to drop out of Java and write native
code. In fact, conventional wisdom holds that most, if not all, Android games
should be written such that the "core" of the game is written in native code,
if the game has any sort of even moderate animation requirements. However, for
a lot of developers, dropping down to native code means writing in C++, and for
a lot of developers, writing code in C++, particularly really fast code in C++,
means spending a lot of time in the debugger. Throw in a need to write that
code such that it can execute across multiple threads simultaneously, and it
becomes tempting to simply wait for the next generation of Android hardware, on
the grounds that "it’ll run great on that new hardware (and then I won’t have
to debug it)".
Fortunately, Intel has stepped up, and provided the Android
community with a version of their Threading Building Blocks (TBB)
library for the Android platform. Armed with that, and the Google Native
Development Kit, it’s a lot less frightening to write parts of your Android app
in C++, accessed from Java via the JNI interface, as a way of getting the best
of both worlds. If you’re not familiar with said library, Intel maintains a
website that contains all of the TBB documentation at http://software.intel.com/en-us/intel-tbb/,
and the download itself has a wide range of samples on how to use TBB, in the
"samples" directory off the root of the install. In this article, we’re just
going to focus on the TBB/Android integration.
By the way, Intel’s investment into Android certainly
doesn’t end here—they’ve also recently released a revamped Android emulator for
Intel-based CPU host platforms, so if you haven't already updated your Android
SDK Manager to pull it down, either do so, or download the new emulator
directly here. The speed difference is
remarkable, and given the cost (free), it seems a pretty easy win.
Getting Started
As with all libraries, the starting point for using TBB
begins with downloading the latest version from the open source website,
available at http://software.intel.com/en-us/intel-tbb/.
From the top menu, "Download" and then "Stable Releases", and the three
versions of TBB (Windows, Linux, Mac OSX) await the mouse click. Having said
that, though, the version the Android developer wants, regardless of the
platform on which they’re writing their Android application code, is the Linux
version. The TBB site points this out on the
Linux download link, but developers download so much stuff for their chosen
development platform that sometimes it takes a moment to realize that the TBB
library is for the target platform—Android—and not the developer host platform.
Once downloaded, open up the archive and store it somewhere
convenient. Inside the archive’s "lib" folder, the "android" directory contains
the Android-compatible binaries that will be needed to link in the TBB
libraries, and the header files live (as is typical) in the archive’s "include"
directory. Specifically, the TBB includes are in the "include/tbb" directory,
but since common C++ practice is to use the directory name as part of the
header include (so that the include looks like "#include <tbb/parallel_for.h>",
for example) as a way of figuring out which headers come from which library,
it’s better to just put the "<TBB_SDK>/include" directory on the include
path for the C++ compiler.
Basics
Architecturally, an Android application that uses TBB is
going to be something of a split personality: parts of it written in typical
Java/Android, and parts of it written in C++ and accessed via JNI. Technically, it’s impossible to write
a full C++ application for Android, though there are "hacks" that get around
this restriction. This means that any developer
looking to get started with TBB for Android is going to need to be familiar
with the JNI tools and API along with Java and C++, or else (more likely) the
work will be done split across developers—some familiar with Java, some with
C++, using the JNI layer as a "black box". Make no mistake, this will
complicate the development process some, but the performance benefits of both
native code and taking full advantage of the device hardware can more than
offset the costs.
Creating an Android project that uses JNI is trickier than a
"normal" Java-only Android project; it requires the use of the Android Native
Development Kit (NDK), which also then in turn requires native tools and build
scripts (Makefile). Thus, demonstrating the TBB here is going to be an
extremely simple demo, to keep things as simple as possible.
Step 1: "Push me!"
To start, we need an Android application, one which will "do
something" in response to a button press, and display the results of that
button press in a Toast message. Thus, after creating the project in your tool
set of choice (Eclipse, IDEA, or, my favorite, the venerable command-line), add
a big ol’ button to the main activity layout, a la:
="1.0" ="utf-8"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Push Me!"
android:onClick="onButtonPushed" />
</LinearLayout>
And remember, add a message-handler named "onButtonPushed
"
to the activity, as well:
package com.tedneward.tbbdemo;
import android.app.Activity;
import android.os.Bundle;
import android.view.*;
import android.widget.*;
public class MainActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public void onButtonPushed(View view)
{
Toast.makeText(this, "Pushed me!",
Toast.LENGTH_SHORT).show();
}
}
Nothing much to see, yet. Well, not entirely true: build it,
install it, run it, and it does exactly what it appears to do: display a
button, and when pushed, pops the "Pushed me!" Toast to the user. Hardly
impressive, but it’s a working scaffold from which to add JNI functionality,
and then the TBB bits.
Step 2: "Go Native!"
Next, we need to integrate a native JNI library (and the
NDK) as part of the build. The Android NDK has a number of samples, not to
mention docs, describing how to use JNI as part of an Android project, but a brief
recap/summary always helps. Assuming that the NDK is already installed and on
the PATH, integrating JNI code into an Android project is remarkably similar to
how it would work for any Java/JNI project: a Java class contains the
native-decorated static Java methods that will be the entry into the native
code library, a separate directory contains the C/C++ source and headers that
are generated by the "javah" Java utility, and a separate build script (an
Android.mk Makefile) describes the necessary pieces required by the "ndk_build"
tool to do the actual native compilation.
First, we create the Java class that will contain the native
calls:
package com.tedneward.tbbdemo;
public class TBBNative
{
static
{
System.loadLibrary("tbb-native");
}
public static native int calculate();
}
While it’s possible to create non-static native methods,
it’s usually easier and clearer to treat the native calls as entirely static,
maintaining no internal per-instance state. Note, also, the static initialization
block that calls System.loadLibrary();
which loads the specified native
library using whatever platform decorations are appropriate for the underlying
platform, whether that’s suffixing ".dll" (for Windows) or prefixing "lib" and
suffixing ".so" (for Linux and, in this case, Android).
Next, we need to write the C/C++ source (which the NDK
expects to be in a "jni" directory as a peer to the Android "src" directory, so
create that if you’ve not done so yet), but getting the exact declarations for
the code that will be linked from the JVM can be tricky, which is why the JDK
provides the "javah" command: once the Java class is compiled, "javah" takes a
class name at the command-line and generates a skeleton C header and
implementation file. Because it takes a Java class name (not source file) as
its input, the Java parts of the project need to be built before we can run
"javah":
javah -classpath ../bin/classes com.tedneward.tbbdemo.TBBNative
The "-classpath
" argument is necessary to point to the
temporary "classes" directory used as part of the Android build, and it spits
out a C-style header file, so we run it from the "jni" directory to get the
resulting header file in the right place. The header technically isn’t
necessary, but it does contain the declaration for the C implementation, and
based on the examples above, that declaration looks like:
JNIEXPORT jint JNICALL Java_com_tedneward_tbbdemo_TBBNative_calculate
(JNIEnv *, jclass);
All of this is described in excruciating detail in the Java JNI
documentation, so if all of this is brand-new, make sure to check that out or
the Sheng Liang book (http://www.amazon.com/The-Java-Native-Interface-Specification/dp/0201325772)
on the subject.
Implementing the native library is what TBB will be about,
but for now, let’s just make sure the build process works, so a simple C/C++
stub suffices:
#include "com_tedneward_tbbdemo_TBBNative.h"
JNIEXPORT jint JNICALL Java_com_tedneward_tbbdemo_TBBNative_calculate
(JNIEnv* jniEnv,
jclass jniClass)
{
return 0;
}
The NDK needs to know how to build this, and expects an
Android.mk Makefile to help it with the per-application-specific details:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := tbb-native
LOCAL_SRC_FILES := com_tedneward_tbbdemo_TBBNative.cpp
include $(BUILD_SHARED_LIBRARY)
The LOCAL_MODULE
setting must correspond (in both spelling
and case) to the value passed to the System.loadLibrary()
call in the
Native.java source file—this is the name of the native library that will be
generated. And, of course, the name of the source file can be whatever the
C/C++ compiler accepts as a legitimate filename—the javah-generated filenames
are often a bit awkward to work with over time.
Assuming the header, source and Android.mk files have no
typos, and that NDK is on the PATH, issuing an "ndk-build" from the "jni"
directory should result in something like the following:
Ted-Newards-MacBook-Pro:jni ted$ ndk-build
[armeabi] Compile++ thumb: tbb-native <= com_tedneward_tbb_Native.cpp
[armeabi] StaticLibrary : libstdc++.a
[armeabi] SharedLibrary : libtbb-native.so
[armeabi] Install : libtbb-native.so =>
libs/armeabi/libtbb-native.so
Ted-Newards-MacBook-Pro:jni ted$ ls ../libs/
armeabi/
Ted-Newards-MacBook-Pro:jni ted$ ls ../libs/armeabi/
libtbb-native.so*
Once the native library is built, running an "ant debug
install" again should compile cleanly, and install the new application—this
time with native code included—onto the device.
Step 3: "Parallelize It!"
The obvious next steps are to integrate TBB itself, which
means modifying the native code to use the TBB library to do some kind of
calculation in parallel. In a game, for example, with multiple pieces on the
field that all need to calculate their next steps, this is an easy case for TBB’s
"parallel_for", by putting the work into each leg of the parallelized for loop.
The TBB library needs to be built with particular settings in place as
part of the C++ compile, so some changes to the Android.mk file are required,
shown below:
LOCAL_PATH := $(call my-dir)
TBB_PATH := /opt/local/tbb42_20131003oss/
include $(CLEAR_VARS)
LOCAL_MODULE := tbb-native
LOCAL_SRC_FILES := com_tedneward_tbbdemo_TBBNative.cpp
LOCAL_CFLAGS += -DTBB_USE_GCC_BUILTINS -std=c++11 –I$(TBB_PATH)/include
LOCAL_LDLIBS := -ltbb -L./
-L$(TBB_PATH)/<path_to_libtbb_so>
include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(TBB_PATH)/lib/android
include $(CLEAR_VARS)
LOCAL_MODULE := libtbb
LOCAL_SRC_FILES := libtbb.so
include $(PREBUILT_SHARED_LIBRARY)
Correspondingly, the native code now includes the TBB
headers, and uses "parallel_for
" to do a rather pointless calculation. The calculation itself isn’t what’s important—it’s the fact
that we’ve got parallel calculations going on. From here, it shouldn’t be hard
to adapt whatever parallel/concurrent calculations your game needs to do,
whatever that may be.
Summary
Intel’s gone to some great lengths to support native coding
on the Android platform, and the Threading Building Blocks are a perfect
example of that. Getting the necessary parts in place to build a hybrid native/Java Android app aren’t trivial, but once in place,
the rest of the story gets a lot easier. Enjoy!
To learn more about Intel tools for the Android developer, visit Intel® Developer Zone for Android.
Other Related Articles
To learn more about Intel tools for the Android developer, visit Intel® Developer Zone for Android.