Преимущества чистой функции

Сегодня я читал о чистой функции, запутался в ее использовании:

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

например. strlen() - чистая функция, а rand() - нечистая.

__attribute__ ((pure)) int fun(int i)
{
    return i*i;
}

int main()
{
    int i=10;
    printf("%d",fun(i));//outputs 100
    return 0;
}

http://ideone.com/33XJU

Вышеприведенная программа ведет себя так же, как и при отсутствии объявления pure.

В чем преимущества объявления функции как pure [если нет изменения в выходе]?

Ответ 1

pure позволяет компилятору знать, что он может сделать определенные оптимизации относительно функции: представьте себе немного кода, например

for (int i = 0; i < 1000; i++)
{
    printf("%d", fun(10));
}

С чистой функцией компилятор может знать, что ему нужно оценивать fun(10) один раз и один раз, а не 1000 раз. Для сложной функции это большая победа.

Ответ 2

Когда вы говорите, что функция "чистая", вы гарантируете, что она не имеет внешне видимых побочных эффектов (и, как говорится в комментарии, если вы лжете, могут случиться плохие вещи). Зная, что функция "чистая" имеет преимущества для компилятора, который может использовать это знание для выполнения определенных оптимизаций.

Вот что документация GCC говорит об атрибуте pure:

чистым

Многие функции не имеют никаких эффектов, кроме возвращаемого значения и их возврата Значение зависит только от параметров и/или глобальных переменных. Такая функция может быть подвержена общему исключению подвыражения и оптимизацию цикла так же, как и арифметический оператор. Эти функции должны быть объявлены с атрибутом pure. Например,

          int square (int) __attribute__ ((pure));

Ответ Филиппа уже показывает, как знать, что функция является "чистой", может помочь в оптимизации циклов.

Вот один из них для устранения общего суб-выражения (при условии, что foo является чистым):

a = foo (99) * x + y;
b = foo (99) * x + z;

Может стать:

_tmp = foo (99) * x;
a = _tmp + y;
b = _tmp + z;

Ответ 3

В дополнение к возможным преимуществам во время выполнения, чистая функция намного легче рассуждать при чтении кода. Кроме того, гораздо проще проверить чистую функцию, так как вы знаете, что возвращаемое значение зависит только от значений параметров.

Ответ 4

Нечистая функция

int foo(int x, int y) // possible side-effects

как расширение чистой функции

int bar(int x, int y) // guaranteed no side-effects

в котором вы, помимо явных аргументов функции x, y, остальная часть вселенной (или что-либо, с чем может взаимодействовать ваш компьютер) в качестве неявного потенциального ввода. Аналогично, помимо явного целочисленного возвращаемого значения, все, что может написать ваш компьютер, неявно является частью возвращаемого значения.

Должно быть понятно, почему гораздо проще рассуждать о чистой функции, чем не чистая.

Ответ 5

Как дополнение, я хотел бы упомянуть, что С++ 11 кодирует некоторые вещи с помощью ключевого слова constexpr. Пример:

#include <iostream>
#include <cstring>

constexpr unsigned static_strlen(const char * str, unsigned offset = 0) {
        return (*str == '\0') ? offset : static_strlen(str + 1, offset + 1);
}

constexpr const char * str = "asdfjkl;";

constexpr unsigned len = static_strlen(str); //MUST be evaluated at compile time
//so, for example, this: int arr[len]; is legal, as len is a constant.

int main() {
    std::cout << len << std::endl << std::strlen(str) << std::endl;
    return 0;
}

Ограничения на использование constexpr делают его таким, чтобы функция была явно чистой. Таким образом, компилятор может более агрессивно оптимизировать (просто убедитесь, что вы используете хвостовую рекурсию, пожалуйста!) И оцените функцию во время компиляции вместо времени выполнения.

Итак, чтобы ответить на ваш вопрос, это то, что если вы используете С++ (я знаю, что вы сказали C, но они связаны), запись чистой функции в правильном стиле позволяет компилятору делать всевозможные классные вещи с помощью функция: -)

Ответ 6

В целом, функции Pure имеют 3 преимущества над нечистыми функциями, которые может использовать компилятор:

Кэширование

Предположим, что у вас есть чистая функция f, которая называется 100000 раз, поскольку она детерминирована и зависит только от ее параметров, компилятор может вычислить ее значение один раз и использовать ее при необходимости

Parallelism

Чистые функции не читаются и не записываются в какую-либо общую память и поэтому могут запускаться в отдельных потоках без каких-либо неожиданных последствий.

Передача по ссылке

Функция f(struct t) получает свой аргумент t по значению, а с другой стороны, компилятор может передать t ссылкой на f, если он объявлен как чистый, гарантируя, что значение t не изменится и получит прирост производительности


В дополнение к соображениям времени компиляции чистые функции можно протестировать достаточно просто: просто позвоните им.

Не нужно создавать объекты или подключаться к DB/файловой системе.