2013-04-08 11 views
6

Napisałem cztery różne programy, aby policzyć wszystkie słowa w dwóch plikach. Te cztery wersje wyglądają w większości tak samo. Pierwsze trzy wersje wykorzystują dwa wątki do zliczania, a same zamówienia trzech zdań są różne. Ostatnia wersja używa jednego wątku do zliczenia. Wymienię najpierw różne części każdej wersji i część wspólną, a następnie wynik każdej wersji i moje pytanie.Co kosztuje dodatkowy czas wykonania procedury w programie pthread?

inna część:

// version 1 
count_words(&file1); 
pthread_create(&new_thread, NULL, count_words, &file2); 
pthread_join(new_thread, NULL); 

// version 2 
pthread_create(&new_thread, NULL, count_words, &file2); 
count_words(&file1); 
pthread_join(new_thread, NULL); 

// version 3 
pthread_create(&new_thread, NULL, count_words, &file2); 
pthread_join(new_thread, NULL); 
count_words(&file1); 

// version 4 
count_words(&file1); 
count_words(&file2); 

część wspólna (Włóż inną część do tej wspólnej części w celu stworzenia pełnej wersji)

#include <stdio.h> 
#include <pthread.h> 
#include <ctype.h> 
#include <stdlib.h> 
#include <time.h> 

#define N 2000 

typedef struct file_t { 
    char *name; 
    int words; 
} file_t; 

double time_diff(struct timespec *, struct timespec *); 
void *count_words(void *); 

// Usage: progname file1 file2 
int main(int argc, char *argv[]) { 
    pthread_t new_thread; 
    file_t file1, file2; 

    file1.name = argv[1]; 
    file1.words = 0; 
    file2.name= argv[2]; 
    file2.words = 0; 

    // Insert different part here 

    printf("Total words: %d\n", file1.words+file2.words); 
    return 0; 
} 

void *count_words(void *arg) { 
    FILE *fp; 
    file_t *file = (file_t *)arg; 
    int i, c, prevc = '\0'; 
    struct timespec process_beg, process_end; 
    struct timespec thread_beg, thread_end; 
    double process_diff, thread_diff; 

    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &process_beg); 
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &thread_beg); 

    fp = fopen(file->name, "r"); 
    for (i = 0; i < N; i++) { 
     while ((c = getc(fp)) != EOF) { 
      if (!isalnum(c) && isalnum(prevc)) 
       file->words++; 
      prevc = c; 
     } 
     fseek(fp, 0, SEEK_SET); 
    } 
    fclose(fp); 

    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &process_end); 
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &thread_end); 

    process_diff = time_diff(&process_beg, &process_end); 
    thread_diff = time_diff(&thread_beg, &thread_end); 
    printf("count_words() in %s takes %.3fs process time and" 
      "%.3fs thread time\n", file->name, process_diff, thread_diff); 
    return NULL; 
} 

double time_diff(struct timespec *beg, struct timespec *end) { 
    return ((double)end->tv_sec + (double)end->tv_nsec*1.0e-9) 
     - ((double)beg->tv_sec + (double)beg->tv_nsec*1.0e-9); 
} 

UWAGA

  1. plik1 jest plik zawierający 10000 słów "słowa". plik2 jest kopią pliku1, utworzoną przez polecenie cp.
  2. Aby czas wykonania był wystarczająco długi, program wielokrotnie zlicza wyrazy. N to liczba pętli. Tak więc wynik nie jest dokładną liczbą całkowitych słów, ale mnoży się przez N.
  3. Nie kładź zbyt dużego nacisku na algorytm liczenia. Jestem tylko zaniepokojony czasem wykonania w tym przykładzie.
  4. Ważna informacja: Maszyna to procesor Intel® Celeron (R) 420 przy 1,60 GHz. jeden rdzeń. System operacyjny to Linux 3.2.0. Być może jeden rdzeń jest przyczyną tego dziwnego zjawiska, jak powiedzieli inni. Ale nadal chcę to rozgryźć.

Program liczy słowa i używa funkcji clock_gettime() do obliczenia czasu procesora procesora i czasu procesora rutyny count_words(), a następnie wyprowadzenia czasów i numeru słowa. Poniżej znajduje się wynik i mój komentarz z pytaniami. Będę bardzo wdzięczny, jeśli ktoś będzie mógł wyjaśnić powód, dla którego poświęca się dodatkowy czas.

// version 1 
count_words() in file1 takes 2.563s process time and 2.563s thread time 
count_words() in file2 takes 8.374s process time and 8.374s thread time 
Total words: 40000000 

Komentarz: Oryginalny wątek kończy count_words() i czeka na zgon nowego wątku. Kiedy count_words() działa w nowym wątku, nie następuje przełączanie kontekstu (ponieważ czas procesu == czas wątku). Dlaczego zajmuje to tyle czasu? Co dzieje się w count_words() w nowym wątku?

