Rumah > Artikel > pembangunan bahagian belakang > Pencarian untuk Persembahan Bahagian III : C Force
Dalam dua ansuran sebelum siri ini, kami mempertimbangkan prestasi operasi terapung di Perl,
Python dan R dalam contoh mainan yang mengira fungsi cos(sin(sqrt(x))), dengan x ialah tatasusunan sangat besar bagi nombor terapung berketepatan ganda 50M.
Pelaksanaan hibrid yang mewakilkan bahagian intensif aritmetik kepada C adalah antara pelaksanaan yang paling berprestasi. Dalam ansuran ini, kami akan menyimpang sedikit dan melihat prestasi pelaksanaan kod C tulen bagi contoh mainan.
Kod C akan memberikan pandangan lanjut tentang kepentingan lokaliti memori untuk prestasi (secara lalai elemen dalam tatasusunan C disimpan dalam alamat berurutan dalam ingatan, dan API berangka seperti PDL atau antara muka numpy dengan bekas tersebut) berbanding bekas ,
cth. Tatasusunan Perl yang tidak menyimpan nilainya dalam alamat berjujukan dalam ingatan. Akhir sekali, tetapi yang paling penting, pelaksanaan kod C akan membolehkan kami menilai sama ada bendera yang berkaitan dengan operasi titik terapung untuk pengkompil tahap rendah (dalam kes ini gcc) boleh menjejaskan prestasi.
Perkara ini patut ditekankan: manusia biasa bergantung sepenuhnya pada pilihan bendera pengkompil apabila "memasang" "memasang" mereka atau membina fail Sebaris mereka. Jika seseorang tidak menyentuh bendera ini, maka dia akan berasa gembira tidak menyedari apa yang mereka mungkin hilang, atau perangkap yang mungkin mereka elakkan.
Makefile fail C yang sederhana membolehkan seseorang membuat penilaian prestasi sedemikian secara eksplisit.
Kod C untuk contoh mainan kami disenaraikan secara keseluruhannya di bawah. Kod ini agak jelas, jadi tidak akan menghabiskan masa untuk menerangkan selain menunjukkan bahawa ia mengandungi empat fungsi untuk
Dalam kes ini, seseorang mungkin berharap bahawa pengkompil cukup bijak untuk mengenali bahawa punca kuasa dua memetakan kepada operasi penunjuk terapung yang dibungkus (divektorkan) dalam pemasangan, supaya satu fungsi boleh divektorkan menggunakan arahan SIMD yang sesuai (nota yang kami lakukan tidak menggunakan program simd untuk kod OpenMP).
Mungkin kelajuan daripada pengvektoran mungkin mengimbangi kehilangan prestasi daripada berulang kali mengakses lokasi memori yang sama (atau tidak).
#include <stdlib.h> #include <string.h> #include <math.h> #include <stdio.h> #include <omp.h> // simulates a large array of random numbers double* simulate_array(int num_of_elements,int seed); // OMP environment functions void _set_openmp_schedule_from_env(); void _set_num_threads_from_env(); // functions to modify C arrays void map_c_array(double* array, int len); void map_c_array_sequential(double* array, int len); void map_C_array_using_OMP(double* array, int len); void map_C_array_sequential_using_OMP(double* array, int len); int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s <array_size>\n", argv[0]); return 1; } int array_size = atoi(argv[1]); // printf the array size printf("Array size: %d\n", array_size); double *array = simulate_array(array_size, 1234); // Set OMP environment _set_openmp_schedule_from_env(); _set_num_threads_from_env(); // Perform calculations and collect timing data double start_time, end_time, elapsed_time; // Non-Sequential calculation start_time = omp_get_wtime(); map_c_array(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Non-sequential calculation time: %f seconds\n", elapsed_time); free(array); // Sequential calculation array = simulate_array(array_size, 1234); start_time = omp_get_wtime(); map_c_array_sequential(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Sequential calculation time: %f seconds\n", elapsed_time); free(array); array = simulate_array(array_size, 1234); // Parallel calculation using OMP start_time = omp_get_wtime(); map_C_array_using_OMP(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Parallel calculation using OMP time: %f seconds\n", elapsed_time); free(array); // Sequential calculation using OMP array = simulate_array(array_size, 1234); start_time = omp_get_wtime(); map_C_array_sequential_using_OMP(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Sequential calculation using OMP time: %f seconds\n", elapsed_time); free(array); return 0; } /* ******************************************************************************* * OMP environment functions ******************************************************************************* */ void _set_openmp_schedule_from_env() { char *schedule_env = getenv("OMP_SCHEDULE"); printf("Schedule from env %s\n", getenv("OMP_SCHEDULE")); if (schedule_env != NULL) { char *kind_str = strtok(schedule_env, ","); char *chunk_size_str = strtok(NULL, ","); omp_sched_t kind; if (strcmp(kind_str, "static") == 0) { kind = omp_sched_static; } else if (strcmp(kind_str, "dynamic") == 0) { kind = omp_sched_dynamic; } else if (strcmp(kind_str, "guided") == 0) { kind = omp_sched_guided; } else { kind = omp_sched_auto; } int chunk_size = atoi(chunk_size_str); omp_set_schedule(kind, chunk_size); } } void _set_num_threads_from_env() { char *num = getenv("OMP_NUM_THREADS"); printf("Number of threads = %s from within C\n", num); omp_set_num_threads(atoi(num)); } /* ******************************************************************************* * Functions that modify C arrays whose address is passed from Perl in C ******************************************************************************* */ double* simulate_array(int num_of_elements, int seed) { srand(seed); // Seed the random number generator double *array = (double *)malloc(num_of_elements * sizeof(double)); for (int i = 0; i < num_of_elements; i++) { array[i] = (double)rand() / RAND_MAX; // Generate a random double between 0 and 1 } return array; } void map_c_array(double *array, int len) { for (int i = 0; i < len; i++) { array[i] = cos(sin(sqrt(array[i]))); } } void map_c_array_sequential(double* array, int len) { for (int i = 0; i < len; i++) { array[i] = sqrt(array[i]); } for (int i = 0; i < len; i++) { array[i] = sin(array[i]); } for (int i = 0; i < len; i++) { array[i] = cos(array[i]); } } void map_C_array_using_OMP(double* array, int len) { #pragma omp parallel { #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = cos(sin(sqrt(array[i]))); } } } void map_C_array_sequential_using_OMP(double* array, int len) { #pragma omp parallel { #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = sqrt(array[i]); } #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = sin(array[i]); } #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = cos(array[i]); } } }
Persoalan kritikal ialah sama ada penggunaan bendera pengkompil terapung cepat, helah yang menukar kelajuan untuk ketepatan kod, boleh menjejaskan prestasi.
Berikut ialah fail make tanpa bendera pengkompil ini
CC = gcc CFLAGS = -O3 -ftree-vectorize -march=native -Wall -std=gnu11 -fopenmp -fstrict-aliasing LDFLAGS = -fPIE -fopenmp LIBS = -lm SOURCES = inplace_array_mod_with_OpenMP.c OBJECTS = $(SOURCES:.c=_noffmath_gcc.o) EXECUTABLE = inplace_array_mod_with_OpenMP_noffmath_gcc all: $(SOURCES) $(EXECUTABLE) clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@ %_noffmath_gcc.o : %.c $(CC) $(CFLAGS) -c $< -o $@
dan inilah yang mempunyai bendera ini:
CC = gcc CFLAGS = -O3 -ftree-vectorize -march=native -Wall -std=gnu11 -fopenmp -fstrict-aliasing -ffast-math LDFLAGS = -fPIE -fopenmp LIBS = -lm SOURCES = inplace_array_mod_with_OpenMP.c OBJECTS = $(SOURCES:.c=_gcc.o) EXECUTABLE = inplace_array_mod_with_OpenMP_gcc all: $(SOURCES) $(EXECUTABLE) clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@ %_gcc.o : %.c $(CC) $(CFLAGS) -c $< -o $@
Dan berikut adalah keputusan menjalankan kedua-dua program ini
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_noffmath_gcc 50000000 Array size: 50000000 Schedule from env guided,1 Number of threads = 8 from within C Non-sequential calculation time: 1.12 seconds Sequential calculation time: 0.95 seconds Parallel calculation using OMP time: 0.17 seconds Sequential calculation using OMP time: 0.15 seconds
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_gcc 50000000 Array size: 50000000 Schedule from env guided,1 Number of threads = 8 from within C Non-sequential calculation time: 0.27 seconds Sequential calculation time: 0.28 seconds Parallel calculation using OMP time: 0.05 seconds Sequential calculation using OMP time: 0.06 seconds
Perhatikan bahawa seseorang boleh menggunakan fastmath dalam kod Numba seperti berikut (lalai ialah fastmath=False):
@njit(nogil=True,fastmath=True) def compute_inplace_with_numba(array): np.sqrt(array,array) np.sin(array,array) np.cos(array,array)
Beberapa perkara yang patut diberi perhatian:
tajuk: " Pencarian untuk Persembahan Bahagian III : C Force "
Dans les deux volets précédents de cette série, nous avons examiné les performances des opérations flottantes en Perl,
Python et R dans un exemple de jouet qui a calculé la fonction cos(sin(sqrt(x))), où x était un très grand tableau de 50 millions de nombres flottants double précision.
Les implémentations hybrides qui déléguaient la partie arithmétique intensive à C étaient parmi les implémentations les plus performantes. Dans cet article, nous allons nous éloigner légèrement et examiner les performances d'une implémentation en code C pur de l'exemple de jouet.
Le code C fournira des informations supplémentaires sur l'importance de la localité mémoire pour les performances (par défaut, les éléments d'un tableau C sont stockés dans des adresses séquentielles en mémoire, et les API numériques telles que PDL ou l'interface numpy avec de tels conteneurs) par rapport aux conteneurs. ,
par ex. Tableaux Perl qui ne stockent pas leurs valeurs dans des adresses séquentielles en mémoire. Dernier point, mais non le moindre, les implémentations du code C nous permettront d'évaluer si les indicateurs liés aux opérations en virgule flottante pour le compilateur de bas niveau (dans ce cas gcc) peuvent affecter les performances.
Ce point mérite d'être souligné : le commun des mortels dépend entièrement du choix des indicateurs du compilateur lors du "piping" de son "installation" ou de la construction de son fichier Inline. Si l’on ne touche pas ces drapeaux, on ignorera parfaitement ce qu’ils pourraient manquer ou les pièges qu’ils pourraient éviter.
Le modeste makefile du fichier C permet de faire explicitement de telles évaluations de performances.
Le code C de notre exemple de jouet est répertorié dans son intégralité ci-dessous. Le code est plutôt explicite, je ne passerai donc pas de temps à expliquer autre que souligner qu'il contient quatre fonctions pour
Dans ce cas, on peut espérer que le compilateur soit suffisamment intelligent pour reconnaître que la racine carrée correspond à des opérations de virgule flottante compressées (vectorisées) dans l'assembly, de sorte qu'une fonction puisse être vectorisée à l'aide des instructions SIMD appropriées (notez que nous l'avons fait n'utilisez pas le programme simd pour les codes OpenMP).
Peut-être que l'accélération de la vectorisation pourrait compenser la perte de performances liée à l'accès répété aux mêmes emplacements mémoire (ou non).
#include <stdlib.h> #include <string.h> #include <math.h> #include <stdio.h> #include <omp.h> // simulates a large array of random numbers double* simulate_array(int num_of_elements,int seed); // OMP environment functions void _set_openmp_schedule_from_env(); void _set_num_threads_from_env(); // functions to modify C arrays void map_c_array(double* array, int len); void map_c_array_sequential(double* array, int len); void map_C_array_using_OMP(double* array, int len); void map_C_array_sequential_using_OMP(double* array, int len); int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s <array_size>\n", argv[0]); return 1; } int array_size = atoi(argv[1]); // printf the array size printf("Array size: %d\n", array_size); double *array = simulate_array(array_size, 1234); // Set OMP environment _set_openmp_schedule_from_env(); _set_num_threads_from_env(); // Perform calculations and collect timing data double start_time, end_time, elapsed_time; // Non-Sequential calculation start_time = omp_get_wtime(); map_c_array(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Non-sequential calculation time: %f seconds\n", elapsed_time); free(array); // Sequential calculation array = simulate_array(array_size, 1234); start_time = omp_get_wtime(); map_c_array_sequential(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Sequential calculation time: %f seconds\n", elapsed_time); free(array); array = simulate_array(array_size, 1234); // Parallel calculation using OMP start_time = omp_get_wtime(); map_C_array_using_OMP(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Parallel calculation using OMP time: %f seconds\n", elapsed_time); free(array); // Sequential calculation using OMP array = simulate_array(array_size, 1234); start_time = omp_get_wtime(); map_C_array_sequential_using_OMP(array, array_size); end_time = omp_get_wtime(); elapsed_time = end_time - start_time; printf("Sequential calculation using OMP time: %f seconds\n", elapsed_time); free(array); return 0; } /* ******************************************************************************* * OMP environment functions ******************************************************************************* */ void _set_openmp_schedule_from_env() { char *schedule_env = getenv("OMP_SCHEDULE"); printf("Schedule from env %s\n", getenv("OMP_SCHEDULE")); if (schedule_env != NULL) { char *kind_str = strtok(schedule_env, ","); char *chunk_size_str = strtok(NULL, ","); omp_sched_t kind; if (strcmp(kind_str, "static") == 0) { kind = omp_sched_static; } else if (strcmp(kind_str, "dynamic") == 0) { kind = omp_sched_dynamic; } else if (strcmp(kind_str, "guided") == 0) { kind = omp_sched_guided; } else { kind = omp_sched_auto; } int chunk_size = atoi(chunk_size_str); omp_set_schedule(kind, chunk_size); } } void _set_num_threads_from_env() { char *num = getenv("OMP_NUM_THREADS"); printf("Number of threads = %s from within C\n", num); omp_set_num_threads(atoi(num)); } /* ******************************************************************************* * Functions that modify C arrays whose address is passed from Perl in C ******************************************************************************* */ double* simulate_array(int num_of_elements, int seed) { srand(seed); // Seed the random number generator double *array = (double *)malloc(num_of_elements * sizeof(double)); for (int i = 0; i < num_of_elements; i++) { array[i] = (double)rand() / RAND_MAX; // Generate a random double between 0 and 1 } return array; } void map_c_array(double *array, int len) { for (int i = 0; i < len; i++) { array[i] = cos(sin(sqrt(array[i]))); } } void map_c_array_sequential(double* array, int len) { for (int i = 0; i < len; i++) { array[i] = sqrt(array[i]); } for (int i = 0; i < len; i++) { array[i] = sin(array[i]); } for (int i = 0; i < len; i++) { array[i] = cos(array[i]); } } void map_C_array_using_OMP(double* array, int len) { #pragma omp parallel { #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = cos(sin(sqrt(array[i]))); } } } void map_C_array_sequential_using_OMP(double* array, int len) { #pragma omp parallel { #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = sqrt(array[i]); } #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = sin(array[i]); } #pragma omp for schedule(runtime) nowait for (int i = 0; i < len; i++) { array[i] = cos(array[i]); } } }
Une question cruciale est de savoir si l'utilisation d'indicateurs de compilateur flottants rapides, une astuce qui échange la vitesse contre la précision du code, peut affecter les performances.
Voici le makefile sans ce drapeau du compilateur
CC = gcc CFLAGS = -O3 -ftree-vectorize -march=native -Wall -std=gnu11 -fopenmp -fstrict-aliasing LDFLAGS = -fPIE -fopenmp LIBS = -lm SOURCES = inplace_array_mod_with_OpenMP.c OBJECTS = $(SOURCES:.c=_noffmath_gcc.o) EXECUTABLE = inplace_array_mod_with_OpenMP_noffmath_gcc all: $(SOURCES) $(EXECUTABLE) clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@ %_noffmath_gcc.o : %.c $(CC) $(CFLAGS) -c $< -o $@
et voici celui avec ce drapeau :
CC = gcc CFLAGS = -O3 -ftree-vectorize -march=native -Wall -std=gnu11 -fopenmp -fstrict-aliasing -ffast-math LDFLAGS = -fPIE -fopenmp LIBS = -lm SOURCES = inplace_array_mod_with_OpenMP.c OBJECTS = $(SOURCES:.c=_gcc.o) EXECUTABLE = inplace_array_mod_with_OpenMP_gcc all: $(SOURCES) $(EXECUTABLE) clean: rm -f $(OBJECTS) $(EXECUTABLE) $(EXECUTABLE): $(OBJECTS) $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@ %_gcc.o : %.c $(CC) $(CFLAGS) -c $< -o $@
Et voici les résultats de l'exécution de ces deux programmes
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_noffmath_gcc 50000000 Array size: 50000000 Schedule from env guided,1 Number of threads = 8 from within C Non-sequential calculation time: 1.12 seconds Sequential calculation time: 0.95 seconds Parallel calculation using OMP time: 0.17 seconds Sequential calculation using OMP time: 0.15 seconds
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_gcc 50000000 Array size: 50000000 Schedule from env guided,1 Number of threads = 8 from within C Non-sequential calculation time: 0.27 seconds Sequential calculation time: 0.28 seconds Parallel calculation using OMP time: 0.05 seconds Sequential calculation using OMP time: 0.06 seconds
Notez que l'on peut utiliser le fastmath dans le code Numba comme suit (la valeur par défaut est fastmath=False) :
@njit(nogil=True,fastmath=True) def compute_inplace_with_numba(array): np.sqrt(array,array) np.sin(array,array) np.cos(array,array)
Quelques points à noter :
Atas ialah kandungan terperinci Pencarian untuk Persembahan Bahagian III : C Force. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!