Перейти к содержанию
    

Странное предупреждение

Как вы можете знать, как работает memcpy() у ТС, если memcpy реализуется разработчиками каждого компилятора под каждую архитектуру? Вы знаете как каждый memcpy() реализован? У каждого своя реализация.

"из кода видно, что с вероятностью близкой к 100..." где-то я это уже слышал недавно? :wacko:

Так вот - у ТС-а IAR, и я им пользуюсь; у ТС-а (согласно строки выше) Cortex-M - и я под него сейчас отлаживаю. Аргументов достаточно?

 

Как правило, memcpy() реализован так, что из всех возможных реализаций код memcpy будет самый оптимальный, вплоть до ухода в асм. Если пользователь компилятора/библиотеки и решит написать свой копипаст, то он будет менее эффективный, в лучшем случае будет такойже, поэтому при копировании памяти не нужно замарачиваться и изобретать скоростной велосипед, а просто использовать memcpy().

С этим категорически не согласен! Попробуйте когда-нить не теоретизировать внустую, а скомпилировать и посмотреть разные варианты с копированием в цикле на разных компиляторах с включённой полной оптимизацией по скорости и с разными условиями цикла (короткий цикл/длинный, условия окончания и указатели - переменные или известны на этапе компиляции). И будете удивлены.

Вариант с memcpy() на коротких циклах, где кол-во проходов и/или указатели заданы переменными, проиграет варианту с простым for, так как ему не надо: a) сохранять содержимое из scratch регистров; б) делать вызов memcpy() и возврат; в) внутри memcpy() выполнять ветвление выбирая нужный вариант цикла копирования по условию длины цикла, выравниваний первого и второго аргумента и т.п.

На циклах с заранее (на этапе компиляции) известными условиями выполнения, for-вариант опять же будет как минимум не хуже, просто хотя-бы потому что компилятор зная условия выполнения цикла может подставить нужную последовательность команд (подобную оптимальному копированию внутри memcpy()), но с другими более удобными регистрами и их количеством - удобными для данной точки кода.

А некоторые компиляторы, так и вообще цикл for так оптимизируют (при определённых условиях) что готовому memcpy() и не снилось - в разы быстрее по скорости. Смотрим на компиляторы для DSP-ядер.

 

Посмотрите сколько лишних операций в for у ТС! какие-то приведения типов, операторы, << и |, дополнительный j, операции j++!!! УЖАС!!!

И что? Современные оптимизаторы творят чудеса. И им по-барабану Ваши жалкие потуги заменить операции индексирования на указатели и подобное - они это и сами хорошо делают, даже ещё и лучше. Так что если бы код автора не был такой кривой, результат работы оптимизирующего компилятора мог быть лучше memcpy().

Я в своё время, разрабатывая для DSP, немало заменил копирований через memcpy() на циклы for - это давало внушительный прирост скорости.

 

(кстати.... j++ достаточно медленный, по сравнению с ++j).

Опять чушь! Если предположить, что автор использует ARM, то что пре- что пост-инкремент - без разницы, и то и другое на ARM можно сделать в пределах одной команды выборки из памяти. Откройте мануал по системе команд и найдите разницу между: LDRB Rx, [Ry], #1 и LDRB Rx, [Ry, #1]!

 

В каких случаях? какие неточности?

Во-первых: memcpy там не к месту как я писал выше;

во-вторых: ничего не известно о значении size_to_take, а если она перед началом цикла может быть отрицательной (и i - тоже знаковое), что будет с вашим вариантом на memcpy()? :biggrin:

в-3-х: с чего Вы приводите входные аргументы к типу void *? А если у ТС-а DataBuffer и data_out объявлены к примеру с модификатором volatile, т.е. указывают на strong ordered memory? Например DataBuffer или data_out - это FIFO-буфера в некоей периферии, чувствительные к размерности и последовательности операций записи/чтения.

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

 

вы сами себе противоречите, причем сразу в одном посте.....

В чём именно противоречие? :wacko:

 

Если по исходнику понятно, что источник шире 8 байт приведет к перемешиванию бит, а в приемнике в итоге получается только 16 значащих бит,

то с уверенностью можно принять за факт их размеры 8 и 16 бит соответственно, и при необходимости заявлять "сам дурак",

