C结构中的自动字段重新排序以避免填充

时间:2009-05-15 07:57:24

标签: c data-structures memory-optimization

我花了几分钟手动重新排序结构中的字段以减少填充效果[1],感觉就像几分钟太多了。我的直觉是说我的时间可能更好地花在编写Perl脚本上,或者为我做这种优化。

我的问题是这是否也是多余的;是否已经有一些我不知道的工具,或者我应该能够启用[2]打包结构的一些编译器功能?

这个问题更加复杂,因为需要在几个不同的架构中对其进行一致优化,因此无论使用何种工具都需要能够考虑不同的结构对齐和指针大小。

编辑:快速澄清 - 我想要做的是重新排序源代码中的字段以避免填充,而不是在没有填充的情况下“编译”结构。

编辑#2:另一个复杂因素:根据配置,某些数据类型的大小也可能会发生变化。显而易见的是针对不同体系结构的指针和指针差异,但也有浮点类型(16,32或64位,取决于'精确性'),校验和(8位或16位取决于“速度”)和一些其他不明显的东西。

[1]有问题的结构在嵌入式设备上被实例化了数千次,因此结构的每个4字节减少可能意味着 go no-go之间的差异这个项目。

[2]可用的编译器是GCC 3. *和4. *,Visual Studio,TCC,ARM ADS 1.2,RVCT 3. *以及其他一些更加模糊的编译器。

8 个答案:

答案 0 :(得分:6)

如果您可以挤出存储的每个单词都很关键,那么我必须建议手动优化结构。一个工具可以为你最佳地安排成员,但是它不知道,例如,这个以16位存储的值实际上永远不会超过1024,所以你可以窃取的高6位此值超过此处 ...

因此,人类几乎肯定会在这份工作中击败机器人。

[编辑]但似乎你真的不想为每个架构手动优化你的结构。也许你真的有很多架构需要支持?

我认为这个问题不适用于一般解决方案,但您可能能够将您的领域知识编码为自定义Perl / Python / something脚本,为每个架构生成结构定义。

此外,如果所有成员的大小都是2的幂,那么只需按大小排序成员(最大的第一个)就可以得到最佳的打包。在这种情况下,你可以使用好的老式的基于宏的结构建筑 - 像这样:

#define MYSTRUCT_POINTERS      \
    Something*  m_pSomeThing;  \
    OtherThing* m_pOtherThing; 

#define MYSTRUCT_FLOATS        \
    FLOAT m_aFloat;            \
    FLOAT m_bFloat;

#if 64_BIT_POINTERS && 64_BIT_FLOATS
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS MYSTRUCT_FLOATS
#else if 64_BIT_POINTERS
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS
#else if 64_BIT_FLOATS
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_FLOATS
#else
    #define MYSTRUCT_64_BIT_MEMBERS
#endif

// blah blah blah

struct MyStruct
{
    MYSTRUCT_64_BIT_MEMBERS
    MYSTRUCT_32_BIT_MEMBERS
    MYSTRUCT_16_BIT_MEMBERS
    MYSTRUCT_8_BIT_MEMBERS
};

答案 1 :(得分:6)

有一个名为pstruct的Perl脚本,通常包含在Perl安装中。该脚本将转储结构成员偏移量和大小。您可以修改pstruct或使用其输出作为制作实用程序的起点,该实用程序按您希望的方式打包您的结构。

$ cat foo.h 
struct foo {
    int x;
    char y; 
    int b[5];
    char c;
};

$ pstruct foo.h
struct foo {
  int                foo.x                      0       4
  char               foo.y                      4       1
                     foo.b                      8      20
  char               foo.c                     28       1
}

答案 2 :(得分:2)

大多数C编译器都不会这样做,因为你可以做一些奇怪的事情(比如在结构中获取一个元素的地址,然后使用指针魔法来访问其余部分,绕过编译器)。一个着名的例子是AmigaOS中的双链表,它使用守护节点作为列表的头尾(这使得在遍历列表时可以避免ifs)。监护人头节点始终具有pred == null,尾节点将具有next == null,开发人员将两个节点转换为单个三指针结构head_next null tail_pred。通过使用head_nextnull的地址作为头节点和尾节点的地址,它们保存了四个字节和一个内存分配(因为它们只需要整个结构一次)。

所以你最好的选择可能是将结构编写为伪代码,然后编写一个预处理器脚本,从中创建真实的结构。

