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

Плавный переход C -> C++ под МК

43 минуты назад, Arlleex сказал:

В Вашем случае да, я это понимаю. Однако я чисто за SLCAN - он уже вряд ли кем-то когда-то будет меняться. В нем все довольно тривиально: прилетела строка символов вида "Sx\r" (без '/0' и где x от 0 до 8, каждой цифре соответствует строго определенная скорость: 10кбит/с, 20, 50, 100, 125, 250, 500, 800 и 1Мбит/с), извлекаем эту цифру и вызваем setBaudRate() с этой цифрой в качестве аргумента.

Ну хорошо, касательно вашего примера установки скорости CAN. Вот вы на одном МК знаете, что он умеет устаналивать все скорости из требуемого ряда "10кбит/с, 20, 50, 100, 125, 250, 500, 800 и 1Мбит/с".

И Вы пишете функцию с прототипом: void CanSetBaud(u32 baud) {...}

Но в другом МК оказывается, что (из-за особенностей тактирования в конкретном проекте), там ну никак нельзя выставить 800 кбод! Все остальные скорости - можно. И что делать? Надо переходить на прототип: bool CanSetBaud(u32 baud) {...} ?  С соответствующей доработкой точек вызова CanSetBaud() в других проектах (потому как этот CanSetBaud() вызывается из другого класса, который также универсален для всех проектов, а значит везде придётся добавлять обработку кода завершения CanSetBaud(), даже в тех МК, где это не нужно). И вот тут в полный рост получаем неоптимальность - лишний код! (кроме необходимости переделок).

 

А завтра этот класс вам потребуется перенести на 8-битный МК, и окажется что используемые в аргументах типы u32, ну очень неоптимальны там! Зато там есть тип u24, довольно оптимальный. И опять придётся думать - как сделать чтобы было оптимально и на всём зоопарке используемых МК. :dash2:

 

А послезавтра окажется что перед тем как "прилетела строка символов вида" надо туда откуда она прилетела, периодически отправлять список скоростей, доступных для установки в данный момент (и этот список может меняться время от времени в зависимости от режима работы устройства). Вот у меня был такой проект, где МК мог работать на сильно разных тактовых частотах (экономия батарейки), и соответственно - список возможных скоростей по интерфейсам менялся).

Ведь очень удобно (и правильно!), когда например пользователю предоставляется некий графический интерфейс, в котором он может выбрать некую скорость (режим) работы интерфейса из выпадающего списка доступных. И очень некрасиво, когда в ответ на выбранную скорость ему девайс говорит - "эта скорость сейчас недоступна". Значит список нужно корректировать по мере работы. Но эта интерактивность нужна только в одном проекте из множества, а в других - список скоростей всегда постоянен, заранее известен и нет необходимости слать его пользователю.

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


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

34 минуты назад, jcxz сказал:

Но в другом МК оказывается, что...

Ну тут уж, что поделать - SLCAN "стандартизован" (специально беру в кавычки, т.к. это стандарт домохозяек): МК должен уметь выставить любую из той сетки скоростей для CAN. Если МК не может это сделать, то "надо сделать так, чтоб сумел":wink: В целом, я понимаю Вашу позицию, что невозможно создать универсальную таблетку. Однако тем плюсы и хороши, что все эти различные реализации (для одного МК, как Вы говорите, можно настроить 800кбит/с, для другого - нет), сколько бы их не было, можно унаследовать от одного интерфейса, и прикладная логика работы девайса не поменяется. Но тут за все приходится платить либо скоростью, либо читаемостью.

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


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

8 minutes ago, Arlleex said:

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

Скорость тут если и страдает (вопреки стараниям современных компиляторов и знанию их особенностей), то это - сущий мизер,

который, как правило, не идет ни в какое сравнение с многократно растущей читаемостью, и что более важнее - "сопровождаемостью", если можно ввести такой термин ;)

 

зы А ведь когда-то такие разговоры вели про сравнение читаемости и быстроты asm и C. А потом будут такие же сравнения со старыми (к тому моменту) плюсами или даже шарпом и более "продвинутых' языков.

 

 

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


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

1 hour ago, Arlleex said:

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

Я уже говорил- ABC. Это именно оно.

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


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

12.10.2021 в 15:53, Forger сказал:

Скорость тут если и страдает (вопреки стараниям современных компиляторов и знанию их особенностей), то это - сущий мизер,

который, как правило, не идет ни в какое сравнение с многократно растущей читаемостью