Из "исходника" даже не ясно, что именно нужно этому горе-программисту (если data_out указывает на unsigned char) - поменять местами байты при копировании или может он хочет продублировать байты. Т.е. сделать так:

1. *(u16 *)&DataBuffer = __REVSH(*(u16 __packed *)&data_out[j]); j += 2;

или так:

2. *(u16 *)&DataBuffer = (uint)*(u8 *)&data_out[j++] * 0x101u;

а может и что другое.....

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

К сожалению, аборигены запомнили только, что нужно хлопать при приземлении.

Продвинутые еще впп научились строить, чтобы их посещали.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

ну на конец то предметный разговор, а не "слепая вера"

 

Попробуйте когда-нить
попробую обязательно.

 

Современные оптимизаторы творят чудеса. И им по-барабану Ваши жалкие потуги заменить операции индексирования на указатели и подобное - они это и сами хорошо делают, даже ещё и лучше.
Это я слышал и/или знаю.... Но, во первых, современные оптимизаторы иногда портят код так, что он не работает как надо, приходится либо отключать оптимизацию полностью, либо на отдельной функции. Во вторых про потуги вы зря.... когда мой код перестал влазить в флешь, а в прерываниях стал долго задерживаться, то сделал рефакторинг - код и влез и стал быстрее работать. Одна строчка до рефакторинга была 6 машинных команд, стала 2 или 4.

Убрал всякие лишние << |, на каждом участке экономил где по 10-20 байт флеша, где по 100.

 

во-вторых: ничего не известно о значении size_to_take, а если она перед началом цикла может быть отрицательной (и i - тоже знаковое)

Морите!? О чем тут дискутировать, если вы не знаете как работает for? Если size_to_take отрицательная и i тоже знаковое, то в примере ТС в тело цикла for не разу не зайдем. Это вообще букварь Си.

 

с чего Вы приводите входные аргументы к типу void *?
Да, согласен, неточность есть. Если быть скрупулёзным к явным преобразованиям, то нужно так

memcpy((void*)DataBuffer, (const void*)&data_out[j], 2*size_to_take);

Я не утверждаю, я всего лишь предположил, что копировать память лучше через memcpy. Если это не 8 и 16 бит, то забыли про мой совет. О чем спор?

 

А если у ТС-а DataBuffer и data_out объявлены к примеру с модификатором volatile
И что? в чем разница вот в таких кодах с volatile (архитектура ст первый, без перевёртывания):

Вариант 1:

volatile uint16_t DataBuffer[100];
volatile uint8_t data_out [1024];
uint16_t size_to_take = 100;
uint8_t j = 17;
for(uint16_t  i=0; i<size_to_take; i++ )
{
   DataBuffer[i] = (data_out[j] << 8)| data_out[j+1];
   j += 2;
}

 

Вариант 2:

volatile uint16_t DataBuffer[100];
volatile uint8_t data_out [1024];
uint16_t size_to_take = 100;
memcpy((void*)DataBuffer, (const void*)&data_out[17], 2*size_to_take);

 

Или по вашему 1-ый вариант работать будет, а второй нет?

 

 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Но, во первых, современные оптимизаторы иногда портят код так, что он не работает как надо, приходится либо отключать оптимизацию полностью, либо на отдельной функции.

За такую ересь - пожизненный эцих с гвоздями :maniac:

Подсказка: вы просто не умеете их готовить.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

За такую ересь - пожизненный эцих с гвоздями :maniac:
Я же говорю - тут церковь какая-то, пропитанная религией ))). Если вы с таким не встречались - это не значит, что такого нет.

 

ps Открою вам тайну, земля круглая!

 

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Морите!? О чем тут дискутировать, если вы не знаете как работает for? Если size_to_take отрицательная и i тоже знаковое, то в примере ТС в тело цикла for не разу не зайдем. Это вообще букварь Си.

и....? Продолжаем думать дальше. Подсказываю ещё раз: А что будет с Вашим вариантом на memcpy()?

 

И что? в чем разница вот в таких кодах с volatile (архитектура ст первый, без перевёртывания):

Или по вашему 1-ый вариант работать будет, а второй нет?

