Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Аппаратное обеспечение

Что каждый программист должен знать о памяти.

Часть 9: Приложения и библиография


Назад Оглавление Вперед

9.2 Предсказание отладочного ветвления

Если, как было рекомендовано, использовать определения likely и unlikely из раздела 6.2.2, то легко {по крайней мере, с помощью инструментальных средств GNU} воспользоваться отладочный режимом для проверки допущений, которые должны быть действительно истинными. Определения макросов можно заменить следующим образом:

#ifndef DEBUGPRED
# define unlikely(expr) __builtin_expect (!!(expr), 0)
# define likely(expr) __builtin_expect (!!(expr), 1)
#else
asm (".section predict_data, \"aw\"; .previous\n"
     ".section predict_line, \"a\"; .previous\n"
     ".section predict_file, \"a\"; .previous");
# ifdef __x86_64__
#  define debugpred__(e, E) \
  ({ long int _e = !!(e); \
     asm volatile (".pushsection predict_data\n" \
                   "..predictcnt%=: .quad 0; .quad 0\n" \
                   ".section predict_line; .quad %c1\n" \
                   ".section predict_file; .quad %c2; .popsection\n" \
                   "addq $1,..predictcnt%=(,%0,8)" \
                   : : "r" (_e == E), "i" (__LINE__), "i" (__FILE__)); \
    __builtin_expect (_e, E); \
  })
# elif defined __i386__
#  define debugpred__(e, E) \
  ({ long int _e = !!(e); \
     asm volatile (".pushsection predict_data\n" \
                   "..predictcnt%=: .long 0; .long 0\n" \
                   ".section predict_line; .long %c1\n" \
                   ".section predict_file; .long %c2; .popsection\n" \
                   "incl ..predictcnt%=(,%0,4)" \
                   : : "r" (_e == E), "i" (__LINE__), "i" (__FILE__)); \
    __builtin_expect (_e, E); \
  })
# else
#  error "debugpred__ definition missing"
# endif
# define unlikely(expt) debugpred__ ((expr), 0)
# define likely(expr) debugpred__ ((expr), 1)
#endif

В этих макросах используется множество функциональных возможностей, реализуемых ассемблером и компоновщиком GNU при сборке файлов ELF. Первая инструкция asm в контейнере DEBUGPRED определяет три дополнительных раздела; она, в основном, предоставляет ассемблеру сведения о том, какие должны быть созданы секции. Все секции доступны во время выполнения программы, а в секцию predict_data также можно осуществлять запись. Важно, чтобы все названия секций были допустимыми идентификаторами языка C. Причина станет понятной в ближайшее время.

Новые определения макросов likely и unlikely обращаются к машинно-спицифическому макросу debugpred__. Этот макрос выполняет следующие задачи:

  1. Размещает два слова в секции predict_data, которые являются счетчиками правильных и неправильных предсказаний. Эти два поля с помощью операции %= получают уникальные имена; лидирующие точки гарантируют, что символы не попадут в таблицу символов.
  2. Размещает одно слово в секции predict_line, которое содержит номер строки, где используется макрос likely или unlikely.
  3. Размещает одно слово в секции predict_file, которое содержит указатель на имя файла, где используется макрос likely или unlikely.
  4. Увеличивает счетчики "correct" ("правильно") или "incorrect" ("неправильно") для этого макроса в соответствии с фактическим значением выражения e. Мы здесь не используем атомарные операции, поскольку они, в своей массе, медленнее и абсолютная точность при маловероятном случае коллизии не так важна. Если потребуется, то это достаточно легко изменить.

Псевдооперации .pushsection и .popsection описаны в руководстве по ассемблеру. Заинтересованному читателю можно предложить изучить детали этого определения при помощи подсказок и руководств, а также методом некоторых проб и ошибок.

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

extern long int __start_predict_data;
extern long int __stop_predict_data;
extern long int __start_predict_line;
extern const char *__start_predict_file;

static void
__attribute__ ((destructor))
predprint(void)
{
  long int *s = &__start_predict_data;
  long int *e = &__stop_predict_data;
  long int *sl = &__start_predict_line;
  const char **sf = &__start_predict_file;
  while (s < e) {
    printf("%s:%ld: incorrect=%ld, correct=%ld%s\n", *sf, *sl, s[0], s[1],
           s[0] > s[1] ? "   <==== WARNING" : "");
    ++sl;
    ++sf;
    s += 2;
  }
}

Здесь начинает играть свою роль тот факт, что названия разделов являются допустимыми идентификаторами языка C; они используются компоновщиком GNU, если это потребуется, при автоматическом создании в секции двух символов. Символы __start_XYZ соответствуют началу секции XYZ, а __stop_XYZ является месторасположением первого байта, идущего за секцией XYZ. Эти символы позволяют повторять содержимое секции во время выполнения программы. Обратите внимание, что поскольку содержимое секций может зависеть от того, какие файлы использует компоновщик во время компоновки программы, у компилятора и ассемблера недостаточно информации для того, чтобы определить размер секции. Только с помощью этих магических символов, генерируемых компоновщиком, можно обеспечить повторное исполнение содержимого секции.

Но код повторно выполняется не только в этой одной секции: это происходит еще в трех секциях. Поскольку мы знаем, что для каждых двух слов, добавляемых в секцию predict_data, мы добавляем одно слово для каждой секции predict_line и predict_file, у нас нет возможности проверять границы этих двух секций. Мы просто используем указатели и увеличиваем им в унисон.

Код выводит строку для каждого предсказания, которое появляется в коде. В ней указывается, где предсказание неверно. Конечно, это можно изменить и режим отладки задать таким образом, чтобы отмечать только записи, в которых больше неверных предсказаний, чем верных. Это кандидаты на изменение. Есть частности, из-за которых происходит усложнение; например, если предсказание ветвления происходит внутри макроса, который используется в нескольких местах, тогда прежде, чем будет принято окончательное решение, следует рассмотреть совместно все макросы.

Два последних комментария: данных, необходимых для этой отладочной операции, немало и в случае, когда используются DSO, их использование ресурсоемко (месторасположение секции predict_file должно изменяться). Таким образом, при создании двоичных файлов режим отладки нужно отключать. Наконец, каждый исполняемый файл и DSO могут выдавать свои собственные выходные данные; это надо иметь в виду при анализе данных.


Назад Оглавление Вперед