Мой компилятор MIPS сошел с ума, или я сошел с ума, выбрав MIPS?

Я использую процессор MIPS (PIC32) во встроенном проекте, но начинаю сомневаться в своем выборе. Я понимаю, что RISC-процессор, такой как MIPS, будет генерировать больше инструкций, чем можно было бы ожидать, но я не думал, что это будет так. Вот фрагмент листа разборки:

225:                         LATDSET = 0x0040;
    sw          s1,24808(s2)
    sw          s4,24808(s2)
    sw          s4,24808(s2)
    sw          s1,24808(s2)
    sw          s4,24808(s3)
    sw          s4,24808(s3)
    sw          s1,24808(s3)

226:                         {

227:                             porte = PORTE;
    lw          t1,24848(s4)
    andi        v0,t1,0xffff
    lw          v1,24848(s6)
    andi        ra,v1,0xffff
    lw          v1,24848(s6)
    andi        ra,v1,0xffff
    lw          v0,24848(s6)
    andi        t2,v0,0xffff
    lw          a2,24848(s5)
    andi        v1,a2,0xffff
    lw          t2,24848(s5)
    andi        v1,t2,0xffff
    lw          v0,24848(s5)
    andi        t2,v0,0xffff

228:                             if (porte & 0x0004)
    andi        t2,v0,0x4
    andi        s8,ra,0x4
    andi        s8,ra,0x4
    andi        ra,t2,0x4
    andi        a1,v1,0x4
    andi        a2,v1,0x4
    andi        a2,t2,0x4

229:                                 pst_bytes_somi[0] |= sliding_bit;
    or          t3,t4,s0
    xori        a3,t2,0x0
    movz        t3,s0,a3
    addu        s0,t3,zero
    or          t3,t4,s1
    xori        a3,s8,0x0
    movz        t3,s1,a3
    addu        s1,t3,zero
    or          t3,t4,s1
    xori        a3,s8,0x0
    movz        t3,s1,a3
    addu        s1,t3,zero
    or          v1,t4,s0
    xori        a3,ra,0x0
    movz        v1,s0,a3
    addu        s0,v1,zero
    or          a0,t4,s2
    xori        a3,a1,0x0
    movz        a0,s2,a3
    addu        s2,a0,zero
    or          t3,t4,s2
    xori        a3,a2,0x0
    movz        t3,s2,a3
    addu        s2,t3,zero
    or          v1,t4,s0
    xori        a3,a2,0x0
    movz        v1,s0,a3

Это кажется сумасшедшим количеством инструкций для простого чтения/записи и тестирования переменных по фиксированным адресам. На другом процессоре я, вероятно, мог бы сократить каждый оператор C примерно до 1..3 инструкций, не прибегая к написанному от руки ассемблеру. Очевидно, что тактовая частота довольно высока, но она не в 10 раз выше, чем у другого процессора (например, dsPIC).

У меня оптимизация на максимум. Мой компилятор C ужасен (это gcc 3.4.4)? Или это типично для MIPS?


person Rocketmagnet    schedule 06.10.2011    source источник
comment
MIPS представляет собой набор инструкций risc, а это означает, что потребуется больше инструкций для выполнения той же задачи, что и в эквивалентной архитектуре cisc, такой как x86. И я почти уверен, что авторы gcc знают, что делают.   -  person Chris Eberle    schedule 06.10.2011
comment
Вот почему я задаюсь вопросом, не сошел ли я с ума. Производительность, кажется, намного ниже того, что я ожидал бы от заведомо хорошей архитектуры и хорошего компилятора.   -  person Rocketmagnet    schedule 06.10.2011
comment
каковы определения различных переменных, которые вы используете в этом фрагменте кода? вывод ассемблера кажется довольно 16-битным для 32-битного MIPS...   -  person Adrien Plisson    schedule 06.10.2011
comment
Возможно, вы указали, что переменные должны быть упакованы или иметь нестандартное выравнивание. Если это так, компилятор будет вынужден обращаться к ним побайтно, что приведет к экстремальным последовательностям кода. (Отказ от ответственности: я не знаю MIPS.)   -  person Lindydancer    schedule 06.10.2011
comment
@ Адриан. Для ясности я их не включал. Но все они представляют собой 16-битные целые числа, адреса которых будут известны во время компоновки. т.е. не должно быть необходимости в поиске указателя или вычислении адреса.   -  person Rocketmagnet    schedule 06.10.2011
comment
Не используйте 16-битные целые числа. Смотрите мои обновления к моему ответу.   -  person R.. GitHub STOP HELPING ICE    schedule 06.10.2011
comment
Gcc 3.4.4 действительно очень старый. Более новый компилятор может работать намного лучше.   -  person Phil Miller    schedule 06.10.2011


Ответы (6)


Наконец понял ответ. Список разборки полностью вводит в заблуждение. Компилятор разворачивает цикл, и то, что мы видим под каждым оператором C, на самом деле в 8 раз превышает количество инструкций, потому что он разворачивает цикл в 8 раз. Инструкции не по последовательным адресам! Отключение развертывания цикла в параметрах компилятора приводит к следующему:

225:                         LATDSET = 0x0040;
    sw          s3,24808(s2)
226:                         {
227:                             porte = PORTE;
    lw          t1,24848(s5)
    andi        v0,t1,0xffff
228:                             if (porte & 0x0004)
    andi        t2,v0,0x4
229:                                 pst_bytes_somi[0] |= sliding_bit;
    or          t3,t4,s0
    xori        a3,t2,0x0
    movz        t3,s0,a3
    addu        s0,t3,zero
230:                 

Паника над всеми.

person Rocketmagnet    schedule 06.10.2011
comment
Это имеет гораздо больше смысла. Я думаю, вы должны принять свой собственный ответ вместо моего, несмотря на то, что использование MIPS по-прежнему безумно. :-) - person R.. GitHub STOP HELPING ICE; 06.10.2011
comment
Я не понимаю, почему развертывание цикла приводит к повторной записи одной и той же области памяти много раз - person phuclv; 15.06.2014
comment
@LưuVĩnhPhúc - Нет. Это не перезаписывает одну и ту же область памяти повторно. Он генерирует несколько инструкций по сборке для каждого оператора C. - person Rocketmagnet; 15.07.2014

Я думаю, что ваш компилятор ведет себя неправильно... Проверьте, например, это утверждение:

228:                             if (porte & 0x0004)
    andi        t2,v0,0x4  (1)
    andi        s8,ra,0x4  (2)
    andi        s8,ra,0x4  (3)
    andi        ra,t2,0x4  (4)
    andi        a1,v1,0x4  (5)
    andi        a2,v1,0x4  (6)
    andi        a2,t2,0x4  (7)

Очевидно, что есть инструкции, которые в основном ничего не делают. Инструкция (3) не делает ничего нового, поскольку сохраняет в s8 тот же результат, вычисленный инструкцией (2). Инструкция (6) также не имеет никакого эффекта, так как она переопределяется следующей инструкцией (7), я полагаю, что любой компилятор, выполняющий некоторую фазу статического анализа, по крайней мере удалит инструкции (3) и (6).

Аналогичный анализ применим и к другим частям вашего кода. Например, в первом операторе вы можете видеть, что некоторые регистры (v0 и v0) загружаются одним и тем же значением дважды.

Я думаю, что ваш компилятор плохо справляется с оптимизацией скомпилированного кода.

person gusbro    schedule 06.10.2011

MIPS — это, по сути, воплощение всего того, что было глупо в дизайне RISC. В наши дни x86 (и x86_64) вобрал в себя почти все полезные идеи из RISC, и ARM стала намного более эффективной, чем традиционный RISC, оставаясь при этом верной концепции RISC о сохранении небольшого систематического набора инструкций.

Чтобы ответить на вопрос, я бы сказал, что вы сошли с ума, выбрав MIPS, или, что еще важнее, выбрав его, не изучив сначала немного о MIPS ISA и почему это так плохо и с какой неэффективностью вам придется мириться, если вы хотите использовать его. Я бы выбрал ARM для маломощных / встроенных систем в большинстве ситуаций или, что еще лучше, Intel Atom, если вы можете позволить себе немного больше энергопотребления.

Редактировать: На самом деле, вторая причина, по которой вы можете сойти с ума... Судя по комментариям, вы используете 16-битные целые числа. Вы никогда не должны использовать типы меньше, чем int в C, кроме как в массивах или в структуре, которая будет размещаться в больших количествах (либо в массиве, либо каким-либо другим способом, таким как связанный список/дерево/и т. д.). Использование малых типов никогда не даст никаких преимуществ, кроме экономии места (что не имеет значения, пока у вас не будет большого количества значений такого типа) и почти наверняка менее эффективно, чем использование «обычных» типов. В случае с MIPS разница огромна. Переключитесь на int и посмотрите, исчезнет ли ваша проблема.

person R.. GitHub STOP HELPING ICE    schedule 06.10.2011
comment
Использование 16-битных целых чисел в MIPS не приводит к резкому снижению эффективности. См. мой ответ ниже, в котором показана стоимость одной дополнительной инструкции для кода, указанного в вопросе. - person markgz; 06.10.2011

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

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

Где вы взяли компилятор? Я обнаружил, что некоторые из «простых» источников часто содержат довольно ужасные инструменты. Мои друзья, занимающиеся разработкой встраиваемых систем, обычно компилируют свою собственную цепочку инструментов с иногда намного лучшими результатами.

person Edwin Buck    schedule 06.10.2011
comment
Компилятор - официальный компилятор Microchip C32, стоивший мне хороших денег. - person Rocketmagnet; 06.10.2011
comment
Если это будет стоить вам достаточно, то они должны предоставить какую-то поддержку. Этот код выглядит странно. Возможно, они могли бы предложить объяснение. - person Edwin Buck; 06.10.2011

Я попытался скомпилировать следующий код с помощью CodeSourcery MIPS GCC 4.4-303 с параметром -O4. Я пробовал это с uint32_t и uint16_t:

#include <stdint.h>
void foo(uint32_t PORTE, uint32_t pst_bytes_somi[], uint32_t sliding_bit) {
    uint32_t LATDSET = 0x0040;
    {
        uint32_t porte = PORTE;
        if (porte & 0x0004)
            pst_bytes_somi[0] |= sliding_bit;
        if (porte & LATDSET)
            pst_bytes_somi[1] |= sliding_bit;
    }
}

Вот разборка с целыми числами uint32_t:

        uint32_t porte = PORTE;
        if (porte & 0x0004)
   0:   30820004    andi    v0,a0,0x4
   4:   10400004    beqz    v0,18 <foo+0x18>
   8:   00000000    nop
./foo32.c:7
            pst_bytes_somi[0] |= sliding_bit;
   c:   8ca20000    lw  v0,0(a1)
  10:   00461025    or  v0,v0,a2
  14:   aca20000    sw  v0,0(a1)
./foo32.c:8
        if (porte & LATDSET)
  18:   30840040    andi    a0,a0,0x40
  1c:   10800004    beqz    a0,30 <foo+0x30>
  20:   00000000    nop
./foo32.c:9
            pst_bytes_somi[1] |= sliding_bit;
  24:   8ca20004    lw  v0,4(a1)
  28:   00463025    or  a2,v0,a2
  2c:   aca60004    sw  a2,4(a1)
  30:   03e00008    jr  ra
  34:   00000000    nop

Вот разборка с целыми числами uint16_t:

        if (porte & 0x0004)
   4:   30820004    andi    v0,a0,0x4
   8:   10400004    beqz    v0,1c <foo+0x1c>
   c:   30c6ffff    andi    a2,a2,0xffff
./foo16.c:7
            pst_bytes_somi[0] |= sliding_bit;
  10:   94a20000    lhu v0,0(a1)
  14:   00c21025    or  v0,a2,v0
  18:   a4a20000    sh  v0,0(a1)
./foo16.c:8
        if (porte & LATDSET)
  1c:   30840040    andi    a0,a0,0x40
  20:   10800004    beqz    a0,34 <foo+0x34>
  24:   00000000    nop
./foo16.c:9
            pst_bytes_somi[1] |= sliding_bit;
  28:   94a20002    lhu v0,2(a1)
  2c:   00c23025    or  a2,a2,v0
  30:   a4a60002    sh  a2,2(a1)
  34:   03e00008    jr  ra
  38:   00000000    nop

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

person markgz    schedule 06.10.2011
comment
Я бы так и подумал. Использование 16-битных целых чисел не имело бы принципиального значения. В MIPS есть инструкции для обработки 32-, 16- и 8-битных целых чисел. - person Rocketmagnet; 07.10.2011

Вы включили оптимизацию компилятора? В неоптимизированном коде много избыточности.

person zvrba    schedule 06.10.2011