Understanding x86 vs ARM Memory Alignment on Android

With Google’s recent release of the NDK (r6), it is now possible build Android application for x86 processors in addition to ARM. In general, this only involves rebuilding native code to port applications from ARM to x86. However, there are a few pitfalls to avoid.

One difference between x86 and ARM is the memory alignment requirements for data. Let’s look at a simple example:


This example just logs the size and offset of variables in TestStruct. The output for this program isn’t too surprising:

ARM
I/libtestjni( 5025): TestStruct (size: 12)
I/libtestjni( 5025): -- Var1 offset: 0
I/libtestjni( 5025): -- Var2 offset: 4
I/libtestjni( 5025): -- Var3 offset: 8

x86
I/libtestjni( 4175): TestStruct (size: 12)
I/libtestjni( 4175): -- Var1 offset: 0
I/libtestjni( 4175): -- Var2 offset: 4
I/libtestjni( 4175): -- Var3 offset: 8

But now, let’s change TestStruct to the following:


The output is now:

ARM
I/libtestjni( 4675): TestStruct (size: 24)
I/libtestjni( 4675): -- Var1 offset: 0
I/libtestjni( 4675): -- Var2 offset: 8
I/libtestjni( 4675): -- Var3 offset: 16

x86
I/libtestjni( 4079): TestStruct (size: 16)
I/libtestjni( 4079): -- Var1 offset: 0
I/libtestjni( 4079): -- Var2 offset: 4
I/libtestjni( 4079): -- Var3 offset: 12

The 8-byte (64-bit) mVar2 results in different layout for TestStruct. This is because ARM requires 8-byte alignment for 64-bit variables like mVar2. In most cases, this won’t cause problems because building for x86 vs ARM requires a full rebuild.

However, if an application serializes class or structures, this could cause a size mismatch. For example, say you create a save file on an ARM application and it writes TestStruct to a file. If you later load this file on an x86 platform, the class size in the application will be different than the saved file. As you can imagine, similar memory alignment issues can happen for network traffic that expects a specific memory layout.

The GCC compiler option “-malign-double” will generate the same memory alignment on x86 and ARM. However, since the OS was not built with this flag, it will break some OS calls.

You can control the alignment of variables through compiler attributes. So, if we tell GCC to align(8) for mVar2, x86 and ARM will have the same alignment:


The output is now:

ARM
I/libtestjni( 4675): TestStruct (size: 24)
I/libtestjni( 4675): -- Var1 offset: 0
I/libtestjni( 4675): -- Var2 offset: 8
I/libtestjni( 4675): -- Var3 offset: 16

x86
I/libtestjni( 4678): TestStruct (size: 24)
I/libtestjni( 4678): -- Var1 offset: 0
I/libtestjni( 4678): -- Var2 offset: 8
I/libtestjni( 4678): -- Var3 offset: 16

Once you understand the memory alignment difference between x86 and ARM, rebuilding your ARM Android NDK application for x86 should be pretty simple! Go grab the latest NDK and give it a try.

Tags:
For more complete information about compiler optimizations, see our Optimization Notice.

Comments

Fernando A.'s picture

Ugh, never thought about it, that's a critical consideration.
ObjectInputStream and ObjectOutputStream and following Android persistency framewok (ContentProvider, sqlite3, etc) whenever it's possible should address it in many of the cases, no?

Orion Granatir (Intel)'s picture

Yes, the Java side will handle these differences automatically (e.g. ObjectInputStream. ObjectOutputStream, etc). If an app use native C/C++ code middleware (e.g. the C/C++ version of sqlite3), then the memory alignment differences need to be kept in mind. Most middleware was originally written for x86, so most handle memory management properly.