В каких-то случаях - да. Читайте мои посты внимательнее. Мы не знаем в какой памяти находятся исходный и целевой адреса. Возможно они находятся в памяти, требующей строго определённого порядка доступа и определённой разрядности доступа (strong ordered memory). Или в памяти при доступе к которой происходят прочие события (например FIFO-буфера периферии (SPI например) при записи/чтении данных в/из них производят другие действия: обмен с регистрами сдвига, удаление данных из FIFO и т.д.; а также чувствительны к разрядности операций чтения/записи - при доступе недопустимой разрядности получите fault; и чувствительны к выравниванию (хотя основная память МК может допуcкать невыровненный доступ, но некоторые регионы - могут генерить fault при невыровненном доступе)).

А какие именно операции доступа к памяти будут использованы внутри memcpy() - нет никакой гарантии. Если например DataBuffer указывает на 16-разрядный SPI-FIFO и size_to_take>=2, то внутри memcpy() может объединить доступы в 32-разрядные и в результате получим bus-fault.

Так что бездумно использовать memcpy() нельзя.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Или по вашему 1-ый вариант работать будет, а второй нет?

Тема больших и малых индейцев не раскрыта. А именно, у больших индейцев memcpy с успехом заменяет тот самый цикл копирования, а у малых - нет. ТС про индейцев ничего не говорил, отчего ваш $рачЪ на ровном месте выглядит особенно забавно :laughing:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Но, во первых, современные оптимизаторы иногда портят код так, что он не работает как надо, приходится либо отключать оптимизацию полностью

А вот здесь: код в студию!

Ибо в 99.9% таких случаев причина - баг в коде. :smile3046:

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Продолжаем думать дальше. Подсказываю ещё раз: А что будет с Вашим вариантом на memcpy()?
Что за глупость? при чем тут memcpy с отрицатеольными size_to_take? В коде ТС явно size_to_take > 0.

А вот здесь: код в студию!

#pragma optimize=none
unsigned int crc16_byte(unsigned int crc, unsigned int data)
{
    //const unsigned int Poly16=0xA001;
    unsigned int LSB;
    crc = ((crc^data) | 0xFF00) & (crc | 0x00FF);
    for (uint8_t i=0; i<8; i++) 
    {
        LSB=(crc & 0x0001);
        crc >>= 1;
        if(LSB)
            crc=crc^0xA001;
    }
    return crc;
}

Такой код работает, если убрать прагму, то црц считается не правильно. Для фантазёров, видящих за рамками кода всякое ООП, перегрузку операторов, переопределение типов и прочую фуе ту.... перегрузки операторов нет, переопределений типов и чего либо ещё нет. тип unsigned int 16 бит!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ибо в 99.9% таких случаев причина - баг в коде. :smile3046:

Да, но 0.1% все же баг в компиляторе.

У меня есть проект, который собирается и работает при O2 и не собирается при Os. Интересно?

C:\Users\user\AppData\Local\Temp\ccShzzAY.s: Assembler messages:
C:\Users\user\AppData\Local\Temp\ccShzzAY.s:3151: Error: value of 256 too large for field of 1 bytes at 000000000000103a
make.EXE: *** [obj_sw/esp8266.o] Error 1

В asm

.L276:
    .byte    (.L274-.L276)/2 - переполнение

Меняешь две строчки в Си-исходнике и проект начинает собираться.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Такой код работает, если убрать прагму, то црц считается не правильно. Для фантазёров, видящих за рамками кода всякое ООП, перегрузку операторов, переопределение типов и прочую фуе ту.... перегрузки операторов нет, переопределений типов и чего либо ещё нет. тип unsigned int 16 бит!

Не видно в этом коде никакого криминала. Конечно, нельзя исключить того, что криминал возникает вне этой функции (она же не в вакууме существует). Опять же, не исключён глюк компилятора. У меня яр для coldfire неправильно считал MD5 при включенной оптимизации. Поймать его за руку на конретной инструкции сложно, так как код MD5 зубодробительный.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Конечно, нельзя исключить того, что криминал возникает вне этой функции (она же не в вакууме существует).
Я тоже про это думал. На весь проект стоит максимальная оптимизация по размеру кода. Дебажил на уровне Си, в асм углубляться не стал. Одна строчка #pragma optimize=none давала правильную работу расчета црц. Поэтому врят-ли что-то в остальном коде не так. Времени не было искать глюк в компиляторе или ещё где.... Видимо это был 0,1%.

 

