Przez jakiś czas pracowałem nad projektem hobbystycznym (napisanym w języku C), a to jeszcze nie jest kompletne. Bardzo ważne jest, aby był szybki, więc ostatnio zdecydowałem się na benchmarking, aby sprawdzić, czy mój sposób rozwiązania problemu nie będzie nieskuteczny.Jak zwiększyć częstotliwość procesora nowo odrodzonego procesu
$ time ./old
real 1m55.92
user 0m54.29
sys 0m33.24
I przeprojektowane części programu znacząco usunąć niepotrzebne operacje, zmniejszenie pamięci cache tęskni i mispredictions oddziałów. Wspaniałe narzędzie Callgrind pokazywało mi coraz więcej imponujących liczb. Większość testów porównawczych przeprowadzono bez rozwidlania procesów zewnętrznych.
$ time ./old --dry-run
real 0m00.75
user 0m00.28
sys 0m00.24
$ time ./new --dry-run
real 0m00.15
user 0m00.12
sys 0m00.02
Najwyraźniej przynajmniej robiłem coś dobrze. Jednak uruchomienie programu na prawdziwe opowiadało inną historię.
$ time ./new
real 2m00.29
user 0m53.74
sys 0m36.22
Jak można zauważyć, czas zależy głównie od procesów zewnętrznych. Nie wiem, co spowodowało regresję. Nie ma w tym nic dziwnego; tylko tradycyjne vfork/execve/waitpid wykonane przez pojedynczy wątek, uruchamiając te same programy w tej samej kolejności.
Coś musiało powodować spowolnienie rozwidlenia, dlatego zrobiłem mały test (podobny do tego poniżej), który tylko odradzałby nowe procesy i nie powodował żadnego obciążenia związanego z moim programem. Oczywiście musiało to być najszybsze.
#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, const char **argv)
{
static const char *const _argv[] = {"/usr/bin/md5sum", "test.c", 0};
int fd = open("/dev/null", O_WRONLY);
dup2(fd, STDOUT_FILENO);
close(fd);
for (int i = 0; i < 100000; i++)
{
int pid = vfork();
int status;
if (!pid)
{
execve("/usr/bin/md5sum", (char*const*)_argv, environ);
_exit(1);
}
waitpid(pid, &status, 0);
}
return 0;
}
$ time ./test
real 1m58.63
user 0m68.05
sys 0m30.96
Chyba nie.
W tym momencie postanowiłem głosować wydajność na gubernatora, a czasy jeszcze lepsze:
$ for i in 0 1 2 3 4 5 6 7; do sudo sh -c "echo performance > /sys/devices/system/cpu/cpu$i/cpufreq/scaling_governor";done
$ time ./test
real 1m03.44
user 0m29.30
sys 0m10.66
Wydaje się, że każdy nowy proces zostanie zaplanowane na oddzielnym rdzeniu, a to zajmuje trochę czasu na to, aby przełączyć się na wyższa częstotliwość. Nie mogę powiedzieć, dlaczego stara wersja działała szybciej. Może to było szczęście. Może to (ze względu na jego nieefektywność) spowodowało, że procesor wybrał wcześniej wyższą częstotliwość.
Dobrym efektem ubocznym zmiany gubernatora było poprawienie czasów kompilacji. Najwyraźniej kompilacja wymaga rozwidlenia wielu nowych procesów. To nie jest praktyczne rozwiązanie, ponieważ ten program będzie musiał działać na komputerach innych ludzi (i laptopach).
Jedynym sposobem znalazłem poprawić oryginalne razy było ograniczenie programu (i procesy potomne) do pojedynczego procesora dodając ten kod na początku:
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(0, &mask);
sched_setaffinity(0, sizeof(mask), &mask);
Które rzeczywiście był najszybszy mimo korzystania domyślny „OnDemand” namiestnik:
$ time ./test
real 0m59.74
user 0m29.02
sys 0m10.67
nie tylko jest to hackish rozwiązanie, ale to nie działa dobrze w przypadku, gdy uruchomiony program korzysta z wielu wątków. Program nie może tego wiedzieć.
Czy ktoś ma jakiś pomysł na to, jak uruchomić procesy odradzania przy wysokiej częstotliwości taktowania procesora? Musi być zautomatyzowany i nie wymagać su priviliges. Chociaż do tej pory testowałem to tylko na Linuksie, zamierzam przenieść to na mniej lub bardziej popularne i niepopularne systemy operacyjne (a także działać na serwerach). Każdy pomysł na dowolnej platformie jest mile widziany.
Prawie na pewno dzieje się coś innego, czego nie możesz wziąć pod uwagę. Aktywni gubernatorzy CPU nie osiągnęliby takiej różnicy w wydajności na żadnym prawdziwym komputerze. Jaki jest twój program? Co on robi, jak to robi i jak go testujesz? – duskwuff
Proszę wypróbować kod testowy. Dla mnie działa w 1m14s z gubernatorem ondemand i 32s z wydajnością (na Core i7-2600). Jestem zaskoczony jak ty. – torso
Miałem na myśli różnicę wydajności wyświetlaną w './New'. – duskwuff