Тут мне вспоминается один мой проект. ~2011-2013гг примерно. Выполнялся на OMAP-L137 (ARM+DSP). Моя задача изначально была: разработка драйверов периферии (UART, SPI, I2C, McASP, USB и т.п.), приём и обработка потока данных с 24-х АЦП через McASP (ARM-ядро), передача этого потока DSP-ядру на обработку, возврат обратно обработанного потока из DSP в ARM и передача обработанного потока данных в интерфейс связи (USB или WiFi). Ну и прочее множество задач, начиная от конфигурирования и кончая управлением в реальном времени АЦП-шками и аудио-кодеком и пр. Планировалось, что другой программист должен был на DSP обрабатывать этот поток данных. У нас в команде была группа, которая на базе мед.клиники (в одной из столиц стран ЕС) под руководством тамошних профессоров медицины, работала над обработкой данных (данных от предыдущей версии устройства, которая была без DSP и умела только собирать, но не обрабатывать данные). Эта группа работала несколько лет до этого, наработала статистику, отладила математический аппарат по обработке этих данных на множестве пациентов. Она это делала на ПК: ПО по обработке крутилось на ПК и над ресурсами никто не задумывался. Написано ПО было программистами по всем правилам С++: со всеми конструкторами/деструкторами, абстрагированием от источников потока данных и прочими блэкджеком и шлюхами си-плюсовыми плюшками. И вобщем - довольно грамотно написано (как потом я его просматривал), с хорошим документированием и т.п.

Дальше была идея: обработку эту внедрить прямо в девайс. Для чего в последней его версии заменили Cortex-M на OMAP. Тем более, что DSP там поддерживает аппаратный double (на котором была написана вся обработка) и проблем не должно было возникнуть. На всякий случай (по требованию начальства) внедрили туда SDRAM на 128МБ (хотя это мне казалось явным излишеством). DSP я запустил на 375 МГц, но при необходимости можно было поднять тактовую до 450 МГц (добавив питания). С трудом удалось убедить начальство, что этой вычислительной мощности должно хватить для решения задачи (начальство хотело более мощный DSP - какого-нить монстра из C64xx/C66xx или типа того). Исходники по мат.обработке данных мне не давали (ноу-хау и все дела :new_russian:), поэтому о необходимых вычислительных ресурсах мне приходилось судить по примерным блок-схемам алгоритма обработки и поверхностным объяснениям программистов группы). Там довольно много всего считалось: одних только БПФ - пучок разных, не считая кучки фильтров и другой спец.обработки. Но по прикидкам должно было заведомо влезть в DSP. Программисты из иностранной группы совершенно не имели опыта в embedded и тем более - в DSP. Поэтому нашли меня. Я должен был обеспечить возможность работы их алгоритмов в DSP, предоставить поток данных и забрать его. К закрытым исходникам (да и бинарникам) меня не планировалось даже подпускать.

Вобщем - я свою часть работы сделал. Дальше - должен был только консультировать тех программистов и по необходимости что-то править. Мяч был на их стороне поля. Прошло несколько месяцев.... (наверное ~полгода)... Выясняется, что "DSP не тянет обработку". :shok:  Причём - "не тянет" значительно: как мне сказали - они уже обрезали функционал по минимуму, что только можно, лишь бы хоть что-то успевало посчитаться в реальном времени. Решили переделать (сильно урезать) алгоритм работы устройства, сделав его каким-то до ужаса извратным (в реальном времени должна была выполняться только самая необходимая обработка, сырые данные должны были накапливаться в большом ОЗУ, а при обнаружении некоторых характеристик в сигнале (по результатам лёгкой обработки в ральном времени) выполнять уже полную обработку над захваченными данными из буфера в ОЗУ, но уже не в реальном времени). Такой порядок работы конечно очень неудобен был бы пользователям, но типа "ничего не поделаешь - не хватает ресурсов DSP на полную обработку в реальном времени на лету". И даже в этом случае производительности DSP не хватало примерно в 3 раза (т.е. - успевало обрабатываться только ~30% данных). Естественно - даже увеличение частоты до 450 МГц не помогло бы.

Начальство наехало на нас (меня и схемотехника) за то, что мы убедили его ставить такой "дохлый" DSP. Ведь оно хотело "Horse power!". А мы не послушались.... :punish: Потом были долгие переговоры. Начальство решило закрыть проект в силу бесперспективности. Месяц или 2 тянулись раздумья и переговоры. Наконец начальство решило, что терять уже всё равно нечего и решили передать исходники мне - может мне удастся как-то их внедрить в DSP чтобы хоть минимум заработал (ведь до этого я имел довольно большой опыт в DSP). Но надежда была слабая. Всё-таки и я с C67xx-ядрами (и вообще VLIW) ранее не работал.

