This blog has been updated and turned into a more formal guide on Arm Developer. You can find the latest guide here:
Arm's Neon technology is a 64/128-bit hybrid SIMD architecture designed to accelerate the performance of multimedia and signal processing applications, including video encoding and decoding, audio encoding and decoding, 3D graphics, speech and image processing.
This is the first part of a series of posts on how to write SIMD code for Neon using assembly language. The series will cover getting started with Neon, using it efficiently, and later, hints and tips for more experienced coders. We will begin by looking at memory operations, and how to use the flexible load and store with permute instructions.
We will start with a concrete example. You have a 24-bit RGB image, where the pixels are arranged in memory as R, G, B, R, G, B... You want to perform a simple image processing operation, like switching the red and blue channels. How can you do this efficiently using NEON?
Using a load that pulls RGB data linearly from memory into registers makes the red/blue swap awkward.
Code to swap channels based on this input is not going to be elegant - masks, shifting, combining. It is unlikely to be efficient.
Neon provides structure load and store instructions to help in these situations. They pull in data from memory and simultaneously separate values into different registers. For this example, you can use VLD3 to split up red, green and blue as they are loaded.
VLD3
Now switch the red and blue registers (VSWP d0, d2) and write the data back to memory, with reinterleaving, using the similarly named VST3 store instruction.
VSWP d0, d2
VST3
Neon structure loads read data from memory into 64-bit NEON registers, with optional deinterleaving. Stores work similarly, reinterleaving data from registers before writing it to memory.
The structure load and store instructions have a syntax consisting of five parts.
VLD
VST
Instructions are available to load, store and deinterleave structures containing from one to four equally sized elements, where the elements are the usual NEON supported widths of 8, 16 or 32-bits.
VLD1
VLD2
VLD4
Stores support the same options, but interleave the data from registers before writing them to memory.
Loads and stores interleave elements based on the size specified to the instruction. For example, loading two Neon registers with VLD2.16 results in four 16-bit elements in the first register, and four 16-bit elements in the second, with adjacent pairs (even and odd) separated to each register.
VLD2.16
Changing the element size to 32-bits causes the same amount of data to be loaded, but now only two elements make up each vector, again separated into even and odd elements.
Element size also affects endianness handling. In general, if you specify the correct element size to the load and store instructions, bytes will be read from memory in the appropriate order, and the same code will work on little and big-endian systems.
Finally, element size has an impact on pointer alignment. Alignment to the element size will generally give better performance, and it may be a requirement of your target operating system. For example, when loading 32-bit elements, align the address of the first element to at least 32-bits.
In addition to loading multiple elements, structure loads can also read single elements from memory with deinterleaving, either to all lanes of a Neon register, or to a single lane, leaving the other lanes intact.
The latter form is useful when you need to construct a vector from data scattered in memory.
Stores are similar, providing support for writing single or multiple elements with interleaving.
Structure load and store instructions support three formats for specifying addresses.
[ {,:}]
[{,:}]!
[{,:}],
Rm
You can also specify an alignment for the pointer passed in Rn, using the optional : parameter, which often speeds up memory accesses.
Rn
:
We have only dealt with structure loads and stores in this post. Neon also provides:
VLDR
VSTR
VLDM
VSTM
For more details on supported load and store operations, see the Arm Architecture Reference Manual. Detailed cycle timing information for the instructions can be found in the Technical Reference Manual for each core.
In my next post, we will look at efficiently handling arrays with lengths that are not a multiple of the vector size. Read it below.
[CTAToken URL = "https://community.arm.com/processors/b/blog/posts/coding-for-neon---part-2-dealing-with-leftovers" target="_blank" text="Read Part 2 - Dealing with Leftovers" class ="green"]