Сегодня читал про чистую функцию, запутался в ее использовании:
Функция называется чистой, если она возвращает тот же набор значений для одного и того же набора входных данных и не имеет никаких наблюдаемых побочных эффектов.
например strlen()
, это чистая функция, а rand()
нечистая.
__attribute__ ((pure)) int fun(int i)
{
return i*i;
}
int main()
{
int i=10;
printf("%d",fun(i));//outputs 100
return 0;
}
Вышеупомянутая программа ведет себя так же, как и при отсутствии pure
объявления.
Каковы преимущества объявления функции как pure
[если нет изменений в выходе]?
c
pure-virtual
Зеленый Гоблин
источник
источник
printf
например, оно подходит (вызов его дважды с одними и теми же аргументами дает одно и то же возвращаемое значение), но оно не является чистым....and no side-effects...
.Ответы:
pure
позволяет компилятору знать, что он может сделать определенную оптимизацию функции: представьте себе небольшой код вродеfor (int i = 0; i < 1000; i++) { printf("%d", fun(10)); }
С чистой функцией компилятор может знать, что ему нужно выполнить оценку
fun(10)
только один раз, а не 1000 раз. Для сложной функции это большой выигрыш.источник
strlen
ему. Тогда снова. То же самое, да? Теперь измените второй символ как\0
. Всеstrlen
еще возвращает 1000 сейчас? Начальный адрес такой же (== ввод такой же), но функция теперь возвращает другое значение.strlen
(в GCC / glibc) на самом деле чистый. Но взгляд на реализацию glibc показал, что это неверно.Когда вы говорите, что функция «чистая», вы гарантируете, что она не имеет видимых извне побочных эффектов (и, как говорится в комментарии, если вы солгаете, могут произойти неприятности). Знание того, что функция «чистая» имеет преимущества для компилятора, который может использовать это знание для выполнения определенных оптимизаций.
Вот что говорится об атрибуте в документации GCC
pure
:Ответ Филиппа уже показывает, как знание того, что функция «чиста», может помочь в оптимизации цикла.
Вот одно для общего исключения подвыражения (дано
foo
чисто):a = foo (99) * x + y; b = foo (99) * x + z;
Может стать:
_tmp = foo (99) * x; a = _tmp + y; b = _tmp + z;
источник
call
инструкция является узким местом для суперскалярных процессоров, некоторая помощь компилятора может помочь.foo
является частью другого модуля компиляции (другого файла C) или находится в предварительно скомпилированной библиотеке. В обоих случаях компилятор не знает, чтоfoo
делает, и не может предварительно вычислить.Помимо возможных преимуществ во время выполнения, при чтении кода гораздо проще рассуждать о чистой функции. Кроме того, гораздо проще протестировать чистую функцию, поскольку вы знаете, что возвращаемое значение зависит только от значений параметров.
источник
Нечистая функция
int foo(int x, int y) // possible side-effects
похожа на продолжение чистой функции
int bar(int x, int y) // guaranteed no side-effects
в котором у вас есть, помимо явных аргументов функции x, y, остальная часть вселенной (или все, с чем ваш компьютер может взаимодействовать) в качестве неявного потенциального ввода. Точно так же, помимо явного целочисленного возвращаемого значения, все, что ваш компьютер может писать, неявно является частью возвращаемого значения.
Должно быть ясно, почему проще рассуждать о чистой функции, чем о нечистой.
источник
В качестве дополнения я хотел бы упомянуть, что C ++ 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 ++ (я знаю, что вы сказали C, но они связаны), написание чистой функции в правильном стиле позволяет компилятору делать с функцией всевозможные классные вещи: -)
источник
В общем, чистые функции имеют 3 преимущества перед нечистыми функциями, которыми может воспользоваться компилятор:
Кеширование
Допустим, у вас есть чистая функция
f
которая вызывается 100000 раз, поскольку она детерминирована и зависит только от ее параметров, компилятор может вычислить ее значение один раз и использовать при необходимости.Параллелизм
Чистые функции не читают и не записывают в какую-либо разделяемую память и, следовательно, могут выполняться в отдельных потоках без каких-либо неожиданных последствий.
Передача по ссылке
Функция
f(struct t)
получает свой аргументt
по значению, и, с другой стороны, компилятор может передатьt
по ссылке,f
если она объявлена как чистая, при этом гарантируя, что значениеt
не изменится и даст прирост производительностиВ дополнение к соображениям времени компиляции, чистые функции можно довольно легко протестировать: просто вызовите их.
Не нужно создавать объекты или имитировать подключения к БД / файловой системе.
источник