Автоматическое переупорядочение полей в структурах C, чтобы избежать заполнения

Я потратил несколько минут, чтобы вручную переупорядочить поля в структуре, чтобы уменьшить эффекты заполнения [1], что кажется на несколько минут лишним. Мои интуитивные ощущения говорят о том, что мое время, возможно, лучше потратить на написание сценария Perl или еще чего-нибудь, что бы сделать для меня такую ​​оптимизацию.

Мой вопрос, является ли это слишком избыточным; уже есть какой-то инструмент, о котором я не знаю, или какая-то функция компилятора, которую я должен иметь возможность включить [2] для упаковки структур?

Проблема еще более усложняется тем фактом, что ее необходимо последовательно оптимизировать для нескольких разных архитектур, поэтому любой используемый инструмент должен также учитывать различные выравнивания структуры и размеры указателей.

РЕДАКТИРОВАТЬ: быстрое пояснение - я хочу переупорядочить поле в исходном коде, чтобы избежать заполнения, а не "упаковать" структуру, как это происходит при компиляции без заполнения.

РЕДАКТИРОВАНИЕ № 2: Еще одно осложнение: в зависимости от конфигурации размеры некоторых типов данных также могут изменяться. Очевидными являются указатели и разность указателей для разных архитектур, а также типы с плавающей запятой (16, 32 или 64-битные в зависимости от "точности"), контрольные суммы (8 или 16-битные в зависимости от "скорости") и некоторые другие неочевидные вещи.

[1] Рассматриваемая структура создается тысячи раз на встроенном устройстве, поэтому каждое 4-байтовое сокращение структуры может означать разницу между " go" и " no" для этого проекта.

[2] Доступными компиляторами являются GCC 3.* и 4.*, Visual Studio, TCC, ARM ADS 1.2, RVCT 3.* и некоторые другие, более неясные.

9 ответов

Решение

Если каждое отдельное слово, которое вы можете выжать из хранилища, имеет решающее значение, тогда я должен рекомендовать оптимизировать структуру вручную. Инструмент может оптимально упорядочить элементы для вас, но он не знает, например, что это значение, которое вы храните здесь в 16 битах, на самом деле никогда не превышает 1024, поэтому вы можете украсть верхние 6 бит для этого значения сверх здесь...

Таким образом, человек почти наверняка победит робота на этой работе.

[Править] Но похоже, что вы действительно не хотите вручную оптимизировать свои структуры для каждой архитектуры. Может быть, вам действительно нужно поддерживать множество архитектур?

Я думаю, что эта проблема не поддается общему решению, но вы можете закодировать свои знания предметной области в специальный скрипт Perl/Python/ что-то, что генерирует определение структуры для каждой архитектуры.

Кроме того, если размеры всех ваших членов равны двум, то вы получите оптимальную упаковку, просто отсортировав элементы по размеру (сначала по величине). В этом случае вы можете просто использовать старое старомодное построение макроса на основе макросов - что-то вроде этого:

#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
};

Существует скрипт Perl, называемый pstruct, который обычно включается в установки 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
}

Большинство компиляторов Си не делают этого, основываясь на том факте, что вы можете делать странные вещи (например, брать адрес элемента в структуре и затем использовать магию указателя для доступа к остальным, минуя компилятор). Известным примером являются двойные связанные списки в AmigaOS, которые использовали узлы-хранители в качестве заголовка и конца списка (это позволяет избежать ifs при обходе списка). Головной узел хранителя всегда будет иметь pred == null и хвостовой узел будет иметь next == nullразработчики свернули два узла в единую структуру с тремя указателями head_next null tail_pred, Используя адрес head_next или null в качестве адреса головного и хвостового узлов они сохранили четыре байта и одно выделение памяти (поскольку им нужна была целая структура только один раз).

Поэтому, вероятно, лучше всего написать структуры в виде псевдокода, а затем написать скрипт препроцессора, который создает из этого реальные структуры.

Для этого есть инструмент под названиемpahole, это существует уже много лет:

https://manpages.ubuntu.com/manpages/jammy/man1/pahole.1.html

Он находит за вас дыры, а также предлагает оптимальный порядок полей.

Это также будет зависеть от платформы / компилятора. Как уже отмечалось, большинство компиляторов дополняют все до 4-байтового выравнивания (или еще хуже!), Поэтому предполагается, что структура имеет 2 шорта и длинную строку:

short
long
short

займет 12 байтов (с заполнением 2*2 байта).

изменить порядок, чтобы быть

short
short
long

будет по-прежнему занимать 12 байт, так как компилятор дополнит его, чтобы ускорить доступ к данным (что является значением по умолчанию для большинства настольных компьютеров, так как они предпочитают быстрый доступ по сравнению с использованием памяти). Ваша встраиваемая система имеет разные потребности, поэтому вам придется использовать пакет #pragma независимо от того.

Что касается инструмента для переупорядочения, я бы просто (вручную) реорганизовал вашу структуру структуры так, чтобы различные типы были размещены вместе. Сначала наденьте все шорты, затем наденьте все шорты и т. Д. Если вы собираетесь закончить упаковку, инструмент так или иначе будет делать. У вас может быть 2 байта в середине в точках перехода между типами, но я не думаю, что об этом стоит беспокоиться.

Компилятор не может переупорядочивать поля в структурах своей собственной головой. Стандарт обязывает, что поля должны быть расположены в том порядке, в котором они определены. Выполнение чего-либо еще может привести к поломке кода тонкими способами.

Когда вы пишете, конечно, вполне возможно создать какой-то генератор кода, который будет эффективно перемещаться по полям. Но я предпочитаю делать это вручную.

Посмотрите на #pragma pack. Это меняет способ компиляции элементов в структуре. Вы можете использовать его, чтобы заставить их быть тесно упакованными без пробелов.

Подробнее смотрите здесь

Думая о том, как мне сделать такой инструмент... Думаю, я начну с информации об отладке.

Получение размера каждой структуры из источника является болью. Это накладывается на большую работу, которую уже выполняет компилятор. Я не достаточно знаком с ELF, чтобы точно сказать, как извлечь информацию о размере структуры из двоичного файла отладки, но я знаю, что информация существует, потому что отладчики могут отображать ее. Возможно, objdump или что-то еще в пакете binutils может получить это для вас тривиально (по крайней мере, для платформ, использующих ELF).

После того, как вы получили информацию, все остальное довольно просто. Упорядочивайте элементы от самых маленьких до самых маленьких, стараясь сохранить как можно больше порядка исходной структуры. С perl или python было бы даже легко сослаться на него с остальными источниками и, возможно, даже сохранить комментарии или #ifdefs в зависимости от того, насколько чисто они использовались. Самой большой болью будет изменение всех инициализаций структуры во всей кодовой базе. Хлоп.

Вот вещь Звучит очень хорошо, но я не знаю ни одного такого существующего инструмента, который бы делал это, и к тому времени, когда вы напишите свой собственный... Я думаю, вы сможете вручную переупорядочить большинство структур в вашей программе.

У меня была такая же проблема. Как предлагается в другом ответе, 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
Другие вопросы по тегам