Исходники передали мне. Я посмотрел и....... тихо офигел!... :shok:  Оказывается товарищи-ООП-программисты перетащили все свои исходники с ПК "как есть". Т.е. - со всеми вызовами член-функций (может даже виртуальных - не помню уж) для чтения потока сэмплов АЦП внутри циклов обработки!!! И запись результатов в кольцевые буфера и последующие их чтения в других циклах последующих стадий мат.обработки! :shok:  А ведь всё это - вызовы функций изнутри циклов, которые при наличии таких вызовов уже не могут выполняться аппаратными циклами DSP. Да и вообще - оптимизирующий компилятор уже не может распараллелить такую обработку в несколько потоков на нескольких десятках регистров DSP. Кто хоть немного плотно работал с DSP - поймёт. Соответственно - скорость обработки падает даже не в разы, а в десятки раз!

Вобщем: когда я выкосил из исходников всё ООП, сделал "DSP-target"-оптимизацию алгоритма обработки, расположения/группирования данных и т.д., то мне удалось повысить скорость выполнения алгоритма примерно в ~30 раз! Всё стало успевать выполняться в реальном времени. Алгоритм вернули к полнофункциональному (как на ПК) - убрали все урезания. Потом в ПО добавили ещё кучу плюшек, которые сперва думались как опциональные "если влезут". И всё равно - загрузка DSP-ядра осталась на уровне ~20...22% (при тактовой = 375МГц).

Правда 128МБ SDRAM оказалась теперь лишней. Такого объёма. Это конечно добавило проблем. :wink: Мне хватило <1МБ для обработки в реальном времени.

 

Теперь, когда слышу, подобное "скорость пострадает минимально от ООП", вспоминаю тот проект. Минимально, да... всего в каких-то 30 раз.... :scratch_one-s_head:

 

PS: Ещё раз повторю: исходники теми иностранными программистами написаны были грамотно, и вообще - там были хорошие программисты. Ничего плохого про их код сказать не могу. Дело тут не в качестве исходного кода.

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


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

Ладно, буду дальше изучать, в том числе CRTP.

Смотрю данный ролик сейчас (см. с 17:02). Сразу заострил внимание на следующем. Там в примере кусок кода вида

int area(int a) {
  return a * a;
}

int perimeter(int a) {
  return a + a + a + a;
}

int main(int argc, char *[]) {
  return argc % 2 ? area(argc) : perimeter(argc);
}


Автор приводит дизасм (псевдо, но не суть), и там видно, что компилятор сначала вычислил perimeter(), а затем, если (argc % 2) != 0, area().

Ведь это по идее не правильно. У операции ?: есть точки следования, как и у всяких && или ||. Т.е. аргументы вычисляются только в зависимости от условия.

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

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


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

18 минут назад, Arlleex сказал:

Ведь это по идее не правильно. У операции ?: есть точки следования, как и у всяких && или ||. Т.е. аргументы вычисляются только в зависимости от условия.

Никогда не рассчитываю на некий порядок вычисления внутри "?:", всегда считаю, что обе ветки могут быть как вычислены заранее так и нет - как фишка ляжет на усмотрение компилятора. А потому - использую "?:" только когда это не принципиально в данном случае. Более того - например IAR всегда компилит этот оператор неоптимально, всегда создавая 2 ветки кода, даже если это не нужно.

Аналогично и с выражением внутри if (): если мне важен порядок вычисления этого выражения, то никогда не использую &&, заменяю на: if () if () ... (с || - поступаю аналогично).

Например пишу так:

void F1(char *ptr, uint size)
{
  if (ptr && size > 1) if (ptr[1]) ...
}

а не:  if (ptr && size > 1 && ptr[1]) ...

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

20 минут назад, Arlleex сказал:

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

По идее в CCS для DSP можно указателям-аргументам функции присваивать спецификатор restrict, говорящий оптимизатору, что обращения по этим указателям внутри функции не имеют побочных эффектов и могут быть как угодно оптимизированы. Например - менять порядок циклов (не от начала к концу, а наоборот или распараллелить обработку данных из этого указателя на несколько потоков). Если такого спецификатора нет, оптимизатор там становится намного скромнее. В IAR мне кажется очень не хватает такого спецификатора. Ещё бы если б его можно было не только аргументам, но и самой функции присваивать.....

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


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

37 minutes ago, Arlleex said:

Ладно, буду дальше изучать, в том числе CRTP.

Смотрю данный ролик сейчас (см. с 17:02). Сразу заострил внимание на следующем. Там в примере кусок кода вида


int area(int a) {
  return a * a;
}

int perimeter(int a) {
  return a + a + a + a;
}

int main(int argc, char *[]) {
  return argc % 2 ? area(argc) : perimeter(argc);
}