кому интересно, компилятор IAR C/C++ for STM8 из иара 3,10,1

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Что за глупость? при чем тут memcpy с отрицатеольными size_to_take? В коде ТС явно size_to_take > 0.

Где это "явно указано"? Ни объявления ни присвоения значения size_to_take там нету. А это означает, что size_to_take к началу цикла может принимать любое значение. И алгоритм вполне может быть рассчитан на то, что при size_to_take<=0 не должно выполняться ни одной итерации.

Глупость - это видеть то, чего нет.

 

Такой код работает, если убрать прагму, то црц считается не правильно.

В исходнике вроде криминала нет. Но это ничего не доказывает.

Без анализа ассемблерного результата ничего сказать нельзя.

Да даже анализ ассемблерного варианта может ничего не дать. Элементарная причина "неверной работы оптимизированного варианта" при работе оптимизированного например: переполнение стека в вызывающей задаче. Оптимизированные варианты, как правило расходуют больше стека (больше регистров используют, соответственно - больше их сохраняют на входе в функции; заменяют некоторые повторяющиеся куски кода на дополнительные подпрограммы, что опять же ведёт к увеличению расхода стека и т.п.), а в неоптимизированном варианте стека ещё хватает.

Другой вариант: при вкл. оптимизации функция может инлайниться в точки вызова, вызывая перераспределение регистров (увеличение их использования), что может приводить к проявлению скрытых багов в вызывающих функциях, которые при меньшем кол-ве используемых регистров не проявляются.

Так что наиболее вероятный диагноз остаётся тем же: с вероятностью 99.9% - баги в коде. :laughing: И необязательно что в этой процедуре.

 

Да, но 0.1% все же баг в компиляторе.

У меня есть проект, который собирается и работает при O2 и не собирается при Os. Интересно?

В приведённом Вами примере баг в Вашем коде, а не в компиляторе. О чём компилятор Вам и сообщает.

Вы вычисляете выражение, результат которого зависит от взаимного расположения меток. Их взаимное расположение видимо определяется на этапе компиляции (в зависимости от результата компиляции кода, который между ними - размер его меняется). Естественно - когда результат не влазит в байт, компилятор Вам об этом сообщает. В чём его ошибка? :wacko:

А писать так код - это выстрел себе в ногу: сегодня компилится, завтра - нет.

 

Я тоже про это думал. На весь проект стоит максимальная оптимизация по размеру кода. Дебажил на уровне Си, в асм углубляться не стал. Одна строчка #pragma optimize=none давала правильную работу расчета црц. Поэтому врят-ли что-то в остальном коде не так. Времени не было искать глюк в компиляторе или ещё где.... Видимо это был 0,1%.

Ну да - обычное оправдание багописателей в таких случаях: "не было времени искать баг, поэтому я закопал его поглубже". Это ничего, что потом, через неопределённой время он опять проявится и вдарит по лбу в самом неожиданном месте. Когда совершенно безобидный код станет глючить. И опять виноват конечно будет компилятор, ну кто-ж ещё! :smile3009:

 

При отладке одно из главнейших правил: ПРИ ПРОЯВЛЕНИИ БАГА НЕ СЛЕДУЕТ СТАРАТЬСЯ ДОБИТЬСЯ ТОГО, ЧТОБЫ ОН НЕ ПРОЯВЛЯЛСЯ. НАДО НАОБОРОТ - ЗАКРЕПИТЬ УСЛОВИЯ, ПРИ КОТОРЫХ ОН ПРОЯВЛЯЕТСЯ, ДОБИТЬСЯ ЕГО ПОВТОРЯЕМОСТИ И ИСКАТЬ ЕГО ПРИЧИНУ И УСТРАНИТЬ! И пока этого не сделано - двигаться дальше нельзя.

Ведь не тот баг страшен, который чётко проявляется, а тот - что проявляется иногда, внезапно и хаотично.

 

Поймать его за руку на конретной инструкции сложно, так как код MD5 зубодробительный.

Один из вариантов действий в такой ситуации:

Скомпилить подозреваемую функцию с оптимизацией. Взять ассемблерный результат её компиляции, оформить в виде асм-функции. И вставить в исходник вместо вызова си-функции. Выключить оптимизацию и проверить работу кода. Если теперь глюк появился и в неоптимизированном варианте, то только в этом случае можно предполагать, что проблема в данной оптимизированной функции. Далее - только анализ этой ассемблерной функции.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Где это "явно указано"?
Дальше в эту сторону дискутировать не выжу смысла. Учитесь читать исходный код.

 

