Информация об изменениях

Сообщение Re[6]: Самый низкоуровневый язык, ага от 02.10.2023 11:07

Изменено 02.10.2023 11:11 vsb

Re[6]: Самый низкоуровневый язык, ага
Здравствуйте, Евгений Музыченко, Вы писали:

vsb>>не хватает аналога async/await в С. Чтобы я мог записать алгоритм в простом виде, а компилятор из этого сварганил машину состояний и оно бы работало параллельно с другими такими же машинами состояний.


ЕМ>Такое может неплохо работать на мощных процессорах, когда запас по быстродействию большой. На МК это рискует тормозить, причем непредсказуемо.


Не понимаю, о чём речь.

Грубо говоря, сейчас алгоритм такой:

for (size_t i = 0; i < length; i++) {
  while (TBE == 0) {} // ждем пока не включится флаг TRANSMIT_BUFFER_EMPTY
  TRANSMIT_DATA := transmit_data[i] // записываем первый байт
  while (RBNE == 0) {} // ждём пока не включится флаг RECEIVE_BUFFER_NOT_EMPTY
  receive_data[i] := RECEIVE_DATA;
}


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

void process_events() {
  switch (state) {
    case WAIT_FOR_TBE:
      if (TBE != 0) state = TRANSMIT_BYTE;
      break;
    case TRANSMIT_BYTE:
      TRANSMIT_DATA := transmit_data[i];
      state = WAIT_FOR_RBNE;
      break;
    case WAIT_FOR_RBNE:
      if (RBNE != 0) state = RECEIVE_BYTE;
      break;
    case RECEIVE_BYTE:
      receive_data[i] := RECEIVE_DATA;
      state = NEXT_ITERATION;
      break;
    case NEXT_ITERATION:
      i++;
      if (i < length) {
        state = WAIT_FOR_TBE;
      } else {
        state = END;
      }
      break;
  }   
}


Как видно, блокирующий алгоритм переделали в неблокирующий, теперь надо только достаточно часто вызывать process_events();

но переделка абсолютно механическая и по этому алгоритму работают многие языки, реализуя async/await под капотом именно в таком виде.

Т.е. можно было бы взять начальный код

for (size_t i = 0; i < length; i++) {
  while (TBE == 0) yield; // ждем пока не включится флаг TRANSMIT_BUFFER_EMPTY
  TRANSMIT_DATA := transmit_data[i] // записываем первый байт
  while (RBNE == 0) yield; // ждём пока не включится флаг RECEIVE_BUFFER_NOT_EMPTY
  receive_data[i] := RECEIVE_DATA;
}


И по yield разбить функцию на куски и каждый кусок оформить в виде куска в switch машины состояний. Ну тут нет вызовов других функций, в общем случае всё: конечно, может быть похитрей, но суть та же.

vsb>>Я что-то подобное сейчас руками делаю


ЕМ>Делаете с пониманием того, как работает вся эта кухня, или интуитивно?


Не очень понял вопроса. Понимать там нечего, всё примитивно.

vsb>>Вроде в Rust оно как-то так работает, но он сложный...


ЕМ>А есть реализации Rust под требуемые МК?


Под интересующие меня — конечно есть, там же LLVM, он много чего умеет. Впрочем я не пробовал, Rust я и сам знаю плохо, и код я пишу не для себя, такой код другие поддерживать не смогут. Считаю, что нужно придерживаться индустриальных стандартов, коим Rust пока не является.
Re[6]: Самый низкоуровневый язык, ага
Здравствуйте, Евгений Музыченко, Вы писали:

vsb>>не хватает аналога async/await в С. Чтобы я мог записать алгоритм в простом виде, а компилятор из этого сварганил машину состояний и оно бы работало параллельно с другими такими же машинами состояний.


ЕМ>Такое может неплохо работать на мощных процессорах, когда запас по быстродействию большой. На МК это рискует тормозить, причем непредсказуемо.


Не понимаю, о чём речь.

Грубо говоря, сейчас алгоритм такой:

for (size_t i = 0; i < length; i++) {
  while (TBE == 0) {} // ждем пока не включится флаг TRANSMIT_BUFFER_EMPTY
  TRANSMIT_DATA := transmit_data[i] // записываем первый байт
  while (RBNE == 0) {} // ждём пока не включится флаг RECEIVE_BUFFER_NOT_EMPTY
  receive_data[i] := RECEIVE_DATA;
}


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

void process_events() {
  switch (state) {
    case WAIT_FOR_TBE:
      if (TBE != 0) state = TRANSMIT_BYTE;
      break;
    case TRANSMIT_BYTE:
      TRANSMIT_DATA := transmit_data[i];
      state = WAIT_FOR_RBNE;
      break;
    case WAIT_FOR_RBNE:
      if (RBNE != 0) state = RECEIVE_BYTE;
      break;
    case RECEIVE_BYTE:
      receive_data[i] := RECEIVE_DATA;
      state = NEXT_ITERATION;
      break;
    case NEXT_ITERATION:
      i++;
      if (i < length) {
        state = WAIT_FOR_TBE;
      } else {
        state = END;
      }
      break;
  }   
}


Как видно, блокирующий алгоритм переделали в неблокирующий, теперь надо только достаточно часто вызывать process_events();

но переделка абсолютно механическая и по этому алгоритму работают многие языки, реализуя async/await под капотом именно в таком виде.

Т.е. можно было бы взять начальный код

for (size_t i = 0; i < length; i++) {
  while (TBE == 0) yield; // ждем пока не включится флаг TRANSMIT_BUFFER_EMPTY
  TRANSMIT_DATA := transmit_data[i] // записываем первый байт
  while (RBNE == 0) yield; // ждём пока не включится флаг RECEIVE_BUFFER_NOT_EMPTY
  receive_data[i] := RECEIVE_DATA;
}


И по yield разбить функцию на куски и каждый кусок оформить в виде куска в switch машины состояний. Ну тут нет вызовов других функций, в общем случае всё: конечно, может быть похитрей, но суть та же.

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

vsb>>Я что-то подобное сейчас руками делаю


ЕМ>Делаете с пониманием того, как работает вся эта кухня, или интуитивно?


Не очень понял вопроса. Понимать там нечего, всё примитивно.

vsb>>Вроде в Rust оно как-то так работает, но он сложный...


ЕМ>А есть реализации Rust под требуемые МК?


Под интересующие меня — конечно есть, там же LLVM, он много чего умеет. Впрочем я не пробовал, Rust я и сам знаю плохо, и код я пишу не для себя, такой код другие поддерживать не смогут. Считаю, что нужно придерживаться индустриальных стандартов, коим Rust пока не является.