Автор приводит дизасм (псевдо, но не суть), и там видно, что компилятор сначала вычислил perimeter(), а затем, если (argc % 2) != 0, area().

Ведь это по идее не правильно. У операции ?: есть точки следования, как и у всяких && или ||. Т.е. аргументы вычисляются только в зависимости от условия.

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

Вы путаете Runtime и Compile time.

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


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

41 minutes ago, Arlleex said:

Там в примере кусок кода вида

Как этот кусок кода вообще компилятору скормили, если:

gcc 1.c 
1.c: In function 'main':
1.c:9:20: error: parameter name omitted
    9 | int main(int argc, char *[]) {
      |                    ^~~~~~~~

clang 1.c 
1.c:9:26: error: parameter name omitted
int main(int argc, char *[]) {
                         ^
1 error generated.

А вот, что генерирует gcc на самом деле:

main:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    %edi, -4(%rbp)
    movq    %rsi, -16(%rbp)
    movl    -4(%rbp), %eax
    andl    $1, %eax
    testl   %eax, %eax
    je  .L6
    movl    -4(%rbp), %eax
    movl    %eax, %edi
    call    area
    jmp .L7
.L6:
    movl    -4(%rbp), %eax
    movl    %eax, %edi
    call    perimeter
.L7:

Так что, не надо тут гнать на компилятор! Все честно считается по-человечески!

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


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

P.S. Посмотрел. Не компилятор вычисляет, а программа. Ну да. Это же быстрее, чем делать полноценный вызов функции, пролог, эпилог, вся вот эта прелесть. А то, что математическая модель, которую вы себе представляете делает одно действие или другое вам кажется оптимальнее, это только кажется. А сделать так, как вы хотите,  - это будет два условных бренча, что тоже не факт, что быстрее, а по размеру программы -точно больше (это же O1 - он не только о скорости. но и о размере думает).

 

P.P.S Ну и да - никаких побочек тут нет, наверняка, он тоже на них смотрит, это легко проверить, кстати.

Изменено пользователем one_eight_seven

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


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

55 минут назад, Arlleex сказал:

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

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

IAR-у бы такую оптимизацию освоить не помешало!.... Из-за того, что её нет, меня всегда немного напрягает использовать "?:".

 

5 минут назад, one_eight_seven сказал:

А сделать так, как вы хотите,  - это будет два условных бренча

Условный будет 1, 2-й - безусловный.

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


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

Кстати, с любыми уровнями оптимизаци (O1, O2, O3) вообще веселуха:

    .file   "1.c"
    .text
    .p2align 4
    .globl  area
    .type   area, @function
area:
.LFB0:
    .cfi_startproc
    movl    %edi, %eax
    imull   %edi, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   area, .-area
    .p2align 4
    .globl  perimeter
    .type   perimeter, @function
perimeter:
.LFB1:
    .cfi_startproc
    leal    0(,%rdi,4), %eax
    ret
    .cfi_endproc
.LFE1:
    .size   perimeter, .-perimeter
    .section    .text.startup,"ax",@progbits
    .p2align 4
    .globl  main
    .type   main, @function
main:
.LFB2:
    .cfi_startproc
    leal    0(,%rdi,4), %eax
    testb   $1, %dil
    je  .L4
    movl    %edi, %eax
    imull   %edi, %eax
.L4:
    ret
    .cfi_endproc
.LFE2:
    .size   main, .-main
    .ident  "GCC: (Gentoo 10.2.0-r5 p6) 10.2.0"
    .section    .note.GNU-stack,"",@progbits

Т.е. несмотря на то, что генерятся коды для функций, gcc их вообще не вызывает, а выполняет вычисления прямо в main! И, действительно, "на всякий случай" он сначала умножает количество аргументов на 4, затем проверяет, и если надо, вычисляет квадрат.

Думаю, если все же более сложный пример привести, а не такую халяву, все будет ОК. В случае С++ вообще всякие constexpr'ы на моменте препроцессора вычисляются.

Изменено пользователем Eddy_Em

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


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

34 минуты назад, jcxz сказал:

Никогда не рассчитываю на некий порядок вычисления внутри "?:", всегда считаю...

Понял. Я придерживаюсь примерно такой же позиции, однако все-таки стараюсь, не злоупотребляя, пользоваться точками следования. Да, грешу всякими if(i > 0 && --i) и т.д.

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


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

Только что, Arlleex сказал:

грешу всякими if(i > 0 && --i) и т.д.

Нее... так точно никогда не делаю.

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


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

28 minutes ago, jcxz said:

Условный будет 1, 2-й - безусловный

Да, точно. Согласен.

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


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

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

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

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

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

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

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

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

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

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