Библиотека сайта rus-linux.net
Что каждый программист должен знать о памяти.
Часть 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__
. Этот макрос выполняет следующие задачи:
- Размещает два слова в секции
predict_data
, которые являются счетчиками правильных и неправильных предсказаний. Эти два поля с помощью операции%=
получают уникальные имена; лидирующие точки гарантируют, что символы не попадут в таблицу символов. - Размещает одно слово в секции
predict_line
, которое содержит номер строки, где используется макросlikely
илиunlikely
. - Размещает одно слово в секции predict_file, которое содержит указатель на имя файла, где используется макрос
likely
илиunlikely
. - Увеличивает счетчики "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 могут выдавать свои собственные выходные данные; это надо иметь в виду при анализе данных.
Назад | Оглавление | Вперед |