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

Предварительное вычисление в gcc-4.8 (С++ 11)

Тестирование кода в старых gcc-4.4.0 и gcc-4.6.4, компилятор смог применить интеллектуальную оптимизацию и предварительно вычислить результат для входов const:

#include <iostream>
#include <chrono>
using namespace std;

const auto N = 1000000000ULL;  // constexptr is tested, no effect

unsigned long long s(unsigned long long n)
{
    auto s = 0ULL;
    for (auto i = 0ULL; i < n; i++)
        s += i;
    return s;
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();

    auto x = s(N);

    auto t2 = std::chrono::high_resolution_clock::now();
    auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(t2-t1).count();
    cout << "Result: " << x << " -- time (ms):" << t/0.1e7 << endl;
}

N является постоянным значением, тогда компилятор может запустить функцию s во время компиляции и присвоить результат x. (Для N не требуется расчет времени выполнения)

Результаты в разных версиях gcc (а также версия clang):

  • Последняя версия (clang-3.4) результат (предварительно вычислен): 0.001532 ms.
  • Старая версия (gcc-4.4.0) результат (предварительно рассчитанная): 0.013517 ms.
  • Старая версия (gcc-4.6.4) результат (предварительно вычислен): 0.001 ms.
  • Более новая версия (gcc-4.8.0 +) не вычисляет ее во время компиляции, результат: 1313.78 ms!!.

Вопрос:

  • Не оптимизирована ли эта оптимизация в 4.8.1? Почему?
  • Есть ли какой-либо компилятор/переключатель для его включения (если он отключен по умолчанию)?
  • Если это опустить, как заставить компилятор выполнить этот предварительный расчет?

 


Примечание (1): Я тестировал оба переключателя -O2 и -O3, никаких эффектов.

Примечание (2): Принуждение, я имею в виду команды и переключатели компилятора.

Примечание (3): Функция s является просто примером, ее можно заменить более сложными функциями.

4b9b3361

Ответ 1

Я представил его как ошибку. Да, это регрессия в версии 4.8, которая исправлена ​​в новых версиях 5 недель назад. Следуйте за ним здесь:

Ответ 2

Вы можете заставить его работать во время компиляции, используя новое ключевое слово С++ 11 constexpr.

Сначала вы должны преобразовать итерацию в рекурсию (это требование удаляется в С++ 1y), например:

constexpr unsigned long long s(unsigned long long n)
{
    return n? n + s(n-1): 0;
}

Или с хвостовой рекурсией (все еще хорошо работает для вычисления времени выполнения, когда вход является переменным):

constexpr unsigned long long s_impl( unsigned long long accum, unsigned long long n, unsigned long long n_max )
{
    return (n < n_max)? s_impl(accum + n + 1, n + 1, n_max): accum;
}
constexpr unsigned long long s(unsigned long long n)
{
    return s_impl(0, 0, n);
}

(В С++ 1y все, что вам нужно сделать, это добавить ключевое слово constexpr к существующей реализации)

Затем вызовите его с помощью

constexpr auto x = s(N);

Ответ 3

Способ С++ 11 для обработки вычислений во время компиляции - это использование constexpr. К сожалению, функции constexpr несколько ограничены в том, что можно сделать. В С++ 11 функция constexpr допускает содержать пустые операторы, объявления static_assert(), typedef s и using декларации/директивы и ровно одно return -statement (я временно запутался, потому что Я смотрел проект С++ 14, который был ослаблен правилами). То есть вам нужно будет рекурсивно сформулировать вашу функцию. С положительной стороны, если функция constexpr вызывается с постоянным выражением, она будет оцениваться во время компиляции.

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

  • сделать функцию s() a inline.
  • объявить N как constexpr unsigned long long N = 1000000000ULL;
  • убедитесь, что вы используете подходящий уровень оптимизации.

Ответ 4

Эта оптимизация опущена в 4.8.1?

Похоже, что он ушел. Он все еще присутствует в 4.7.2, хотя.

Почему? [Из одного из ваших комментариев:] Я думаю, что оптимизация была отличной и ничего не повредила.

Скорее всего, это случайный случай, и разработчики gcc не знают об этом.

Я могу думать о хорошей причине, по которой я хотел бы, по крайней мере, дать верхнюю оценку этой оптимизации. Я был укушен MSVC еще в 2009 году: когда я дал ему сгенерированный машиной код C, он пытался его оптимизировать, и компилятор боролся с ним в течение нескольких минут. Очевидно, что он отчаянно пытался сделать некоторую оптимизацию, которая должна была быть ограничена каким-то образом, чтобы компилятор не пытался бороться за несколько минут над исходным файлом в 7 КБ. Моя точка зрения: вы можете ограничить оптимизацию, которая может увеличить время компиляции слишком сильно.

Однако, похоже, это не так. Я пробовал его с довольно маленьким N, и эта оптимизация также не выполняется.

Если это опустить, как заставить компилятор выполнить этот предварительный расчет?
Примечание (2): Принуждение, я имею в виду команды и переключатели компилятора

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