Почему прямой доступ к элементам структуры производит значительно больше кода сборки по сравнению с косвенным доступом в GCC?
здесь много тем, является ли прямой доступ или косвенный доступ (через указатель) быстрее при доступе к членам структуры, в C.
пример: C указатели против прямого доступа членов для структур
общее мнение, что прямой доступ будет быстрее (по крайней мере теоретически), так как разыменование указателя не используется.
поэтому я попробовал с куском кода в моей системе: GNU Embedded Tools GCC 4.7.4, генерирующий код для ARM (на самом деле ARM-Cortex-A15).
удивительно, прямой доступ был намного медленнее. Затем я сгенерировал коды сборки для объектного файла.
код прямого доступа имеет 114 строк кода сборки, а код косвенного доступа имеет 33 строки кода сборки. Что здесь происходит?
Ниже приведены код C и сгенерированный код сборки функций. Все структуры сопоставляются с внешней памятью, а элементы структуры имеют длину в один байт (без знака тип char.)
первая функция, с косвенным доступом:
void sub_func_1(unsigned int num_of, struct file_s *__restrict__ first_file_ptr, struct file_s *__restrict__ second_file_ptr, struct output_s *__restrict__ output_ptr)
{
if(LIKELY(num_of == 0))
{
output_ptr->curr_id = UNUSED;
output_ptr->curr_cnt = output_ptr->cnt;
output_ptr->curr_mode = output_ptr->_mode;
output_ptr->curr_type = output_ptr->type;
output_ptr->curr_size = output_ptr->size;
output_ptr->curr_allocation_type = output_ptr->allocation_type;
output_ptr->curr_allocation_localized = output_ptr->allocation_localized;
output_ptr->curr_mode_enable = output_ptr->mode_enable;
if(output_ptr->curr_cnt == 1)
{
first_file_ptr->status = BLOCK_IDLE;
first_file_ptr->type = USER_DATA_TYPE;
first_file_ptr->index = FIRST__WORD;
first_file_ptr->layer_cnt = output_ptr->layer_cnt;
second_file_ptr->status = DISABLED;
second_file_ptr->index = 0;
second_file_ptr->redundancy_version = 1;
output_ptr->total_layer_cnt = first_file_ptr->layer_cnt;
}
}
}
00000000 <sub_func_1>:
0: e3500000 cmp r0, #0
4: e92d01f0 push {r4, r5, r6, r7, r8}
8: 1a00001b bne 7c <sub_func_1+0x7c>
c: e5d34007 ldrb r4, [r3, #7]
10: e3a05008 mov r5, #8
14: e5d3c003 ldrb ip, [r3, #3]
18: e5d38014 ldrb r8, [r3, #20]
1c: e5c35001 strb r5, [r3, #1]
20: e5d37015 ldrb r7, [r3, #21]
24: e5d36018 ldrb r6, [r3, #24]
28: e5c34008 strb r4, [r3, #8]
2c: e5d35019 ldrb r5, [r3, #25]
30: e35c0001 cmp ip, #1
34: e5c3c005 strb ip, [r3, #5]
38: e5d34012 ldrb r4, [r3, #18]
3c: e5c38010 strb r8, [r3, #16]
40: e5c37011 strb r7, [r3, #17]
44: e5c3601a strb r6, [r3, #26]
48: e5c3501b strb r5, [r3, #27]
4c: e5c34013 strb r4, [r3, #19]
50: 1a000009 bne 7c <sub_func_1+0x7c>
54: e5d3400b ldrb r4, [r3, #11]
58: e3a05005 mov r5, #5
5c: e5c1c000 strb ip, [r1]
60: e5c10002 strb r0, [r1, #2]
64: e5c15001 strb r5, [r1, #1]
68: e5c20000 strb r0, [r2]
6c: e5c14003 strb r4, [r1, #3]
70: e5c20005 strb r0, [r2, #5]
74: e5c2c014 strb ip, [r2, #20]
78: e5c3400f strb r4, [r3, #15]
7c: e8bd01f0 pop {r4, r5, r6, r7, r8}
80: e12fff1e bx lr
вторая функция, с прямым доступом:
void sub_func_2(unsigned int output_index, unsigned int cc_index, unsigned int num_of)
{
if(LIKELY(num_of == 0))
{
output_file[output_index].curr_id = UNUSED;
output_file[output_index].curr_cnt = output_file[output_index].cnt;
output_file[output_index].curr_mode = output_file[output_index]._mode;
output_file[output_index].curr_type = output_file[output_index].type;
output_file[output_index].curr_size = output_file[output_index].size;
output_file[output_index].curr_allocation_type = output_file[output_index].allocation_type;
output_file[output_index].curr_allocation_localized = output_file[output_index].allocation_localized;
output_file[output_index].curr_mode_enable = output_file[output_index].mode_enable;
if(output_file[output_index].curr_cnt == 1)
{
output_file[output_index].cc_file[cc_index].file[0].status = BLOCK_IDLE;
output_file[output_index].cc_file[cc_index].file[0].type = USER_DATA_TYPE;
output_file[output_index].cc_file[cc_index].file[0].index = FIRST__WORD;
output_file[output_index].cc_file[cc_index].file[0].layer_cnt = output_file[output_index].layer_cnt;
output_file[output_index].cc_file[cc_index].file[1].status = DISABLED;
output_file[output_index].cc_file[cc_index].file[1].index = 0;
output_file[output_index].cc_file[cc_index].file[1].redundancy_version = 1;
output_file[output_index].total_layer_cnt = output_file[output_index].cc_file[cc_index].file[0].layer_cnt;
}
}
}
00000084 <sub_func_2>:
84: e92d0ff0 push {r4, r5, r6, r7, r8, r9, sl, fp}
88: e3520000 cmp r2, #0
8c: e24dd018 sub sp, sp, #24
90: e58d2004 str r2, [sp, #4]
94: 1a000069 bne 240 <sub_func_2+0x1bc>
98: e3a03d61 mov r3, #6208 ; 0x1840
9c: e30dc0c0 movw ip, #53440 ; 0xd0c0
a0: e340c001 movt ip, #1
a4: e3002000 movw r2, #0
a8: e0010193 mul r1, r3, r1
ac: e3402000 movt r2, #0
b0: e3067490 movw r7, #25744 ; 0x6490
b4: e3068488 movw r8, #25736 ; 0x6488
b8: e3a0b008 mov fp, #8
bc: e3066498 movw r6, #25752 ; 0x6498
c0: e02c109c mla ip, ip, r0, r1
c4: e082c00c add ip, r2, ip
c8: e28c3b19 add r3, ip, #25600 ; 0x6400
cc: e08c4007 add r4, ip, r7
d0: e5d39083 ldrb r9, [r3, #131] ; 0x83
d4: e08c5006 add r5, ip, r6
d8: e5d3a087 ldrb sl, [r3, #135] ; 0x87
dc: e5c3b081 strb fp, [r3, #129] ; 0x81
e0: e5c39085 strb r9, [r3, #133] ; 0x85
e4: e2833080 add r3, r3, #128 ; 0x80
e8: e7cca008 strb sl, [ip, r8]
ec: e5d4a004 ldrb sl, [r4, #4]
f0: e7cca007 strb sl, [ip, r7]
f4: e5d47005 ldrb r7, [r4, #5]
f8: e5c47001 strb r7, [r4, #1]
fc: e7dc6006 ldrb r6, [ip, r6]
100: e5d5c001 ldrb ip, [r5, #1]
104: e5c56002 strb r6, [r5, #2]
108: e5c5c003 strb ip, [r5, #3]
10c: e5d4c002 ldrb ip, [r4, #2]
110: e5c4c003 strb ip, [r4, #3]
114: e5d33005 ldrb r3, [r3, #5]
118: e3530001 cmp r3, #1
11c: 1a000047 bne 240 <sub_func_2+0x1bc>
120: e30dc0c0 movw ip, #53440 ; 0xd0c0
124: e30db0c0 movw fp, #53440 ; 0xd0c0
128: e1a0700c mov r7, ip
12c: e7dfc813 bfi ip, r3, #16, #16
130: e1a05007 mov r5, r7
134: e1a0900b mov r9, fp
138: e02c109c mla ip, ip, r0, r1
13c: e1a04005 mov r4, r5
140: e1a0a00b mov sl, fp
144: e7df9813 bfi r9, r3, #16, #16
148: e7dfb813 bfi fp, r3, #16, #16
14c: e1a06007 mov r6, r7
150: e7dfa813 bfi sl, r3, #16, #16
154: e58dc008 str ip, [sp, #8]
158: e7df6813 bfi r6, r3, #16, #16
15c: e1a0c004 mov ip, r4
160: e7df4813 bfi r4, r3, #16, #16
164: e02b109b mla fp, fp, r0, r1
168: e7df5813 bfi r5, r3, #16, #16
16c: e0291099 mla r9, r9, r0, r1
170: e7df7813 bfi r7, r3, #16, #16
174: e7dfc813 bfi ip, r3, #16, #16
178: e0261096 mla r6, r6, r0, r1
17c: e0241094 mla r4, r4, r0, r1
180: e082b00b add fp, r2, fp
184: e0829009 add r9, r2, r9
188: e02a109a mla sl, sl, r0, r1
18c: e28bbc65 add fp, fp, #25856 ; 0x6500
190: e58d600c str r6, [sp, #12]
194: e2899c65 add r9, r9, #25856 ; 0x6500
198: e3a06005 mov r6, #5
19c: e58d4010 str r4, [sp, #16]
1a0: e59d4008 ldr r4, [sp, #8]
1a4: e0251095 mla r5, r5, r0, r1
1a8: e5cb3000 strb r3, [fp]
1ac: e082a00a add sl, r2, sl
1b0: e59db00c ldr fp, [sp, #12]
1b4: e5c96001 strb r6, [r9, #1]
1b8: e59d6004 ldr r6, [sp, #4]
1bc: e28aac65 add sl, sl, #25856 ; 0x6500
1c0: e58d5014 str r5, [sp, #20]
1c4: e0271097 mla r7, r7, r0, r1
1c8: e0825004 add r5, r2, r4
1cc: e30d40c0 movw r4, #53440 ; 0xd0c0
1d0: e02c109c mla ip, ip, r0, r1
1d4: e0855008 add r5, r5, r8
1d8: e7df4813 bfi r4, r3, #16, #16
1dc: e5ca6002 strb r6, [sl, #2]
1e0: e5d59003 ldrb r9, [r5, #3]
1e4: e082600b add r6, r2, fp
1e8: e59db014 ldr fp, [sp, #20]
1ec: e0201094 mla r0, r4, r0, r1
1f0: e2866c65 add r6, r6, #25856 ; 0x6500
1f4: e59d1010 ldr r1, [sp, #16]
1f8: e306a53c movw sl, #25916 ; 0x653c
1fc: e0827007 add r7, r2, r7
200: e2877c65 add r7, r7, #25856 ; 0x6500
204: e082c00c add ip, r2, ip
208: e5c69003 strb r9, [r6, #3]
20c: e59d6004 ldr r6, [sp, #4]
210: e28ccc65 add ip, ip, #25856 ; 0x6500
214: e082500b add r5, r2, fp
218: e0820000 add r0, r2, r0
21c: e0824001 add r4, r2, r1
220: e085500a add r5, r5, sl
224: e0808008 add r8, r0, r8
228: e7c4600a strb r6, [r4, sl]
22c: e5c56005 strb r6, [r5, #5]
230: e5c73050 strb r3, [r7, #80] ; 0x50
234: e5dc3003 ldrb r3, [ip, #3]
238: e287704c add r7, r7, #76 ; 0x4c
23c: e5c83007 strb r3, [r8, #7]
240: e28dd018 add sp, sp, #24
244: e8bd0ff0 pop {r4, r5, r6, r7, r8, r9, sl, fp}
248: e12fff1e bx lr
и последняя часть, мои параметры компиляции:
# Compile options.
C_OPTS = -Wall
-std=gnu99
-fgnu89-inline
-Wcast-align
-Werror=uninitialized
-Werror=maybe-uninitialized
-Werror=overflow
-mcpu=cortex-a15
-mtune=cortex-a15
-mabi=aapcs
-mfpu=neon
-ftree-vectorize
-ftree-slp-vectorize
-ftree-vectorizer-verbose=4
-mfloat-abi=hard
-O3
-flto
-marm
-ffat-lto-objects
-fno-gcse
-fno-strict-aliasing
-fno-delete-null-pointer-checks
-fno-strict-overflow
-fuse-linker-plugin
-falign-functions=4
-falign-loops=4
-falign-labels=4
-falign-jumps=4
обновление:
примечание: Я удалил определения структуры, потому что были различия с версией моей собственной программы. Это на самом деле огромная структура, и она не эффективна, чтобы поставить здесь полностью.
As предложил, я избавляюсь от-fno-gcse, и сгенерированный asm не огромен, как раньше.
без-fno-gcse sub_func_1 генерирует тот же код, что и выше.
для sub_func_2:
00000084 <sub_func_2>:
84: e3520000 cmp r2, #0
88: e92d0070 push {r4, r5, r6}
8c: 1a000030 bne 154 <sub_func_2+0xd0>
90: e30d30c0 movw r3, #53440 ; 0xd0c0
94: e3a06008 mov r6, #8
98: e3403001 movt r3, #1
9c: e0030093 mul r3, r3, r0
a0: e3a00d61 mov r0, #6208 ; 0x1840
a4: e0213190 mla r1, r0, r1, r3
a8: e59f30ac ldr r3, [pc, #172] ; 15c <sub_func_2+0xd8>
ac: e0831001 add r1, r3, r1
b0: e2813b19 add r3, r1, #25600 ; 0x6400
b4: e5d34083 ldrb r4, [r3, #131] ; 0x83
b8: e1a00003 mov r0, r3
bc: e5d35087 ldrb r5, [r3, #135] ; 0x87
c0: e5c36081 strb r6, [r3, #129] ; 0x81
c4: e5c34085 strb r4, [r3, #133] ; 0x85
c8: e3064488 movw r4, #25736 ; 0x6488
cc: e2833080 add r3, r3, #128 ; 0x80
d0: e7c15004 strb r5, [r1, r4]
d4: e5d05094 ldrb r5, [r0, #148] ; 0x94
d8: e0844006 add r4, r4, r6
dc: e7c15004 strb r5, [r1, r4]
e0: e5d04095 ldrb r4, [r0, #149] ; 0x95
e4: e5d0c092 ldrb ip, [r0, #146] ; 0x92
e8: e5c04091 strb r4, [r0, #145] ; 0x91
ec: e3064498 movw r4, #25752 ; 0x6498
f0: e7d15004 ldrb r5, [r1, r4]
f4: e5c0c093 strb ip, [r0, #147] ; 0x93
f8: e5d04099 ldrb r4, [r0, #153] ; 0x99
fc: e5c0509a strb r5, [r0, #154] ; 0x9a
100: e5c0409b strb r4, [r0, #155] ; 0x9b
104: e5d33005 ldrb r3, [r3, #5]
108: e3530001 cmp r3, #1
10c: 1a000010 bne 154 <sub_func_2+0xd0>
110: e281cc65 add ip, r1, #25856 ; 0x6500
114: e3a06005 mov r6, #5
118: e2810b19 add r0, r1, #25600 ; 0x6400
11c: e1a0500c mov r5, ip
120: e5cc3000 strb r3, [ip]
124: e1a0400c mov r4, ip
128: e5cc6001 strb r6, [ip, #1]
12c: e5cc2002 strb r2, [ip, #2]
130: e5d0608b ldrb r6, [r0, #139] ; 0x8b
134: e5cc6003 strb r6, [ip, #3]
138: e306c53c movw ip, #25916 ; 0x653c
13c: e7c1200c strb r2, [r1, ip]
140: e5c52041 strb r2, [r5, #65] ; 0x41
144: e285503c add r5, r5, #60 ; 0x3c
148: e5c43050 strb r3, [r4, #80] ; 0x50
14c: e284404c add r4, r4, #76 ; 0x4c
150: e5c0608f strb r6, [r0, #143] ; 0x8f
154: e8bd0070 pop {r4, r5, r6}
158: e12fff1e bx lr
15c: 00000000 .word 0x00000000
2 ответов
TL: DR: не удается воспроизвести этот безумный вывод компилятора. Может быть, это сделал окружающий код + LTO?
у меня есть предложения по улучшению кода: см. материал ниже о копировании целых структур вместо копирования многих отдельных членов.
вопрос, который вы связали, касается доступа к глобальному значению типа напрямую против через глобальный указатель. На ARM, где требуется несколько инструкций или чтобы получить произвольный 32-битный указатель в регистр, передача указателей лучше, чем прямая ссылка на глобальную функцию.
см. этот пример на Проводник Компилятора Godbolt (рука gcc 4.8.2-O3)
struct example {
int a, b, c;
} global_example;
int load_global(void) { return global_example.c; }
movw r3, #:lower16:global_example @ tmp113,
movt r3, #:upper16:global_example @ tmp113,
ldr r0, [r3, #8] @, global_example.c
bx lr @
int load_pointer(struct example *p) { return p->c; }
ldr r0, [r0, #8] @, p_2(D)->c
bx lr @
(по-видимому, gcc ужасен при передаче структур val как функции args, см. код для byval(struct example by_val)
по ссылке godbolt.)
еще хуже, если у вас есть глобальный указатель: сначала вы нужно загрузить значение указателя, а затем другую нагрузку, чтобы разыменовать его. Это косвенные накладные расходы, которые обсуждались в вопросе, который вы связали. Если обе загрузки пропускают в кэше, вы платите задержку туда и обратно дважды. Адрес загрузки для 2-й загрузки недоступен до завершения первой загрузки, поэтому конвейеризация этих запросов памяти невозможна даже на неупорядоченном процессоре.
если у вас уже есть указатель в качестве аргумента, он будет в реестре. Разыменование это то же самое, что загрузка из глобального. (Но лучше, потому что вам не нужно самостоятельно вводить адрес global в реестр.)
ваш реальный код
я не могу воспроизвести ваш массивный выход asm с помощью ARM gcc 4.8.2 на Godbolt или локально с помощью ARM gcc 5.2.1. Однако я не использую LTO, так как у меня нет полной тестовой программы.
все, что я вижу, это немного больший код, чтобы сделать некоторый индекс математика.
bfi
является Bitfield Insert. Я думаю 144: e7df9813 bfi r9, r3, #16, #16
устанавливает верхнюю половину r9
= минимум половину r3
. Я не вижу, как это и mla
(целое число mul-accumulate) имеет большой смысл. Кроме извращенных результатов от -ftree-vectorize
, все, о чем я могу думать, это может быть -fno-gcse
имеет очень плохое влияние на версию GCC, которую вы тестировали.
это манипулирование константами, которые будут сохранены? Код, который вы фактически разместили #define
все до 0, которым GCC пользуется. (Он также использует тот факт, что у него уже есть 1
в реестре если curr_cnt == 1
, и магазины, которые регистрируются для second_file_ptr->redundancy_version = 1;
). Руку нет str [mem], immediate
или что-нибудь вроде x86 mov [mem], imm
.
если ваш компилятор выводится из кода с разными значениями для этих констант, компилятор будет делать больше работы для хранения разных вещей.
к сожалению, gcc плохо при слиянии узких магазинов в один более широкий store (давняя ошибка пропущенной оптимизации). Для x86 clang делает это, по крайней мере, в одном случае, сохраняя 0x0100
(256) вместо 0 и 1. (проверьте godbolt, перевернув компилятор на clang 3.7.1 или что-то еще и удалив ARM-специфические компиляторы. здесь mov word ptr \[rsi\], 256
где gcc использует
mov BYTE PTR [rsi], 0 # *first_file_ptr_23(D).status,
mov BYTE PTR [rsi+1], 1 # *first_file_ptr_23(D).type,
если вы тщательно расположите свои структуры, в этой функции будет больше возможностей для копирования блоков 4B.
это также может помочь имеют две идентичные подструктуры curr
, а неcurr
, вместо curr_size
и size
. Возможно, вам придется объявить это packed
чтобы избежать заполнения после подструктур. Ваши две группы членов не находятся в одном и том же порядке, что мешает компиляторам делать много блочного копирования в любом случае, когда вы выполняете кучу назначений.
это помогает gcc и clang копировать несколько байтов сразу, если вы делаете:
struct output_s_optimized {
struct __attribute__((packed)) stuff {
unsigned char cnt,
mode,
type,
size,
allocation_type,
allocation_localized,
mode_enable;
} curr; // 7B
unsigned char curr_id; // no non-curr id?
struct stuff non_curr;
unsigned char layer_cnt;
// Another 8 byte boundary here
unsigned char total_layer_cnt;
struct cc_file_s cc_file[128];
};
void foo(struct output_s_optimized *p) {
p->curr_id = 0;
p->non_curr = p->curr;
}
void bar(struct output_s_optimized *output_ptr) {
output_ptr->curr_id = 0;
output_ptr->curr.cnt = output_ptr->non_curr.cnt;
output_ptr->curr.mode = output_ptr->non_curr.mode;
output_ptr->curr.type = output_ptr->non_curr.type;
output_ptr->curr.size = output_ptr->non_curr.size;
output_ptr->curr.allocation_type = output_ptr->non_curr.allocation_type;
output_ptr->curr.allocation_localized = output_ptr->non_curr.allocation_localized;
output_ptr->curr.mode_enable = output_ptr->non_curr.mode_enable;
}
4.8.2 GCC компилирует foo()
в трех экземплярах: байт, 2Б и 4Б, даже на руку. Он компилирует bar()
до восьми копий 1B, а также clang-3.8 на x86. Таким образом, копирование целых структур может помочь вашему компилятору (а также убедиться, что копируемые данные расположены в одном порядке в обоих местах).
тот же код на x86: ничего нового
можно использовать -fverbose-asm
чтобы поставить комментарии к каждой строке. Для x86 вывод компилятора из gcc 6.1 -O3
очень похож между версиями, как вы можете видеть на Проводник Компилятора Godbolt. режимы адресации x86 могут индексировать глобальную переменную напрямую, поэтому вы видите такие вещи, как
movzx edi, BYTE PTR [rcx+10] # *output_ptr_7(D)._mode
# where rcx is the output_ptr arg, used directly
и
movzx ecx, BYTE PTR output_file[rdi+10] # output_file[output_index_7(D)]._mode
# where rdi = output_index * 1297 (sizeof(output_file[0])), calculated once at the start
(gcc, по-видимому, не заботится о том, что каждая инструкция имеет смещение 4B как часть режима адресации, но это вопрос ARM, поэтому я не буду идти на компромиссы между размером кода и количеством insn с помощью insns переменной длины x86.)
в общих (архитектурно-агностических) терминах, это то, что делают ваши инструкции:
global_structure_pointer->поле = Значение;
- загружает значение
global_structure_pointer
в регистр адресов. - добавляет смещение, представленное
field
в регистр адресации. - магазинах
value
в расположение памяти, адресованное регистром адресации.
global_structure[index].поле = Значение;
- загружает адрес
global_structure
в регистр адресов. - загружает значение
index
в арифметический регистр. - умножает арифметический регистр на размер
global_structure
. - добавляет арифметический регистр в регистр адресации.
- магазинах
value
в ячейку памяти, рассмотрены решения реестр.
ваша путаница, похоже, связана с непониманием того, что такое "прямой доступ" на самом деле.
этой прямой доступ:
global_structure.поле = Значение;
то, что вы считали прямым доступом, на самом деле является индексированным доступом.