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

Как повысить скорость работы по сети AT91SAM7X256

А чем очередь принципиально отличается от массива?

Это же массив указателей и пара индексов put/get.

В любом случае Вы вольны написать свои обёртки очередей.

 

Насколько я смог разобраться когда-то и вспомнить сейчас, во FreeRTOS, работа с очередями организована через усыпление всех активных задач, что, соответственно, сказывалось на быстродействии системы. Поэтому пришлось переделывать. Сейчас посмотрю что изменилось в новой версии операционки и как это всё сделано в интересующем меня примере.

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


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

Да от memcpy надо уходить.

Не надо уходить от memcpy! Стандартные библиотеки писаны далеко не дураками, и не на C.

Ссылка.

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


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

Не надо уходить от memcpy!

Обойти можно только наложив дополнительные ограничения, например ввиде обязательной выровненности даннных.... А так сложно.. memcpy() не вовсех либах хороши :( у 4 IAR - отвратительные, а в 5 резко улучшили, даже отдельно хвастались, что дескать самый быстрый стал..

При испытаниях действительно в некоторых условиях превосходил ранее мной используемый для старых IAR:

       NAME memcpy

       RSEG CSTACK:DATA:NOROOT(2)

       MULTWEAK ??memcpy??rT
       MULTWEAK ??memmove??rT
       PUBLIC memcpy
       PUBLIC memmove

memcpy              SYMBOL "memcpy"
??memcpy??rT        SYMBOL "??rT", memcpy

memmove             SYMBOL "memmove"
??memmove??rT       SYMBOL "??rT", memmove


       RSEG CODE:CODE:NOROOT(2)
       THUMB
??memcpy??rT:
       BX       PC
       Nop
       REQUIRE memcpy


       RSEG CODE:CODE:NOROOT(2)
       ARM

#ifndef configUSE_MEMCPY8W              
               #define WORDS8_TRANSFER  0
#else 
       #if configUSE_MEMCPY8W == 1 
               #define WORDS8_TRANSFER  1
       #else
               #define WORDS8_TRANSFER  0
       #endif
#endif

//---------------------------------------------------------------------------
// void *(memcpy)(void *p1, const void *p2, size_t n)
// Copy char p2[n] to p1[n]
//---------------------------------------------------------------------------
memcpy:
       teq      r2,#0                  // Is p1 == 0 ?
       bxeq     lr                     // If p1 == 0, return

       stmdb    sp!,{lr}               // Push return address
       mov      r12,r0                 // Copy pointer p1
       cmp      r2,#8                  // Is buffer long or short?
       ble      byteserial             // Jump if n <= 8

       sub      r3,r0,r1               // Compare pointers p1, p2
       tst      r3,#3                  // Strings aligned same?
       bne      byteserial             // Jump if buffers not aligned


// Both strings are similarly aligned WRT word boundaries.
// At least a portion of the data can be copied an entire
// word at a time, which is faster than copying bytes.
wordserial:
       ands     r3,r0,#3               // Check byte alignment
       beq      wordaligned            // Jump if p1, p2 word-aligned

       rsb      r3,r3,#4               // m = no. of odd initial bytes
       sub      r2,r2,r3               // n = n - m


// If the two buffers do not begin on word boundaries, begin
// by copying the odd bytes that precede the first full word.
preloop:
       ldrb     lr,[r1],#1             // Read byte from source
       subs     r3,r3,#1               // --m (decrement loop count)
       strb     lr,[r12],#1            // Write byte to destination
       bne      preloop                // Loop if more bytes to move


wordaligned:
#if WORDS8_TRANSFER == 1
       movs     r3,r2,asr #5           // Any chunks of 8 words?
       beq      octsdone               // Jump if no 8-word chunks

       and      r2,r2,#0x1F            // Subtract chunks from n
       stmdb    sp!,{r4-r10}           // Save registers on stack


// The strings are long enough that we can transfer at least
// some portion of the data in 8-word chunks.
octloop:
       ldmia    r1!,{r4-r10,lr}        // Load 8 words from source
       subs     r3,r3,#1               // More 8-word chunks to move?
       stmia    r12!,{r4-r10,lr}       // Write 8 words to destination
       bne      octloop                // Loop if more chunks

       ldmia    sp!,{r4-r10}           // Restore registers from stack

octsdone:
#endif
       movs     r3,r2,asr #2           // Any more whole words to move?
       beq      wordsdone              // Jump if no more whole words


// Copy as much of the remaining data as possible one word at
// a time.
wordloop2:
       ldr      lr,[r1],#4             // Read next word from source
       subs     r3,r3,#1               // Decrement word count
       str      lr,[r12],#4            // Write next word to destination
       bne      wordloop2              // Loop while more words to move

wordsdone:
       ands     r2,r2,#3               // Any last bytes to transfer?
       beq      theend                 // Return if already done


// The two strings do not end on word boundaries.
// Copy the remaining data one byte at a time.
byteserial:
       ldrb     lr,[r1],#1             // Read byte from source
       subs     r2,r2,#1               // --n (decrement loop count)
       strb     lr,[r12],#1            // Write byte to destination
       bne      byteserial             // Loop if more bytes to move

theend:
       ldmia    sp!,{lr}               // Return
       bx       lr

//---------------------------------------------------------------------------
       RSEG CODE:CODE:NOROOT(2)
       THUMB
??memmove??rT:
       BX       PC
       Nop
       REQUIRE memmove


       RSEG CODE:CODE:NOROOT(2)
       ARM

//---------------------------------------------------------------------------
// Safely copy c bytes from source s to destination d.
// void *memmove(void *d, const void *s, unsigned c);
//---------------------------------------------------------------------------
memmove:
       cmp      r0,r1          // Is d > s ?
       bls      memcpy         // Jump to memcpy if d <= s

// Need to copy backwards, starting at tail ends of source and
// destination arrays.  Copy a word or a byte at a time?
       orr      r3,r1,r0       // tmp = s | d
       orr      r3,r3,r2       // tmp = s | d | c
       ands     r3,r3,#3       // Is tmp even multiple of 4?

       add      r1,r1,r2       // s + c (end of source buffer)
       add      r2,r2,r0       // d + c (end of dest'n buffer)
       beq      move1          // Jump if tmp is multiple of 4
       b        move2

// Because the source and destination arrays are not aligned to even
// word boundaries in memory, transfer only a byte at a time.
move3:
       ldrb     r3,[r1,#-1]!   // Load next byte from source
       strb     r3,[r2,#-1]!   // Store next byte to dest'n
move2:
       teq      r0,r2          // More bytes to move?
       bne      move3          // Jump if more bytes
       bx       lr             // All done

// Because count c is an even multiple of 4 and the source
// and destination arrays begin on even word boundaries, move
// an entire word at a time from source to destination.
move4:
       ldr      r3,[r1,#-4]!   // Load next word from source
       str      r3,[r2,#-4]!   // Store next word to dest'n
move1:
       teq      r0,r2          // More words to move?
       bne      move4          // Jump if more words

       bx       lr             // All done

       END

А 'уходить' по любому надо, но максимально исключая пересылки память-память в пинципе :)

 

 

Но для SAM7 это не так. Надо помнить что слабое место у SAM7 это флеш. В ARM режиме размер кода увеличивается в среднем на 30%, а это значит как минимум на 30% больше обращений к медленной флеш. Плюс в SAM7 применена технология ускоренной выборки для Thumb режима (по две Thumb инструкции за одно обращение к флеш). Т.е. при размещении программы во флеш Thumb режим заведомо в выигрыше.

1. К счатью на SAM7 свет клином не сошелся :).

2. Все не так радужно и с Thumb - на моих реальных программах много использующих 32-битовость увеличение размера кода МНОГО меньше, а количество команд которые по любому надо исполнять ЗАМЕТНО больше. Практически вышесказанное Вами в большей степени относится к коду написанному в "восьмибитном" стиле с обильным использованием 8-бит переменных, что изрядно душит ARM (в обоих режимах). Для изначально "правильных" программ Thumb у меня ПРОИГРАЛ. Правда на SAM7 не гонял, а на LPC там, конечно, MAM описанный Вами эффект дополнительно сглаживает, но не слишком, ибо частота повыше а Flash потормознее.

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


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

В ARM режиме резонно написать некоторые особо критические функции, и разместить их в RAM. Но надо смотреть каждую конкретную функцию, т.к. например функция копирования приведенная выше и в ARM режиме и в Thumb компилуруется в 16 инструкций. Листинги привожу ниже:

...

 

Вы уж простите, что лезу, но вменяемые компиляторы делают совсем другой код:

    623          __arm void memcpy_burst( U32 *pDest, U32 *pSrc, U32 size ) 
    624          {
    625            U32  wCnt = (size + 3) >> 2; // words count to be copied
   \                     memcpy_burst:
   \   00000000   032082E2           ADD      R2,R2,#+3
   \   00000004   2221A0E1           LSR      R2,R2,#+2
    626            do 
    627            { 
    628              *pDest++ = *pSrc++;
   \                     ??memcpy_burst_0:
   \   00000008   ........           LDR      R3,[R1], #+4
   \   0000000C   ........           STR      R3,[R0], #+4
    629            } while (--wCnt);
   \   00000010   012052E2           SUBS     R2,R2,#+1
   \   00000014   FBFFFF1A           BNE      ??memcpy_burst_0
    630          }
   \   00000018   0EF0A0E1           MOV      PC,LR           ;; return

    623          __thumb void memcpy_burst( U32 *pDest, U32 *pSrc, U32 size ) 
    624          {
    625            U32  wCnt = (size + 3) >> 2; // words count to be copied
   \                     memcpy_burst:
   \   00000000   D21C               ADDS     R2,R2,#+3
   \   00000002   9208               LSRS     R2,R2,#+2
    626            do 
    627            { 
    628              *pDest++ = *pSrc++;
   \                     ??memcpy_burst_0:
   \   00000004   0B68               LDR      R3,[R1, #+0]
   \   00000006   0360               STR      R3,[R0, #+0]
   \   00000008   091D               ADDS     R1,R1,#+4
   \   0000000A   001D               ADDS     R0,R0,#+4
    629            } while (--wCnt);
   \   0000000C   521E               SUBS     R2,R2,#+1
   \   0000000E   F9D1               BNE      ??memcpy_burst_0
    630          }
   \   00000010   7047               BX       LR

 

А вообще-то, правильный memcpy должен работать в ARM и использовать STM/LDM (для больших кусков, для маленьких - развернутое копирование с переходом в нужное место) - тогда будет максимальное быстродействие, конечно, для выровненных данных, без этого - никуда.

 

Кроме того при прочих равных (если код выполнять из RAM), для IP стека ARM режим практически не даст никакого выигрыша, возможно даже проиграет, из-за big-endian формата данных в пакетах и множества byte и half-word полей.

 

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

По поводу big-endian - тоже не согласен, переворот байт в арм-режиме занимает меньше, чем в тумбе.

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


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

Не надо уходить от memcpy! Стандартные библиотеки писаны далеко не дураками, и не на C.

Ссылка.

Ну тогда объясните, чем memcpy лучше обмена списками вида

typedef struct __DATALIST
{ 
   struct __DATALIST *next;  /* next datalist */
   void  *data;  /* adress for data */
   UINT data_len; /* lenght for this data in bytes */
   UINT total_len; /* total lenght for all data in datalist */

} DATALIST;

Вернее указателями на списки

 

Писать свой менеджер памяти, пока для меня будет сложно, поэтому разбираюсь с lwIP. Правда ещё попробую копировать по 4 байта. А пока такой вопрос - прошивка у меня компилируется в Thumb виде, согласно документации - использование ARM команд повышает быстродействие по сравнению с Thumb режимом. Пока что, при компиляции в ARM у меня система перестаёт работать - запускается нормально, но при попытке объявить (или запустить, ещё не разобрался) какую-либо задачу (использую FreeRTOS) - виснет. Соответсвенно вопрос - стоит ли этим баловаться (в смысле компиляцией в ARM) и в чём может быть проблема, что у меня подвисает система (может я не учёл что-либо общеизвестное)?
FreeRTOS не лучшая ОСь для повышения скорости. Если с lwIP более-менее разобрались, почему бы не выбрать что-нить (OS) побыстрее и посеръезнее? Попробуйте перевести проект на ucOS-II. Наверняка получите еще несколько десятков попугаев, а возможно и исчезнут проблемы при компиляции в ARM-режиме, хотя как уже сказали выше, толку не будет из за пакетов-венегретов :)
Изменено пользователем prottoss

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


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

Вы уж простите, что лезу, но вменяемые компиляторы делают совсем другой код:

Да дело не во вменяемости. Компилил вменяемым RVCT.

Единственное забыл включить оптимизацию.

 

Но даже если взять оптимизированный код приведенный вами, то в ARM получается 4 слова в ключевом цикле, в Thumb - 3. Так что все равно надо смотреть, что будет выполняться быстрее на SAM7 при размещении во флеш.

 

Стандартные библиотеки писаны далеко не дураками, и не на C.

Не важно на чем они писаны.

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

 

Не надо уходить от memcpy!

Тут все по-своему правы. Уходить надо, там где важна скорость. В этой ветке скорость важна, значит уходить надо.

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

 

Ну тогда объясните, чем memcpy лучше обмена списками вида

Есть места где от копирования не уйти. Если DMA не умеет самостоятельно склеивать списки в пакет, то "memcpy" как минимум понадобится для копирования ethernet/ip header'a в каждый пакет.

 

Для изначально "правильных" программ Thumb у меня ПРОИГРАЛ.

По объему кода?

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


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

Есть места где от копирования не уйти. Если DMA не умеет самостоятельно склеивать списки в пакет, то "memcpy" как минимум понадобится для копирования ethernet/ip header'a в каждый пакет.
Я же привел выше основную структуру данных... Пример: Нужно отправить TCP пакет = hEthernet+hIP+hTCP+DATA. Выделем у менеджера памяти минимум 4 блока (или больше в зависимости от размера блока DATA). У каждого блока есть DATALIST. Заполняем каждый блок свои хедером ну и пристегиваем данные. Адрес первого DATALIST (+ пристегнутые к нему остальные листы) отдаем драйверу EMAC. Драйвер после отправки пакета возвращает блоки опять в пулл памяти. И никккакого memcpy - тока обмен указателями

 

Вернее под DATA блоки вообще не выделяются - их транспорту отдает приложение, а он просто цепляет эти блоки в конец состава. Т.е разгрузки-погрузки вагонов не происходит :)

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


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

Я же привел выше основную структуру данных... Пример: Нужно отправить TCP пакет = hEthernet+hIP+hTCP+DATA. Выделем у менеджера памяти минимум 4 блока (или больше в зависимости от размера блока DATA). У каждого блока есть DATALIST. Заполняем каждый блок свои хедером ну и пристегиваем данные. Адрес первого DATALIST (+ пристегнутые к нему остальные листы) отдаем драйверу EMAC. Драйвер после отправки пакета возвращает блоки опять в пулл памяти. И никккакого memcpy - тока обмен указателями

Ну как же нет. Ключевая фраза у Вас - "заполняем каждый блок своим хидером". Как заполняем? быстрее и логичнее всего - скопировать заранее подготовленный шаблон. Стало быть полностью от копирования не ушли.

 

На более мощных процах (ARM9) EMAC DMA позволяет склеивать пакеты. Выделяется один блок требуемой длины под header, единожды заполняется (при старте TCP сессии). А дальше можно передавать DMA для каждого пакета этой сессии список состоящий из двух элементов, первый элемент - всегда указатель на один и тот же блок header, второй - на данные. Память с блоком содержищим Eth/IP header не будет освобождаться, и не будет нужды постоянно запонять Eth/IP.

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


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

Ну как же нет. Ключевая фраза у Вас - "заполняем каждый блок своим хидером". Как заполняем? быстрее и логичнее всего - скопировать заранее подготовленный шаблон. Стало быть от копирования нельзя уйти.
Хедер TCP я заполняю примерно так:

 

/* Fill IP header */
hIP.vihl = socket->vihl;
hIP.tos = socket->tos;
hIP.tlen = SWAP16(sizeof(IP_HEADER) +  sizeof(TCP_HEADER) + data_len);
hIP.id = IP_GetNextPacketID();
hIP.frags = 0;
hIP.ttl  = socket->ttl; /* ttl */
hIP.protocol  = IP_TCP; /* packet protocol */
hIP.src_ip = HOST_IP(); /* My IP */
hIP.dest_ip = arp_entry->ip; /* Dest IP */

/* Make checksum */
hIP.checksum  = 0;  
hIP.checksum  = (UINT16)~IP_MakeChecksum(0, &hIP, sizeof(IP_HEADER));

 

 

 

/* Fill TCP header */
hTCP.src_port  = socket->src_port;
hTCP.dest_port = socket->dest_port;
hTCP.seqno = SWAP32(socket->send_unacked);
hTCP.ackno = SWAP32(socket->receive_next);
hTCP.hlen = (sizeof(TCP_HEADER) << 2) & 0xfc;
hTCP.window = SWAP16(socket->window);
hTCP.urgent = 0;
if(flags) /* If flags are sets, we update them, differently we leave old */
hTCP.flags = flags;
hTCP.checksum = TCP_MakeChecksum(&hIP, &hTCP, data);

 

hIP и hTCP - соответсвенно блоки прамяти для IP и TCP заголовка, и мне не понятно, с какого шаблона я их сформирую:-)

 

Ну и, думаю, что БЫСТРЕЕ и логичнее заполнить именно так

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


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

hIP и hTCP - соответсвенно блоки прамяти для IP и TCP заголовка,

Ок, как часто у вас поменяется Ethernet header и сколько полей IP заголовка в пределах сессии?

То, что не меняется - и будет шаблоном.

 

и мне не понятно, с какого шаблона я их сформирую:-)

Ну например в структуре "socket" резервируете 14 + 20 байт +2 байта GAP. единожды заполняете Eth/IP header - это будет шаблон.

далее выделяете блок для Eth/IP, копируете туда шаблон спец функцией расчитаной на быстрое копирование выровненных блоков длиной 9 слов. Меняете поле Len и пересчитываете CS.

Уже будет гораздо быстрее чем заполнение для каждого пакета.

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


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

Надо ли отметить, что опустив эти проверки, получается дополнительный выигрыш в скорости?

Оверхед от проверок минимальный, если, конечно, копируются не блоки по 8 байт.

 

Тут все по-своему правы. Уходить надо, там где важна скорость. В этой ветке скорость важна, значит уходить надо.

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

ИМХО, если уходить значит вообще не копировать, тогда конечно. Но уделать по скорости копирования нормальную memcpy очень трудно.

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


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

Ок, как часто у вас поменяется Ethernet header и сколько полей IP заголовка в пределах сессии?

То, что не меняется - и будет шаблоном.

То что Вы называете шаблоном для Ethernet header я называю ARP-cashe :) С него и формируется Ethernet header. Но опять же 14 байт я копирую без memcpy а копированием 3-х 32-бит чисел (dest & source hardware address) и одного 16-бит (protocol)

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


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