Так что наиболее вероятный диагноз остаётся тем же: с вероятностью 99.9% - баги в коде.
Если не на кофейной гуще гадать, а на картах Таро, то баг в ОС, на которой кросскомпиляция идет.

 

Вот ещё пример

#define     __IO    volatile         /*!< defines 'read / write' permissions  */
typedef struct SPI_struct
{
  __IO uint8_t SR;     /*!< SPI status register */
}
SPI_TypeDef;
...
#define SPI1                        ((SPI_TypeDef *) SPI1_BASE)
SPI_FLAG_BSY    = (uint8_t)0x80
...





#pragma optimize=none //без прагмы waitSpi(); не дожидается выполнения
inline void waitSpi() {       while( (SPI1->SR & (uint8_t)SPI_FLAG_BSY));}

Тут без прагмы вход в waitSpi и мгновенный выход, не зависимо от флага SPI_FLAG_BSY.

 

переполнение стека в вызывающей задаче.
ээээ.... а разве при переполнении стека программа аварийно не завершается? В моём случае с crc16_byte() идет подсчет црц, но на выходе он не вырный, падения программы нет. Програма продолжает работать, но бракует пакет по црц. Принятые данные не портятся во время работы crc16_byte() и во время работы программы в целом. Хотел подебажить crc16_byte(), но с оптимизацией код не шагается по строчками си. А без оптимизации шагается но и работает без ошибок.

 

Да даже анализ ассемблерного варианта может ничего не дать. Элементарная причина "неверной работы оптимизированного варианта" при работе оптимизированного например: переполнение стека в вызывающей задаче.
Что-то я не пойму.... Вот, вы же сами утверждаете, что оптимизатор может сломать работу программы написанной без ошибок. Я о том же.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В приведённом Вами примере баг в Вашем коде, а не в компиляторе. О чём компилятор Вам и сообщает.

Вот мой код. Это участок типа

if(0){}
else if(состояние_1){сделать_1;}
else if(состояние_2){сделать_2;}

Так не собирается.

А так

if(0){}
else if(состояние_2){сделать_2;}
else if(состояние_1){сделать_1;}

нет проблем.

Это чисто косяк компилятора.

 

Вот полный код:

	//-------------------------------------------
//	ESP8266_ST_UDP0_START
//- - - - - - - - - - - - - - - - - - - - - -
else if(esp8266_state == ESP8266_ST_UDP0_START)
{
	if(esp8266_cmd_flag == ESP8266_CMD_START)
	{
		send_cmd("AT+CIPSTART=0,\"UDP\",\"255.255.255.255\",5002,5001,2");
	}
	if(esp8266_to > 1000)
	{
		SEND_TO;
		esp8266_to_cmd(ESP8266_ST_IDLE);
	}
}
//-------------------------------------------
//	ESP8266_ST_UDP0_SEND
//- - - - - - - - - - - - - - - - - - - - - -
else if(esp8266_state == ESP8266_ST_UDP0_SEND)
{
	if(esp8266_cmd_flag == ESP8266_CMD_START)
	{
		esp8266_cmd_flag = ESP8266_CMD_ST + 0;
		esp8266_to = 0;
		esp8266_last_cmd[0] = 0;
		bl_export.bl_sp_str(&esp8266, "AT+CIPSEND=0,");
		bl_export.bl_sp_dec(&esp8266, esp8266_udp_send_len);
		bl_export.bl_sp_str(&esp8266, "\r\n");
		bl_export.bl_sp_start(&esp8266);
	}
	if(esp8266_to > 1000)
	{
		SEND_TO;
		esp8266_to_cmd(ESP8266_ST_IDLE);
	}
}

Если соответствующие блоки ESP8266_ST_UDP0_START и ESP8266_ST_UDP0_SEND в исходнике переставить местами, то

компилятор генерит правильный s-файл. Иначе генерит таблицу, в которой смещения не помещаются.

Где тут моя ошибка? Где ошибка компилятора я разобрался и на всякий случай сменил компилятор (была сборка arm-kgp-eabi-procyon).

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...