如何随机访问ARM处理器上的字对齐数据?

时间:2012-06-20 16:20:00

标签: c embedded alignment arm

至少ARMv5的ARM CPU不允许随机访问非字对齐的内存地址。这里详细描述了这个问题:http://lecs.cs.ucla.edu/wiki/index.php/XScale_alignment - 一种解决方案是重写代码或首先考虑这种对齐。然而,它没有说怎么样。给定一个字节流,其中我有2个或4个字节的整数,它们在流中没有字对齐。如何以智能方式访问这些数据而不会损失太多性能?

我有一个代码片段来说明问题:

#include <stdio.h>
#include <stdlib.h>

#define BUF_LEN 17

int main( int argc, char *argv[] ) {
    unsigned char   buf[BUF_LEN];
    int             i;
    unsigned short  *p_short;
    unsigned long   *p_long;

    /*  fill array  */
    (void) printf( "filling buffer:" );
    for ( i = 0; i < BUF_LEN; i++ ) {
        /* buf[i] = 1 << ( i % 8 ); */
        buf[i] = i;
        (void) printf( " %02hhX", buf[i] );
    }
    (void) printf( "\n" );

    /*  testing with short  */
    (void) printf( "accessing with short:" );
    for ( i = 0; i < BUF_LEN - sizeof(unsigned short); i++ ) {
        p_short = (unsigned short *) &buf[i];
        (void) printf( " %04hX", *p_short );
    }
    (void) printf( "\n" );

    /*  testing with long   */
    (void) printf( "accessing with long:" );
    for ( i = 0; i < BUF_LEN - sizeof(unsigned long); i++ ) {
        p_long = (unsigned long *) &buf[i];
        (void) printf( " %08lX", *p_long );
    }
    (void) printf( "\n" );

    return EXIT_SUCCESS;
}

在x86 CPU上,这是输出:

filling buffer: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
accessing with short: 0100 0201 0302 0403 0504 0605 0706 0807 0908 0A09 0B0A 0C0B 0D0C 0E0D 0F0E
accessing with long: 03020100 04030201 05040302 06050403 07060504 08070605 09080706 0A090807 0B0A0908 0C0B0A09 0D0C0B0A 0E0D0C0B 0F0E0D0C

在ATMEL AT91SAM9G20 ARMv5核心上我得到了(注意:这是该CPU的预期行为!):

filling buffer: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10
accessing with short: 0100 0100 0302 0302 0504 0504 0706 0706 0908 0908 0B0A 0B0A 0D0C 0D0C 0F0E
accessing with long: 03020100 00030201 01000302 02010003 07060504 04070605 05040706 06050407 0B0A0908 080B0A09 09080B0A 0A09080B 0F0E0D0C

所以我想要或者必须在不对齐的地址访问字节流:我将如何在ARM上有效地执行此操作?

3 个答案:

答案 0 :(得分:2)

您编写自己的打包/解包功能,它们在对齐的变量和未对齐的字节流之间进行转换。例如,

void unpack_uint32(uint8_t* unaligned_stream, uint32_t* aligned_var)
{
  // copy byte-by-byte from stream to var, you can fill in the details
}

答案 1 :(得分:1)

您的示例将在任何平台上演示问题。当然简单的解决方法:

unsigned char   *buf;
int             i;
unsigned short  *p_short;
unsigned long   p_long[BUF_LEN>>2];

如果你不能用更好的对齐来组织数据(更多的字节有时候可以提供更好的性能)那么就做那么明显并将所有内容都解决为32位并从那里删除部分,优化器会处理很多一个字中的短路和字节(实际上包括结构中的字节和短路,无论是结构还是从存储器中挑选的字节,都可能更昂贵,因为会有额外的指令,而不是你把所有东西作为单词传递,你必须做的你的系统工程)。

提取未对齐单词的示例。 (当然必须管理你的结尾)

a = (lptr[offset]<<16)|(lptr[offset+1]>>16);

从armv4到现在的所有手臂核都允许不对齐访问,大多数情况下默认情况下打开例外但您可以将其关闭。现在较旧的那些在单词内旋转,但是如果我没有弄错的话,其他人可以抓住其他字节通道。

进行系统工程,进行性能分析,并确定将所有内容作为单词移动是更快还是更慢。实际移动数据会产生一些开销,但如果一切都对齐,双方的代码运行速度会快得多。您是否可以减少数倍X数据移动,以便在生成和接收数据时提高2倍到4倍?

答案 2 :(得分:0)

此函数始终使用对齐的32位访问:

uint32_t fetch_unaligned_uint32 (uint8_t *unaligned_stream)
{
    switch (((uint32_t )unaligned_stream) & 3u)
    {
        case 3u:
            return ((*(uint32_t *)unaligned_stream[-3]) << 24)
                 | ((*(uint32_t *)unaligned_stream[ 1]) & 0xffffffu);
        case 2u:
            return ((*(uint32_t *)unaligned_stream[-2]) << 16)
                 | ((*(uint32_t *)unaligned_stream[ 2]) & 0x00ffffu);
        case 1u:
            return ((*(uint32_t *)unaligned_stream[-1]) <<  8)
                 | ((*(uint32_t *)unaligned_stream[ 3]) & 0x0000ffu);
        case 0u:
        default:
            return *(uint32_t *)unaligned_stream;
    }
}

它可能比分别读取和移动所有4个字节更快。