расчитаной на быстрое копирование выровненных блоков длиной 9 слов.

 

Мне кажется, это мизер по сравнению с переносом при помощи memcpy из буфера в буфер собственно полезных данных, которых в каждом пакете под 1.5килобайта, если рекой льется ;)

 

Вот где надо копать.

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


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

Но опять же 14 байт я копирую без memcpy а копированием 3-х 32-бит чисел (dest & source hardware address) и одного 16-бит (protocol)

Под memcpy в последних двух постах я подразумевал "копирование", а не библиотечную функцию.

всевозможные ускоренные копирования, это подразновидности memcpy. ;>

 

Мне кажется, это мизер по сравнению с переносом при помощи memcpy из буфера в буфер собственно полезных данных, которых в каждом пакете под 1.5килобайта, если рекой льется ;)

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

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


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

Под memcpy в последних двух постах я подразумевал "копирование", а не библиотечную функцию.

всевозможные ускоренные копирования, это подразновидности memcpy. ;>

Ну в таком случае по вашему копирование двух указателей на данные а не самих данных тожде memcpy? :)

 

Я говорю не про не про Ethernet заголовок, который можно копировать и с помощью memcpy - ради бога (+- один попугай), а про то что ссказал Rst7 постом выше - про реку данных (или даже пакетов по 60 байт - их две реки в приличной сети), которые приходится постоянно копировать.

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


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

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

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

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

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

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

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

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

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

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