Android™ x86 Support
Posted on August 12, 2014
Android™ tablets and smartphones equipped with Intel Atom CPUs are getting more and more popular. Therefore the Murl Engine allows creating Android™ apps with native x86-support since version 1.00.4754Beta.
Short Summary:
- Murl Engine provides Android™ x86 libs for all upcoming releases
- Android™ x86 support is activated per default
- Performance boost of about 1.6x / 2.5x
Background
Originally all Android™ devices were typically equipped with an ARM CPU. Since 2012 also Android™ devices with Intel x86 Atom CPUs are available. Some examples of Android™ devices running on Intel Architecture are: Samsung Galaxy Tab 3 10.1, Asus MemoPad FHD 10, Dell Venue 7/8, Motorola Razr I, Lenovo K900 or more recently the HP 7.
Intel developed a Binary Translator called Houdini which allows existing Android™ apps with native ARM code to run also on Android™ devices with Intel architecture. The Binary Translator reads native ARM code and on the fly translates it into equivalent x86 code.
Hence almost every ARM NDK application will run also on Android™ x86 devices without modification. However the NDK bridging technology is slower and may cause problems in certain cases. Therefore adding x86 support is in most cases the preferred option.
How It Works
All upcoming releases of the Murl Engine will also provide precompiled libraries for Android™ x86 devices. The Android™ build scripts will automatically build and include also x86 Android™ code per default.
The parameter MURL_ANDROID_CPUS
can be used in the common Make file projekt/common/gnumake/module_yourapp.mk
to select the supported CPU architectures for your app. Example:
MURL_ANDROID_CPUS += armeabi-v7a
MURL_ANDROID_CPUS += x86
The specified values correspond to the values of the APP_ABI parameter of the Android™ NDK build environment (ABI is short for "Application Binary Interface").
armeabi ARM-based CPUs that support at least the ARMv5TE instruction set armeabi-v7a ARM-based CPUs that support ARM Architecture v7-a instruction set with Thumb-2 instructions und VFPv3-D16 hardware FPU. x86 x86-based CPUs. The NDK build uses the following gcc flags: -march=i686 -mtune=atom -mstackrealign -msse3 -mfpmath=sse -m32
The Murl Engine build scripts additionally add the parameter -mssse3
. This is safe because all x86 Android™ devices support SSSE3 (see also https://ph0b.com/improving-x86-support-of-android-apps-libs-engines). Further information about the different architectures can be found in the NDK documentation in the file docs/CPU-ARCH-ABIS.html
.
You can check the lib directory in your APK archive to verify that the correct libraries have been included. Remember that the .apk file is an archive file compressed with the zip format. You can e.g. rename it to .zip and view/extract the content with your favorite compression tool.
Is It Worth It?
The performance gain resulting from the use of native x86 code compared to the use of the Binary Translator obviously depends on the type of app, the used features and furthermore on the version of the Binary Translator. To get a better feeling we did some simple benchmark testing with the Murl Engine. Please note that this simple tests are not suited to deduce a general conclusion about performance gain you would see in production code. Hence, you may wish to do your own testing.
An Asus Memo Pad FHD 10 was used as test device, which was generously lent to us by Intel. The tablet is equipped with a dual core 1.6 GHz Intel® Atom™ Z2560 CPU with an integrated PowerVR SGX544 GPU and has a 10.1 inch WUXGA display with a resolution of 1920 x 1200 pixel.
The test results with the Murl Engine show that on average the armeabi-v7a code runs 1.6 times slower and armeabi code runs 2.5 times slower than native x86 code. Detailed information about the performed tests can be found below.
App Size
The additional x86 files will of course increase the overall size of the APK file.
If the app size is a critical factor, you may want to consider splitting the app into individual APK files for every cpu architecture.
When doing so, please note that the version code of the x86 APK needs to be greater than the version code of the ARM APK
(parameter MURL_ANDROID_VERSION_CODE
in the common Make file).
x86 version code > armeabi-v7a version code > armeabi version code
Google Play will serve the compatible APK with the highest version number. Further information can be found in section Multiple APK Support of the Android™ developer documentation.
Benchmarks
An Asus Memo Pad FHD 10 has been used to execute the tests. The values reflect the needed processing time. Hence greater values are worse than lesser values.
Summary
x86 | armeabi-v7a | armeabi | |
---|---|---|---|
GenerateData | 100% | 150% | 249% |
Calc UInt32 | 100% | 190% | 239% |
Sort Array | 100% | 174% | 208% |
Calc Pi | 100% | 149% | 367% |
Scenegraph | 100% | 131% | 180% |
GenerateData
Create 20,000,000 random values and store them in three initial empty containers (UInt32Array, FloatArray, DoubleArray).
Util::TT800 rng(42); UInt32Array uInt32Array; FloatArray floatArray; DoubleArray doubleArray; for (UInt32 i = 0; i < mMax; i++) { UInt32 val = rng.Rand(); uInt32Array.Add(val); floatArray.Add(Float(val)); doubleArray.Add(Double(val)); }
t [ms] | x86 | armeabi-v7a | armeabi |
---|---|---|---|
average value | 5837 | 8728 | 14517 |
relative standard deviation | 0.99% | 0.79% | 1.79% |
relative difference | 100% | 150% | 249% |
Calc UInt32
Calculate a UInt32 value from the 20,000,000 randomly generated values in the container.
UInt32 a = 0; UInt32 lastIndex = uInt32Array.GetCount() - 1; for (UInt32 i = 0; i < lastIndex; i++) { a += (uInt32Array[lastIndex-i] * uInt32Array[i]) / 2147483648; }
t [ms] | x86 | armeabi-v7a | armeabi |
---|---|---|---|
average value | 130 | 247 | 310 |
relative standard deviation | 0.00% | 2.34% | 0.00% |
relative difference | 100% | 190% | 238% |
Sort Array
Sort a UInt32Array with 2,000,000 random numbers using the quicksort algorithm.
Util::SortArray(uInt32Array, false);
t [ms] | x86 | armeabi-v7a | armeabi |
---|---|---|---|
average value | 1581 | 2753 | 3282 |
relative standard deviation | 0.46% | 0.76% | 1.09% |
relative difference | 100% | 174% | 208% |
Calc PI
Calculate the number Pi using the Leibniz formula with 40,000,000 summands.
Double quaterpi = 1; UInt32 divisor = 3; for (UInt32 i=0; i < mMax; i++) { quaterpi -= Double(1) / divisor; divisor += 2; quaterpi += Double(1) / divisor; divisor += 2; }
t [ms] | x86 | armeabi-v7a | armeabi |
---|---|---|---|
average value | 1933 | 2883 | 7093 |
relative standard deviation | 0.05% | 0.14% | 0.08% |
relative difference | 100% | 149% | 367% |
Scenegraph
Process a scene graph with 20,000 sprite objects which will be rendered on random positions.
SInt32 widthX = (state->GetAppConfiguration()->GetDisplaySurfaceSizeX() - 64) / 2; SInt32 widthY = (state->GetAppConfiguration()->GetDisplaySurfaceSizeY() - 64) / 2; Graph::IRoot* root = state->GetGraphRoot(); const Graph::INodeArray& spriteList = mSpriteGroup->GetChildren(); UInt32 count = spriteList.GetCount(); Util::TT800 rng; for (UInt32 i = 0; i < count; i++) { Graph::ITransform* node = (dynamic_cast<Graph::IPlaneGeometry*>(spriteList[i]))->GetTransformInterface(); node->SetPosition(rng.RandSInt(-widthX, widthX),rng.RandSInt(-widthY, widthY),0); }
t [ms] | x86 | armeabi-v7a | armeabi |
---|---|---|---|
average value | 129.1 | 169. | 233.0 |
relative standard deviation | 2.45% | 1.05% | 2.00% |
relative difference | 100% | 131% | 180% |
Don't miss out on any update,
subscribe to our newsletter.