答案 3 :(得分:0)

看看#pragma pack。这会更改编译器如何对齐结构中的元素。您可以使用它来强制它们紧密包装在一起,没有空格。

See more details here

答案 4 :(得分:0)

它也取决于平台/编译器。如上所述,大多数编译器将所有内容填充到4字节对齐(或更糟!),因此假设一个结构有2个短路和一个长:

short
long
short

将占用12个字节(填充2 * 2个字节)。

将其重新排序为

short
short
long

仍将占用12个字节,因为编译器会将其填充以更快地访问数据(这是大多数桌面的默认设置,因为他们更喜欢快速访问内存使用情况)。您的嵌入式系统有不同的需求,因此无论如何都必须使用#pragma pack。

至于重新排序的工具,我只需(手动)重新组织您的结构布局,以便将不同的类型放在一起。首先把所有的短裤放进去,然后把所有的短裤放进去等等。如果你要完成包装,这就是工具无论如何都会做的。在类型之间的转换点,中间可能有2个字节的填充,但我不认为值得担心。

答案 5 :(得分:0)

编译器可能不会通过自己的头重新排序结构中的字段。标准要求字段应按其定义的顺序排列。做其他事可能会以微妙的方式破坏代码。

当你写作时,当然完全有可能制作某种代码生成器,以有效的方式在字段周围进行混乱。但我更喜欢手动这样做。

答案 6 :(得分:0)

考虑如何制作这样的工具...我想我会从调试信息开始。

从源头获取每个结构的大小是一件痛苦的事。它重叠了编译器已经完成的许多工作。我对ELF不太熟悉,无法准确地说明如何从调试二进制文件中提取结构大小信息,但我知道信息存在是因为调试器可以显示它。或许obudump或binutils包中的其他内容可以为您轻松获取(至少对于使用ELF的平台)。

获得信息后,其余部分非常简单。将成员从大到小排序,尽可能保持原始结构的排序。使用perl或python,甚至可以很容易地将其与源的其余部分交叉引用,甚至可以保留注释或#ifdef,具体取决于它们的使用方式。最大的痛苦是在整个代码库中更改结构的所有初始化。让人惊讶。

这就是事情。这听起来真的很好,但我不知道有任何这样的现有的工具可以做到这一点,当你自己写的时候......我想你已经能够手动重新排序大多数你节目中的结构。

答案 7 :(得分:0)

我有同样的问题。正如另一个答案中所建议的那样,pstruct可能有所帮但是,它确实提供了我们所需要的。实际上pstruct使用gcc提供的调试信息。我根据同样的想法写了另一个脚本。

您必须使用STUBS调试信息(-gstubs)生成程序集文件。 (有可能从矮人那里获得相同的信息,但我使用的方法与pstruct相同)。如果不修改编译过程,这是一个很好的方法是将"-gstubs -save-temps=obj"添加到编译选项中。

以下脚本读取程序集文件,并检测在结构中添加额外字节的时间:

    #!/usr/bin/perl -n

    if (/.stabs[\t ]*"([^:]*):T[()0-9,]*=s([0-9]*)(.*),128,0,0,0/) {
       my $struct_name = $1;
       my $struct_size = $2;
       my $desc = $3;
       # Remove unused information from input
       $desc =~ s/=ar\([0-9,]*\);[0-9]*;[-0-9]*;\([-0-9,]*\)//g;
       $desc =~ s/=[a-zA-Z_0-9]+://g;
       $desc =~ s/=[\*f]?\([0-9,]*\)//g;
       $desc =~ s/:\([0-9,]*\)*//g;
       my @members = split /;/, $desc;
       my ($prev_size, $prev_offset, $prev_name) = (0, 0, "");
       for $i (@members) {
          my ($name, $offset, $size) = split /,/, $i;
          my $correct_offset = $prev_offset + $prev_size;
          if ($correct_offset < $offset) {
             my $diff = ($offset - $correct_offset) / 8;
             print "$struct_name.$name looks misplaced: $prev_offset + $prev_size = $correct_offset < $offset (diff = $diff bytes)\n";
          }
          # Skip static members
          if ($offset != 0 || $size != 0) {
            ($prev_name, $prev_offset, $prev_size) = ($name, $offset, $size);
          }
       }
    }

调用它的好方法:

find . -name *.s | xargs ./detectPaddedStructs.pl | sort | un