Статическое предсказание ветвления на Arm9 с RVCT4.0 - PullRequest
3 голосов
/ 23 марта 2011

Я пишу некоторый код C на логи для процессора ARM9.Этот код будет записывать некоторые данные, если присутствует динамический модуль.Модуль обычно не будет присутствовать в производственной сборке, но код журнала всегда будет скомпилирован. Идея состоит в том, что если у клиента возникнет ошибка, мы можем загрузить этот модуль, и код журнала выведет отладочную информацию.

Код регистрации должен иметь минимальное влияние, когда модуль отсутствует, поэтому каждый цикл считается.В целом, код регистрации выглядит примерно так:

__inline void log_some_stuff(Provider *pProvider, other args go here...)
{
    if (NULL == pProvider)
        return;
    ... logging code goes here ...
}

При включенной оптимизации RVCT 4.0 генерирует код, который выглядит следующим образом:

ldr     r4,[r0,#0x2C]     ; pProvider,[r0,#44]
cmp     r4,#0x0           ; pProvider,#0
beq     0x23BB4BE (usually taken)
... logging code goes here...
... regular code starts at 0x23BB4BE

Этот процессор не имеет предиктора ветвления иНасколько я понимаю, каждый раз, когда берется ветвь, есть 2 цикла (нет, если ветвь не берется).

Я бы хотел, чтобы общий случай, где NULL == pProvider, был быстрым случаем, когда ветвь не берется.Как я могу заставить RVCT 4.0 генерировать код, подобный этому?

Я пытался использовать __builtin_expect следующим образом:

if (__builtin_expect(NULL == pProvider, 1))
    return;

К сожалению, это не влияет на сгенерированный код.Я неправильно использую __builtin_expect?Есть ли другой метод (надеюсь, без встроенной сборки)?

Ответы [ 4 ]

1 голос
/ 23 марта 2011

Если вы используете следующую конструкцию:

void log_some_stuff_implementation(Provider *pProvider, int x, int y, char const* str);


__inline void log_some_stuff(Provider *pProvider, int x, int y, char const* str)
{
    if (__builtin_expect( pProvider != NULL, 0)) {
        log_some_stuff_implementation(pProvider, x, y, str);
    }

    return;
}

GCC 4.5.2 с -O2 генерирует следующий код (по крайней мере, для моего простого теста) для вызова log_some_stuff():

// r0 already has the Provider* in it - r2 has a value that indicates whether 
//      r0 was loaded with a valid pointer or not
cmp r2, #0
ldrne   r3, [r1, #0]
addne   r1, r2, #1
ldrneb  r2, [r3, #0]    @ zero_extendqisi2
blne    log_some_stuff_implementation

Таким образом, в общем случае (где Provider * равен NULL) используются 4 инструкции, но не выполняются из-за условия, но конвейер ARM не сбрасывается.Я думаю, что это, вероятно, примерно так же хорошо, как вы получите для общего случая, когда вы на самом деле не хотите, чтобы код ведения журнала запускался.

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

Кстати, для моего простого теста генерируется та же самая последовательность кода (или, по сути, та же самая последовательность), даже если __builtin_expect() опущен, однако я представляю, что в более сложных последовательностях, чем в моем простом тесте, встроенная программа может помочь компилятору.Поэтому я бы, вероятно, оставил это, но я бы также использовал более читаемые версии, такие как макросы ядра Linux:

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)
1 голос
/ 23 марта 2011

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

__inline void log_some_stuff(Provider *pProvider, other args go here...)
{
    if (pProvider) {
       ... logging code goes here ...
    }
}

, который «может» компилироваться в:

ldr     r4,[r0,#0x2C]     ; pProvider,[r0,#44]
cmp     r4,#0x0           ; pProvider,#0
bneq     logging_code (usually NOT taken)
... regular code here
logging_code: .. well anywhere

если вам повезет, но даже если это произойдет сейчас, каждое изменение в компиляторе может изменить его, и я понятия не имею, приведет ли это даже к ассемблерному коду с любым компилятором, который вы используете.Так что, вероятно, написать это во встроенной сборке в любом случае?Не так много кода и gcc (а также VC; я полагаю, что и другие) делают это довольно легко.Проще всего определить дополнительный метод с помощью кода регистрации и вызвать его (понятия не имею о ARM ABI, поэтому вам придется написать его самостоятельно)

0 голосов
/ 23 марта 2011

Вы можете использовать goto:

__inline void log_some_stuff(Provider *pProvider, other args go here...)
{
    if (pProvider != NULL)
        goto LOGGING;
    return;
LOGGING:
    ... logging code goes here ...
}

Использование __ builtin_expect проще, но я не уверен, что RVCT имеет его.

0 голосов
/ 23 марта 2011

Оптимизация вашего филиала принесет вам очень мало. Вы могли бы получить намного больше, если бы вы сделали следующее:

#define log_some_stuff(pProvider, other_arg) \
        do {\
           if(pProvider != NULL) \
              real_log_some_stuff(pProvider, other_arg); \
         } \
        while(0)

что он будет делать, так это встроит проверку NULL во весь вызывающий код. Это может показаться потерей, но на самом деле происходит то, что компилятор может избежать накладных расходов при вызове функции, включая нажатие регистров, саму ветвь, а также аннулирование r0-r3 и lr с помощью простой проверки NULL (что пришлось делать в любом случае). В целом, я бы поспорил, что это принесет гораздо больше, чем один цикл, который вы бы сэкономили, выполнив одну инструкцию раньше.

...