Arlleex 131 14 апреля, 2021 Опубликовано 14 апреля, 2021 · Жалоба 51 минуту назад, AlexRayne сказал: ...если функция не static, по прямо таки интересно - когда он её заинлайнит? Да вот возьмите хоть тот же ARM Compiler 6. Его тоже на оптимизации выше -O1 сложно заставить не инлайнить короткие функции. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 20 14 апреля, 2021 Опубликовано 14 апреля, 2021 (изменено) · Жалоба Меня больше всего удивило когда я однажды изобразил умножение на 10 с помощью сдвигов и сложений, а gcc заменил это на одну инструкцию mul, или даже деление там было. По переупорядочиванию, думаю компилятор все правильно делает. Его задача обеспечить меньший расход тактов на выполнение всей функции, он этого добивается переупорядочиванием независимых инструкций для оптимизации загрузки конвейера. Вам же хочется оптимизировать на скорость (или скорее на чистоту асм-листинга?) выполнения отдельный кусочек этой функции. Если такие детали важны то действительно надо написать этот код вручную а не выжимать из компилятора. Проверьте только, может быть помещение той инструкции между вашими ничего не стоит по времени. Изменено 14 апреля, 2021 пользователем amaora Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 184 14 апреля, 2021 Опубликовано 14 апреля, 2021 · Жалоба 3 часа назад, AlexRayne сказал: а какая тут может быть проблема? Например: Есть функция f1() из которой вызываются f2() и f3(). Если f2, f3() не инлайнятся, то их локальные переменные занимают одно и то же место на стеке, если инлайнятся - для каждой выделяется свой отдельный объём на стеке f1(). И если объём локальных переменных f2(), f3() достаточно велик, то может произойти переполнение стека. Конечно компилятор мог бы объединить массивы локальных переменных f2() и f3() в виде union, но IAR почему-то так не делает и создаёт отдельные наборы переменных. Впрочем - возможно другие компиляторы поступают по иному..... Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AlexRayne 7 15 апреля, 2021 Опубликовано 15 апреля, 2021 · Жалоба 9 часов назад, jcxz сказал: И если объём локальных переменных f2(), f3() достаточно велик, то может произойти переполнение стека. Я столкнулся с проблемой на стеке у ГЦЦ при слиянии функций. но у меня проблема лежала в диких case/switch почемуто гну не смог адекватно тогда это переварить, поехала раскладка аргументов ко вложенной функции на стеке. но это эксклюзив же. если функция большая и сложная - дублировать её тело компилятор не станет. даже ценой небольшого ускорения - на АРМах цена вызова невелика, а кеш на всех системах пенальти чувствует. проблема может быть если он массово инлайнит маленькие функции. но тут сложно приписать криминал - как правило это именно то что ожидается от компилера. 10 часов назад, jcxz сказал: но IAR почему-то так не делает и создаёт отдельные наборы переменных однако странно что ИАР не оптимизирует стек. у него же спец-инструмент вроде даже есть для оценки расхода стека. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
jcxz 184 15 апреля, 2021 Опубликовано 15 апреля, 2021 · Жалоба 49 минут назад, AlexRayne сказал: но это эксклюзив же. если функция большая и сложная - дублировать её тело компилятор не станет. Для того чтобы использовать много стека, функции не обязательно быть большой. 49 минут назад, AlexRayne сказал: однако странно что ИАР не оптимизирует стек. у него же спец-инструмент вроде даже есть для оценки расхода стека. Нет никакого инструмента. В проекте используется РТОС. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 131 3 февраля, 2022 Опубликовано 3 февраля, 2022 · Жалоба Приветствую еще раз всех. Есть следующий код (функция чтения данных из программно-аппаратного FIFO UART) class FIFO { ... volatile u32 rpos, wpos; // read and write position u8 buf[FIFO_RXBUF_SIZE]; // working data buffer volatile bool isOvf, isTrash; // "overflow" and "there is trash in the data buffer" flags }; s32 FIFO::read(u8 dst[], u32 len) { const u32 bufSize = FIFO_RXBUF_SIZE, crpos = rpos; // current read position u32 bslen = wpos - crpos; // busy space length definition if((s32)bslen < 0) bslen += bufSize; if(len > bslen) len = bslen; if(len > 0) { u32 nrpos = crpos + len; // next read position if(nrpos >= bufSize) nrpos -= bufSize; if(dst != NULL) { // copy data from buffer to the destination if(nrpos > crpos) memcpy(dst, buf + crpos, len); else { const u32 llen = nrpos, rlen = bufSize - crpos; memcpy(dst, buf + crpos, rlen); if(llen > 0) memcpy(dst + rlen, buf, llen); } } rpos = nrpos; // update read position return len; } else return isOvf || isTrash ? -1 : 0; } Функция read() возвращает запрошенное количество данных (с учетом заполненности буфера). Если данных в буфере нету - то либо оповещаем вызывающий код об ошибке (если было переполнение или в буфер прилетел "битый" с точки зрения периферии UART кадр-символ), либо говорим, что данных нет. Потенциальная проблема в следующем. Строка обновления rpos = nrpos должна быть вызвана именно в том месте, где она расположена в коде. Это должен гарантировать компилятор. Технически между копированием данных из буфера в пользовательский dst[] (строки с memcpy()) и rpos = nrpos нет ни зависимости по данным, ни по побочным эффектам. Соответственно, оптимизатор вполне может переместить rpos = nrpos куда-то выше копирований. Не зря же пишут в описании __schedule_barrier() о том, что обычные точки следования допускают перемежение операций с побочными эффектами, которые не влияют друг на друга. Должен ли я намеренно поставить барьер оптимизации перед rpos = nrpos, или же я сейчас упускаю какой-то момент? В листинге все чисто на всех уровнях оптимизации - но гарантированно ли это? Просто так воткнуть DMB() не катит - можно, но это не добавит мне понимания и академически-практических скилов. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
amaora 20 3 февраля, 2022 Опубликовано 3 февраля, 2022 (изменено) · Жалоба Я в таком месте ставлю барьер, а квалификатор volatile не использую. Можно через atomic обёртку обращаться к указателям чтения/записи, здесь это будет уместно. В разновидностях компиляторных барьеров я возможно чего-то не знаю, для меня простейший пример барьера это вызов функции пустышки из стороннего модуля реализация которого компилятору неведома. У такого вызова нет гарантий неизменности памяти (неизвестно, что делает эта функция), потому перед вызовом все записи в память будут завершены, а после него снова прочитаны требуемые значения из нелокальных переменных. Но практически используется другой метод (в зависимости от компилятора), чтобы избежать лишних расходов на вызов пустой функции, а эффект остаётся тот же. Инструкция упорядочивания записи для процессора соответственно тоже нужна, если такая имеется. Вставка DMB обычно уже сделана так, что содержит и компиляторный барьер и инструкцию. На псевдокоде: _rpos = atomic_get(&rpos); _wpos = atomic_get(&wpos); if (_rpos != _wpos) { _nrpos = _rpos + 1; memcpy(dst, &queue[rpos], ...); __compiler_memory_fence(); __memory_barrier_instruction(); atomic_set(&rpos, _nrpos); } Добавка: атомики здесь для того чтобы подчеркнуть намерения, что эти значения никак нельзя читать/писать по частям (побайтно или как-то ещё). Изменено 3 февраля, 2022 пользователем amaora Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 131 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Просматривал дизасм для следующей функции static struct { u32 volatile rpos, wpos; u8 buf[FIFO_TX_SIZE]; bool volatile isStart; u32 volatile reqSize; } TxFIFO; s32 writeTxFIFO(u8 src[], u32 len) { if(len > 0) { __disable_irq(); u32 const curRPos = TxFIFO.rpos, curReqSize = TxFIFO.reqSize, curReqNDTR = DMA1_Channel4->CNDTR; __enable_irq(); u32 const bufSize = FIFO_TX_SIZE; u32 nxtRPos = curRPos + curReqSize - curReqNDTR; if(nxtRPos >= bufSize) nxtRPos -= bufSize; u32 const curWPos = TxFIFO.wpos; u32 freeLen = nxtRPos; if((s32)(freeLen -= curWPos) <= 0) freeLen += bufSize; if(freeLen - 1 >= len) { u32 llen = 0, rlen = len, nxtWPos = curWPos + len; if(nxtWPos >= bufSize) nxtWPos -= bufSize, llen = nxtWPos, rlen -= llen; memcpy(&TxFIFO.buf[curWPos], src, rlen); if(llen > 0) memcpy(TxFIFO.buf, &src[rlen], llen); TxFIFO.wpos = nxtWPos; if(!TxFIFO.isStart) TxFIFO.isStart = true, reqTxDMA(&TxFIFO.buf[curWPos], rlen); } else return -1; } return 0; } Функция записывает в программный FIFO и запускает DMA-транзакцию, если последнее необходимо. Не суть. В начале небольшая критическая секция вычитывает volatile-переменные из памяти, чтобы дальше можно было оптимизировать. На балансной оптимизации, хоть и ожидал неоптимального размазывания команд, не вызывающих побочек, но не так сильно. Вот что получилось _ZN6nsUART11writeTxFIFOEPhj 0x08002670: b5f0 PUSH {r4-r7,lr} 0x08002672: b083 SUB sp,sp,#0xc 0x08002674: 2800 CMP r0,#0 0x08002676: d04f BEQ 0x8002718 ; _ZN6nsUART11writeTxFIFOEPhj + 168 0x08002678: 4604 MOV r4,r0 0x0800267a: f3ef8010 MRS r0,PRIMASK 0x0800267e: b672 CPSID i 0x08002680: 4b26 LDR r3,[pc,#152] ; [0x800271c] = 0x200002cc 0x08002682: 6818 LDR r0,[r3,#0] 0x08002684: 2143 MOVS r1,#0x43 0x08002686: 008e LSLS r6,r1,#2 0x08002688: 5999 LDR r1,[r3,r6] 0x0800268a: 1808 ADDS r0,r1,r0 0x0800268c: 4924 LDR r1,[pc,#144] ; [0x8002720] = 0x40020044 0x0800268e: 6849 LDR r1,[r1,#4] 0x08002690: 1a42 SUBS r2,r0,r1 0x08002692: 20ff MOVS r0,#0xff 0x08002694: 43c0 MVNS r0,r0 0x08002696: 2aff CMP r2,#0xff 0x08002698: d900 BLS 0x800269c ; _ZN6nsUART11writeTxFIFOEPhj + 44 0x0800269a: 1812 ADDS r2,r2,r0 0x0800269c: b662 CPSIE i 0x0800269e: 6859 LDR r1,[r3,#4] 0x080026a0: 1a52 SUBS r2,r2,r1 ... Есть ли способ (кроме всяких пюре-асм) сказать компилятору, что между точками А и Б в коде нужно проводить только операции загрузки из памяти? Т.е. я хочу, чтобы до критической секции в регистрах уже лежали все нужные адреса, смещения и т.д., а внутри критической секции было 3 инструкции LDR? А то он накомпилировал аж проверку if(nxtRPos >= bufSize) nxtRPos -= bufSize; внутри критической секции, а оно мне тут даром не надо Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 14 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Барьерчики попробуйте. На выбор: /** \brief Instruction Synchronization Barrier \details Instruction Synchronization Barrier flushes the pipeline in the processor, so that all instructions following the ISB are fetched from cache or memory, after the instruction has been completed. */ __attribute__((always_inline)) __STATIC_INLINE void __ISB(void) { __ASM volatile ("isb 0xF":::"memory"); } /** \brief Data Synchronization Barrier \details Acts as a special kind of Data Memory Barrier. It completes when all explicit memory accesses before this instruction complete. */ __attribute__((always_inline)) __STATIC_INLINE void __DSB(void) { __ASM volatile ("dsb 0xF":::"memory"); } /** \brief Data Memory Barrier \details Ensures the apparent order of the explicit memory operations before and after the instruction, without ensuring their completion. */ __attribute__((always_inline)) __STATIC_INLINE void __DMB(void) { __ASM volatile ("dmb 0xF":::"memory"); } Или можно без всяких барьеров, просто asm volatile ("":::"memory"); (тут я не уверен) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 131 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Нет, ничего из этого не помогает( Хотел еще попробовать изобразить как-то предварительное формирование адресов volatile-переменных, а их чтение организовать уже в критической секции (чтобы вынести из крит. секции суп из адресов, сдвигов и т.д.) u32 volatile * volatile i = &TxFIFO.rpos, * volatile j = &TxFIFO.reqSize, * volatile k = &DMA1_Channel4->CNDTR; __disable_irq(); u32 const curRPos = *i, curReqSize = *j, curReqNDTR = *k; __enable_irq(); Указатели i, j, k пометил как volatile для того, чтобы компилятор насоздавал точки следования и четко высчитал адреса строго до начала критической секции. Однако тут другая проблема - никак не сказать компилятору, чтобы он эти i, j, k хранил в регистрах CPU, а не на стеке функции. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 14 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Это IAR так своевольничает? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 131 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Keil, ARM Compiler 6. Тут не то что своевольничество (ведь все весьма даже соответствует поведению абстрактной машины Си/++), просто знать бы, мб у компилятора есть средства сказать ему какими-то ключевыми словами "делай что говорят". Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 14 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Барьеры для того и придуманы, чтобы говорить компилятору "делай что говорят" 🙂 Вот это : :"memory" в конце asm-вставки говорит компилятору, что вставка "портит" память, и поэтому компилятор не должен делать предположений о её сохранности после вставки. То есть, по идее как раз то, что нужно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
Arlleex 131 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Так из листинга выше видно, что инструкции, которые вклинились в критическую секцию, не имеют побочных эффектов, память не портят. Соответственно, компилятор законно разместил их где захотел. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 14 21 марта, 2023 Опубликовано 21 марта, 2023 · Жалоба Вот что у меня gcc выдал: 08003550 <writeTxFIFO(unsigned char*, unsigned long)>: 8003550: b5f8 push {r3, r4, r5, r6, r7, lr} 8003552: 0005 movs r5, r0 8003554: 2900 cmp r1, #0 8003556: d021 beq.n 800359c <writeTxFIFO(unsigned char*, unsigned long)+0x4c> 8003558: b672 cpsid i 800355a: 4c1b ldr r4, [pc, #108] ; (80035c8 <writeTxFIFO(unsigned char*, unsigned long)+0x78>) 800355c: 4a1b ldr r2, [pc, #108] ; (80035cc <writeTxFIFO(unsigned char*, unsigned long)+0x7c>) 800355e: 6823 ldr r3, [r4, #0] 8003560: 6960 ldr r0, [r4, #20] 8003562: 6852 ldr r2, [r2, #4] 8003564: b662 cpsie i 8003566: 181b adds r3, r3, r0 8003568: 1a9b subs r3, r3, r2 800356a: 2b07 cmp r3, #7 800356c: d818 bhi.n 80035a0 <writeTxFIFO(unsigned char*, unsigned long)+0x50> ... Вот как выглядят __attribute__( ( always_inline ) ) __STATIC_INLINE void __enable_irq(void) { __ASM volatile ("cpsie i" : : : "memory"); } __attribute__( ( always_inline ) ) __STATIC_INLINE void __disable_irq(void) { __ASM volatile ("cpsid i" : : : "memory"); } Всё как и должно быть, клоббер "memory" отрабатывает. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться