성과를 위한 탐구 3부 : C Force

王林
풀어 주다: 2024-08-06 01:10:02
원래의
919명이 탐색했습니다.

The Quest for Performance Part III : C Force

이 시리즈의 이전 두 기사에서는 Perl의 부동 연산 성능을 고려했습니다.
cos(sin(sqrt(x)))함수를 계산하는 장난감 예제의 Python 및 R. 여기서 x는 50M 배정밀도 부동 숫자로 구성된매우 큰배열입니다.
산술 집약적인 부분을 C에 위임한 하이브리드 구현은 가장 성능이 뛰어난 구현 중 하나였습니다. 이번 회에서는 약간 다른 내용으로 장난감 예제의 순수 C 코드 구현 성능을 살펴보겠습니다.
C 코드는 성능을 위한 메모리 지역성의 중요성에 대한 추가 통찰력을 제공합니다(기본적으로 C 배열의 요소는 메모리의 순차 주소와 PDL 또는 해당 컨테이너와의 numpy 인터페이스와 같은 수치 API에 저장됨). ,
예를 들어 메모리의 순차 주소에 값을 저장하지 않는 Perl 배열입니다. 마지막으로 C 코드 구현을 통해 저수준 컴파일러(이 경우 gcc)의 부동 소수점 연산과 관련된 플래그가 성능에 영향을 미칠 수 있는지 여부를 평가할 수 있습니다.
이 점은 강조할 가치가 있습니다. 일반 사용자는 "설치"를 "파이핑"하거나 인라인 파일을 빌드할 때 컴파일러 플래그 선택에 전적으로 의존합니다. 이 플래그를 건드리지 않으면 플래그가 무엇을 놓칠 수 있는지, 피하고 있는 함정이 무엇인지 전혀 알지 못할 것입니다.
간단한 C 파일 makefile을 사용하면 이러한 성능 평가를 명시적으로 수행할 수 있습니다.

장난감 예제의 C 코드 전체가 아래에 나열되어 있습니다. 코드는 설명이 필요 없기 때문에

에 대한 네 가지 기능이 포함되어 있다는 점을 지적하는 것 외에는 설명하는 데 시간을 할애하지 않을 것입니다.
  • 비용이 많이 드는 함수의 비순차적 계산: 세 가지 부동 포인팅 작업 모두 하나의 스레드를 사용하여 단일 루프 내에서 발생합니다
  • 비용이 많이 드는 함수의 순차적 계산: 3개의 부동 소수점 함수 평가 각각은 하나의 스레드를 사용하여 별도의 루프 내부에서 수행됩니다
  • 비순차 OpenMP 코드: 비순차 코드의 스레드 버전
  • 순차 OpenMP 코드: 순차 코드의 스레드

이 경우 컴파일러가 제곱근이 어셈블리의 압축된(벡터화된) 부동 포인팅 연산에 매핑된다는 것을 인식할 만큼 똑똑하여 하나의 함수가 적절한 SIMD 명령어를 사용하여 벡터화될 수 있기를 바랄 수 있습니다(우리는 사용하지 않았음을 참고하세요). OpenMP 코드용 simd 프로그램).
아마도 벡터화로 인한 속도 향상은 동일한 메모리 위치에 반복적으로 액세스하는 것(또는 그렇지 않은 것)으로 인한 성능 손실을 상쇄할 수 있습니다.

으아악

중요한 질문은 코드의 정확성을 위해 속도를 교환하는 트릭인 빠른 부동 컴파일러 플래그의 사용이 성능에 영향을 미칠 수 있는지 여부입니다.
다음은 이 컴파일러 플래그가 없는 makefile입니다

으아악

이 플래그가 있는 항목은 다음과 같습니다.

으아악

그리고 이 두 프로그램을 실행한 결과는 다음과 같습니다

  • -ffast-math 없이
으아악
  • -ffast-math 사용
으아악

다음과 같이 Numba 코드에서 fastmath를 사용할 수 있습니다(기본값은 fastmath=False).

으아악

주의할 만한 몇 가지 사항:

  • -ffast-math는 성능을 크게 향상시키지만(단일 스레드 및 다중 스레드 코드 모두에서 약 300%) 잘못된 결과를 생성할 수 있습니다
  • Fastmath는 Numba에서도 작동하지만 같은 이유로 피해야 하며 정확성을 추구하는 모든 응용 프로그램에서는 피해야 합니다
  • 순차 C 단일 스레드 코드는 단일 스레드 PDL 및 Numpy와 유사한 성능을 제공합니다
  • 다소 놀랍게도 순차 코드는 올바른(빠르지 않은) 수학을 사용할 때 비순차 코드보다 약 20% 더 빠릅니다.
  • 놀랍지도 않게 멀티 스레드 코드는 단일 스레드 코드보다 빠릅니다. :)
  • 이 간단한 함수의 C 코드에 비해 numbas가 어떻게 50%의 성능 프리미엄을 제공하는지 아직도 설명할 수 없습니다.

제목: " 퍼포먼스 파트 III : C Force "

tarikh: 2024-07-07

Dalam dua ansuran sebelum siri ini, kami mempertimbangkan prestasi operasi terapung di Perl,
Python dan R dalam contoh mainan yang mengira fungsicos(sin(sqrt(x))), dengan x ialah tatasusunansangat besarbagi 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

  • Pengiraan bukan urutan bagi fungsi mahal : ketiga-tiga operasi penunjuk terapung berlaku di dalam satu gelung menggunakan satu benang
  • Pengiraan berurutan bagi fungsi mahal : setiap satu daripada 3 penilaian fungsi titik terapung mengambil dalam gelung berasingan menggunakan satu benang
  • Kod OpenMP bukan urutan : versi berulir kod bukan urutan
  • Kod OpenMP berurutan: berulir kod berjujukan

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 (perhatikan kami tidak menggunakan program simd untuk kod OpenMP).
Mungkin percepatan daripada vektorisasi boleh mengimbangi kehilangan prestasi daripada berulang kali mengakses lokasi memori yang sama (atau tidak).

#include  #include  #include  #include  #include  // 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 \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 inilah keputusan menjalankan dua program ini

  • Tanpa -ffast-math
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
로그인 후 복사
  • Dengan -ffast-math
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:

  • The -ffast-math memberikan rangsangan besar dalam prestasi (kira-kira 300% untuk kedua-dua kod berulir tunggal dan berbilang benang), tetapi ia boleh menjana hasil yang salah
  • Fastmath juga berfungsi di Numba, tetapi harus dielakkan atas sebab yang sama ia harus dielakkan dalam mana-mana aplikasi yang berusaha untuk ketepatan
  • Kod berulir tunggal C berjujukan memberikan prestasi serupa dengan PDL berulir tunggal dan Numpy
  • Agak menghairankan, kod jujukan adalah kira-kira 20% lebih pantas daripada kod bukan urutan apabila matematik yang betul (tidak pantas) digunakan.
  • Tidak menghairankan, kod berbilang benang lebih pantas daripada kod berbenang tunggal :)
  • Saya masih tidak dapat menjelaskan bagaimana numbas menyampaikan premium prestasi 50% berbanding kod C fungsi yang agak mudah ini.

위 내용은 성과를 위한 탐구 3부 : C Force의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!