Подтвердить что ты не робот

Возможно ли, чтобы GCC оптимизировал isnan (x) || isnan (y) в isunordered (x, y)?

Здесь мой код:

int f(double x, double y)
{
  return std::isnan(x) || std::isnan(y);
}

Если вы используете C вместо С++, просто замените std:: на __builtin_ (не просто удалите std:: по причинам, указанным здесь: Почему GCC реализовать isnan() более эффективно для С++ <cmath> чем C < math.h > ?).

Здесь сборка:

ucomisd %xmm0, %xmm0 ; set parity flag if x is NAN
setp    %dl          ; copy parity flag to %edx
ucomisd %xmm1, %xmm1 ; set parity flag if y is NAN
setp    %al          ; copy parity flag to %eax
orl     %edx, %eax   ; OR one byte of each result into a full-width register

Теперь попробуйте альтернативную формулировку, которая делает то же самое:

int f(double x, double y)
{
  return std::isunordered(x, y);
}

Здесь сборка для альтернативы:

xorl    %eax, %eax
ucomisd %xmm1, %xmm0
setp    %al

Это здорово - мы сокращаем сгенерированный код почти вдвое! Это работает, потому что ucomisd устанавливает флаг четности, если один из его операндов является NAN, поэтому мы можем тестировать два значения за раз, SIMD-стиль.

Вы можете видеть код, например, оригинальную версию в дикой природе, например: https://svn.r-project.org/R/trunk/src/nmath/qnorm.c

Если бы мы могли сделать GCC достаточно умным, чтобы объединить два вызова isnan() во всем мире, это было бы довольно круто. Мой вопрос: можем ли мы и как? У меня есть некоторое представление о том, как работают компиляторы, но я не знаю, где в GCC такая оптимизация может быть выполнена. Основная идея - всякий раз, когда есть пара вызовов isnan() (или __builtin_isnan) OR'd вместе, она должна издавать одну команду ucomisd, используя два операнда одновременно.

Отредактировано, чтобы добавить некоторые исследования, вызванные Базиле Старинкевичем:

Если я скомпилирую с -fdump-tree-all, я нахожу два файла, которые кажутся релевантными. Во-первых, *.gimple содержит это (и немного больше):

D.2229 = x unord x;
D.2230 = y unord y;
D.2231 = D.2229 | D.2230;

Здесь мы можем ясно видеть, что GCC знает, что он пройдет (x, x) до isunordered(). Если мы хотим оптимизировать преобразование на этом уровне, это правило будет примерно следующим: "Заменить a unord a | b unord b на a unord b". Это то, что вы получаете при компиляции моего второго кода C:

D.2229 = x unord y;

Другим интересным файлом является *.original:

return <retval> = (int) (x unord x || y unord y);

Это фактически весь файл без комментария, созданный -fdump-tree-original. И для лучшего исходного кода это выглядит так:

return <retval> = x unord y;

Очевидно, что такое же преобразование можно применить (просто здесь || вместо |).

Но, к сожалению, если мы изменим исходный код, например:

if (__builtin_isnan(x))
  return true;
if (__builtin_isnan(y))
  return true;
return false;

Затем мы получаем совершенно разные выходные файлы Gimple и Original, хотя последняя сборка такая же, как и раньше. Так, может быть, лучше попробовать эту трансформацию на более позднем этапе в конвейере? Файл *.optimized (среди прочих) показывает тот же код для версии с "if" s, что и для исходной версии, так что обещание.

4b9b3361

Ответ 2

Есть два вопроса:

  • - это оптимизация, которую вы предлагаете всегда законной в строгом стандарте С++ 11 (я не знаю).

  • можно настроить GCC, добавив такую ​​оптимизацию: да! Вы можете расширить его, используя MELT -e.g. напишите свое собственное расширение MELT, сделав это, или с вашим собственным плагином GCC, закодированным (больно) на С++.

Однако добавление дополнительной оптимизации в GCC - это значительная работа (даже с MELT): вам нужно понять внутренности GCC. Таким образом, это больше, чем неделю работы.

И я не уверен, что такая оптимизация действительно стоит усилий.