#c #optimization #valgrind #callgrind
#c #оптимизация #valgrind #callgrind
Вопрос:
Рассмотрим этот фрагмент кода
| 34 static bool
| 35 _valid_character(char *str, size_t *idx)
| 36 {
| 37 char c = str[*idx];
| 38
| 39 if (c != '\' amp;amp; c != '"') {
| 40 (*idx) = 1;
| 41 return true;
| 42 } else if (c == '"') {
| 43 return false;
| 44 } else {
| 45 char b = str[(*idx) 1];
| 46 switch (b) {
| 47 case '"':
| 48 case '\':
| 49 case '/':
| 50 case 'b':
| 51 case 'f':
| 52 case 'n':
| 53 case 'r':
| 54 case 't':
| 55 (*idx) = 2;
| 56 return true;
| 57 default:
| 58 pprint_error("%s@%s:%d invalid escape sequnce \%c%c (aborting)",
| 59 __FILE_NAME__, __func__, __LINE__, c, b);
| 60 abort();
| 61 }
| 62 }
| 63 }
Эта функция является основной причиной значительного замедления моего кода. Я пробовал использовать только операторы if и только операторы switch, но это лучшая оптимизация, которую я могу придумать, где callgrind приводит к наилучшей производительности. На эту функцию приходится около 25% времени выполнения, поэтому в соответствии с сомнительным (извините) правилом 80/20 в моих интересах ускорить этот код.
Ниже приведен вывод callgrind, визуализированный с помощью kcachegrind для этой функции.
Кажется, callgrind говорит, что мой первый прыжок — худший прыжок, но я перепробовал все комбинации этого оператора if, чтобы попытаться минимизировать прыжки, и каждый раз первый прыжок — худший прыжок.
Этот код был скомпилирован с clang
clang ... -Weverything -Werror -Wpedantic -m64 -O0 -g
Итак, мой вопрос заключается в том, какой наилучший способ оптимизировать этот код и альтернативные методы, включая модификацию сборки, для оптимизации этого простого, но смертельно опасного фрагмента кода.
Я хотел бы продолжать использовать -O0
, так как я нахожу его наиболее полезным для отладки и поиска оптимизаций. -O1,2,3,fast
как правило, абстрагируются от многого, чтобы получить хорошее представление о том, что происходит.
— Редактировать 1 Был запрошен пример ввода.
char cstr[BUF] = "abcdefghijklmnopqrstuvwxyz"randomgarbageafterthis";
size_t idx = 0;
while (_valid_character(cstr, amp;idx));
В конце вводится просто строка, и цикл вызывается до конечного "
символа. Конечное значение idx имеет значение cstr[idx] == '"''
true.
Комментарии:
1. Не могли бы вы предоставить нам, пожалуйста, полный пример с примерами входных данных и вашими типами? (Таким образом, мы можем воспроизвести вашу производительность и все они будут иметь одинаковый набор входных данных)
2. @JCWasmx86 пример использования этого был добавлен к вопросу.
3. Боюсь, это слишком мало данных для теста, одна строка — это не так много. Кроме того, вы должны выполнять тесты всегда с наивысшим уровнем оптимизации, так как
-O0
это не отражает производительность, поскольку для каждой строки есть загрузки и сохранения.4. Запрос на хорошую производительность и использование
-O0
является просто эксклюзивным и не имеет особого смысла. Этот режим в основном компилируется так, как указано для абстрактной машины. Вы не даете компилятору возможности проявить свой потенциал.5. Вместо того, чтобы проверять, допустим ли каждый символ, почему бы не выполнить поиск первого (возможного) недопустимого символа?
Ответ №1:
Если вы действительно хотите оптимизировать свой код, используйте ручку и бумагу и создайте таблицу булевой логики и оптимизируйте ее алгебру по своему усмотрению. После этого воссоздайте свой код и, пожалуйста, используйте break;
в своих switch
операторах.
Например:
Это ваш код.
if (c != '\' amp;amp; c != '"')
{
(*idx) = 1;
return true;
}
Этот вариант быстрее, чем предыдущий.
if (c != '\')
{
if (c != '"')
{
(*idx) = 1;
return true;
}
}
Вместо сравнения с некоторыми символами, такими как if (c != '\')
, используйте зарегистрированные КОНСТАНТЫ.
register const char BACKSLASH = '\';
А также выполните:
register char c = str[*idx];
Константы и переменные, которые загружаются в регистр процессора, намного быстрее в сравнении.
if (c != BACKSLASH)
Как правило, если вам нужен быстрый код, используйте только if
и else
и используйте простые логические аргументы сравнения.
Редактировать:
Пример кода 1:
if (c != '\')
{
if (c == '"')
{
return false;
}
else
{
(*idx) = 1;
return true;
}
}
эквивалентно:
Пример кода 2:
if (c != '\')
{
if (c != '"')
{
(*idx) = 1;
return true;
}
}
else if (c == '"')
{
return false;
}
Но первый пример кода сравнивается только 2 раза, а второй код сравнивается 3 раза.
Комментарии:
1. Не могли бы вы объяснить, почему эти изменения должны повысить производительность? Учитывая, что C использует оценку короткого замыкания для условий, не должно быть заметной разницы.
2. Что вы имеете в виду под «использовать break»? Должны ли они использовать
break
вместоreturn
или должны использоваться только отдельные значения, а инструкция копируется в каждый отдельный случай?3. Как это не работает? Это работает отлично.
i
никогда не будет выполняться точно так же, как сif(0){if(i ){}}
4. Если первая часть выражения уже есть
false
, часть послеamp;amp;
не будет вычислена, потому что результат больше никогда не может быть полученtrue
. Это то, что делает оценка короткого замыкания.5. Правильно. Это основная цель оценки короткого замыкания.