Dzisiaj czytałem o czystej funkcji, pomyliłem się z jej użyciem:
O funkcji mówi się, że jest czysta, jeśli zwraca ten sam zestaw wartości dla tego samego zestawu danych wejściowych i nie ma żadnych obserwowalnych skutków ubocznych.
np. strlen()
jest czystą funkcją, podczas gdy rand()
jest nieczysta.
__attribute__ ((pure)) int fun(int i)
{
return i*i;
}
int main()
{
int i=10;
printf("%d",fun(i));//outputs 100
return 0;
}
Powyższy program zachowuje się analogicznie jak w przypadku braku pure
deklaracji.
Jakie są korzyści z zadeklarowania funkcji jako pure
[jeśli nie ma zmiany w wyniku]?
c
pure-virtual
Zielony Goblin
źródło
źródło
printf
na przykład kwalifikowałaby się (dwukrotne wywołanie jej z tymi samymi argumentami daje tę samą wartość zwracaną), ale nie jest czysta....and no side-effects...
części.Odpowiedzi:
pure
pozwala kompilatorowi wiedzieć, że może dokonać pewnych optymalizacji dotyczących funkcji: wyobraź sobie kawałek kodu, na przykładfor (int i = 0; i < 1000; i++) { printf("%d", fun(10)); }
Dzięki czystej funkcji kompilator może wiedzieć, że musi przeprowadzić ocenę
fun(10)
raz i tylko raz, a nie 1000 razy. W przypadku złożonej funkcji to duża wygrana.źródło
strlen
to. Potem znowu. To samo tak? Teraz zmodyfikuj drugi znak, który ma być\0
. Czystrlen
nadal zwraca teraz 1000? Adres początkowy jest taki sam (wejście == jest takie samo), ale funkcja zwraca teraz inną wartość.strlen
(w GCC / glibc) jest w rzeczywistości czyste. Ale spojrzenie na implementację glibc pokazało, że to błąd.Kiedy mówisz, że funkcja jest „czysta”, gwarantujesz, że nie ma ona żadnych zewnętrznych efektów ubocznych (a jak mówi komentarz, jeśli kłamiesz, mogą się zdarzyć złe rzeczy). Świadomość, że funkcja jest „czysta”, przynosi korzyści kompilatorowi, który może wykorzystać tę wiedzę do wykonania pewnych optymalizacji.
Oto, co dokumentacja GCC mówi o
pure
atrybucie:Odpowiedź Philipa już pokazuje, jak wiedza o funkcji jest „czysta” może pomóc w optymalizacji pętli.
Oto jedno z typowych eliminacji podwyrażeń (podane
foo
jest czyste):a = foo (99) * x + y; b = foo (99) * x + z;
Może zostać:
_tmp = foo (99) * x; a = _tmp + y; b = _tmp + z;
źródło
call
instrukcja jest wąskim gardłem dla superskalarnych procesorów, pomoc ze strony kompilatora może pomóc.foo
jest częścią innej jednostki kompilacji (inny plik C) lub w wstępnie skompilowanej bibliotece. W obu przypadkach kompilator nie będzie wiedział, cofoo
robi i nie może wstępnie obliczyć.Oprócz możliwych korzyści w czasie wykonywania, czysta funkcja jest znacznie łatwiejsza do rozważenia podczas czytania kodu. Ponadto o wiele łatwiej jest przetestować czystą funkcję, ponieważ wiesz, że wartość zwracana zależy tylko od wartości parametrów.
źródło
Nie czysta funkcja
int foo(int x, int y) // possible side-effects
jest jak rozszerzenie czystej funkcji
int bar(int x, int y) // guaranteed no side-effects
w którym oprócz jawnych argumentów funkcji x, y masz resztę wszechświata (lub cokolwiek, z czym twój komputer może się komunikować) jako niejawne potencjalne dane wejściowe. Podobnie, poza jawną wartością zwracaną w postaci liczby całkowitej, wszystko, do czego komputer może zapisać, jest niejawnie częścią wartości zwracanej.
Powinno być jasne, dlaczego wnioskowanie o czystej funkcji jest znacznie łatwiejsze niż o nieczystej.
źródło
Jako dodatek chciałbym wspomnieć, że C ++ 11 kodyfikuje pewne rzeczy za pomocą słowa kluczowego constexpr. Przykład:
#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; }
Ograniczenia dotyczące użycia constexpr sprawiają, że funkcja jest dająca się udowodnić czystość. W ten sposób kompilator może bardziej agresywnie optymalizować (po prostu upewnij się, że używasz rekurencji ogonowej, proszę!) I oceniać funkcję w czasie kompilacji, a nie w czasie wykonywania.
A więc, odpowiadając na twoje pytanie, jeśli używasz C ++ (wiem, że powiedziałeś C, ale są one powiązane), napisanie czystej funkcji w odpowiednim stylu pozwala kompilatorowi robić różne fajne rzeczy z funkcją: -)
źródło
Ogólnie rzecz biorąc, funkcje Pure mają 3 zalety w porównaniu z nieczystymi funkcjami, z których kompilator może skorzystać:
Buforowanie
Powiedzmy, że masz czystą funkcję,
f
która jest wywoływana 100000 razy, ponieważ jest deterministyczna i zależy tylko od jej parametrów, kompilator może obliczyć jej wartość raz i użyć jej w razie potrzebyRównoległość
Czyste funkcje nie odczytują ani nie zapisują w żadnej pamięci współdzielonej i dlatego mogą działać w oddzielnych wątkach bez żadnych nieoczekiwanych konsekwencji
Przekazywanie przez odniesienie
Funkcja
f(struct t)
pobiera argumentt
przez wartość, az drugiej strony kompilator może przekazaćt
przez odwołanie do,f
jeśli jest zadeklarowana jako czysta, gwarantując, że wartośćt
nie zmieni się i przyniesie wzrost wydajnościOprócz kwestii związanych z czasem kompilacji, czyste funkcje można testować dość łatwo: wystarczy je wywołać.
Nie ma potrzeby tworzenia obiektów ani pozorowania połączeń z bazami danych / systemem plików.
źródło