// version 2 
count_words() in file1 takes 16.755s process time and 8.377s thread time 
count_words() in file2 takes 16.753s process time and 8.380s thread time 
Total words: 40000000 

Komentarz: tutaj biegają równoległe dwie nitki. Przełącza kontekst, więc czas procesu> czas wątku.

// version 3 
count_words() in file2 takes 8.374s process time and 8.374s thread time 
count_words() in file1 takes 8.365s process time and 8.365s thread time 
Total words: 40000000 

Komentarz: Nowy wątek liczy się jako pierwszy i czeka na niego oryginalny wątek. Po połączeniu nowego wątku zaczyna się liczyć oryginalny wątek. Żadna z nich nie ma przełączania kontekstu, dlaczego tak dużo czasu zajęło, szczególnie liczenie po przyłączeniu nowego wątku?

// version 4 
count_words() in file1 takes 2.555s process time and 2.555s thread time 
count_words() in file2 takes 2.556s process time and 2.556s thread time 
Total words: 40000000 

Komentarz: Najszybsza wersja. Nie utworzono nowego wątku. Obie count_words() działa w jednym wątku.

+0

Wersja porównawcza 1 + 2: Udostępnianie fałszywe? Być może to pomaga: http://stackoverflow.com/q/8331255/694576 – alk

+0

@alk: Moja maszyna ma jeden rdzeń. Zgodnie z przyjętą odpowiedzią w cytowanym linku fałszywe udostępnianie jest wynikiem wielu rdzeni. Czy można to zrobić na pojedynczym rdzeniu? –

Odpowiedz

7

Jest tak prawdopodobnie dlatego, że tworzenie dowolnego wątku wymusza na libc używanie synchronizacji w getc. To sprawia, że ​​funkcja ta jest znacznie wolniejsza.Poniższy przykład jest dla mnie tak wolno, jak w wersji 3:

void *skip(void *p){ return NULL; }; 
pthread_create(&new_thread, NULL, skip, NULL); 
count_words(&file1); 
count_words(&file2); 

Aby rozwiązać ten problem, można użyć bufor:

for (i = 0; i < N; i++) { 
    char buffer[BUFSIZ]; 
    int read; 
    do { 
     read = fread(buffer, 1, BUFSIZ, fp); 
     int j; 
     for(j = 0; j < read; j++) { 
      if (!isalnum(buffer[j]) && isalnum(prevc)) 
       file->words++; 
      prevc = buffer[j]; 
     } 
    } while(read == BUFSIZ); 
    fseek(fp, 0, SEEK_SET); 
} 

W tym rozwiązaniu, funkcje IO nazywane są dość rzadko, aby synchronizacja napowietrznych nieistotne . To nie tylko rozwiązuje problem dziwnych czasów, ale także sprawia, że ​​jest kilka razy szybszy. Dla mnie jest to redukcja z 0.54s (bez wątków) lub 0.85s (z wątkami) na 0.15s (w obu przypadkach).

+0

Zastosowałem twoją metodę do każdej wersji, a wszystkie wątki cpu procesora i procesora procesora są skrócone. Teraz count_words() ma czas procesora wątku między 1,20 s a 1,30 s przy każdym stanie. Czy używanie synchronizacji używanej w getc() oznacza używanie blokowania w każdej funkcji biblioteki we/wy, aby zapewnić bezpieczeństwo wątku? Próbowałem zastąpić getc() funkcją getc_unlocked(), co znacznie skróciło czas do około 1,50. Ale wciąż trochę wolniej niż twoja metoda i wątek jest niebezpieczny. Odkryłem również, że w moim przykładzie przełączanie kontekstu nie jest czasochłonną pracą. Dzięki Ci. –

+0

Nadal chcę iść głębiej. Blokowanie pliku odbywa się za każdym razem, gdy działa getc(). Ale dlaczego zajmuje dużo więcej czasu w kontekście dwóch wątków niż w kontekście jednego wątku (8.xxx vs 2.xxx)? Należy również zauważyć, że łatwo jest wymagać blokady w wersji 1, 2, 3. W wersji 3 count_words() uruchamia się po połączeniu nowego wątku, gdy pozostanie tylko jeden wątek. Nie można uzyskać getc() znaleźć i czy szybkie blokowanie (lub coś innego), jak to robi w wersji 4? –

+0

@WenhaoYang, tak, w tym przypadku synchronizacja jest blokowana, jest to krótko opisane w 'man flockfile'. Funkcje "_unlocked" powinny być tu bezpieczne, ponieważ każda struktura 'FILE' jest używana przez jeden wątek. Co dokładnie powoduje, że działa szybciej, zanim utworzę wątki, nie wiem. Może 'if (o nazwie pthread_create) really_lock()' na pewnym poziomie? – zch