在本系列的前兩期中,我們考慮了 Perl 中浮動操作的性能,
Python 和 R 在一個玩具範例中計算函數 cos(sin(sqrt(x))),其中 x 是一個 非常大 50M 雙精確度浮點數數組。
將算術密集型部分委託給 C 的混合實作是效能最高的實作之一。在本期中,我們將稍微偏離主題,看看玩具範例的純 C 程式碼實現的效能。
C 程式碼將提供有關記憶體局部性對於效能重要性的進一步見解(預設情況下,C 陣列中的元素儲存在記憶體中的順序位址中,以及數字API,例如PDL 或numpy 與此類容器的介面)相對於容器,
例如Perl 數組不將其值儲存在記憶體中的連續位址中。最後但同樣重要的是,C 程式碼實作將允許我們評估與低階編譯器(在本例中為 gcc)的浮點運算相關的標誌是否會影響效能。
這一點值得強調:普通人在「管道」安裝或建置內聯檔時完全依賴編譯器標誌的選擇。如果一個人不觸及這些標誌,那麼人們就會幸福地不知道他們可能會錯過什麼,或者他們可能會避免的陷阱。
簡單的 C 檔案 makefile 允許人們明確地進行此類效能評估。
下面完整列出了我們玩具範例的 C 程式碼。程式碼是不言自明的,因此除了指出它包含
的四個函數之外,不會花時間解釋- 昂貴函數的非順序計算:所有三個浮點操作都使用一個執行緒在單一循環內進行
- 昂貴函數的順序計算:3 個浮點函數計算中的每一個都使用一個執行緒在單獨的循環中進行
- 非順序 OpenMP 程式碼:非順序程式碼的執行緒版本
- 順序 OpenMP 程式碼:順序程式碼的執行緒化
在這種情況下,人們可能希望編譯器足夠聰明,能夠識別平方根映射到彙編中的打包(向量化)浮點操作,以便可以使用適當的SIMD 指令對一個函數進行向量化(請注意,我們做了一個不使用OpenMP 程式碼的simd 程式)。
也許向量化帶來的加速可以抵消重複存取(或不存取)相同記憶體位置所造成的效能損失。
#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 <p>一個關鍵問題是使用快速浮動編譯器標誌(一種以速度換取程式碼準確性的技巧)是否會影響效能。 <br> 這是沒有這個編譯器標誌的 makefile<br> </p> <pre class="brush:php;toolbar:false">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 $ <p>這是帶有此標誌的:<br> </p> <pre class="brush:php;toolbar:false">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 $ <p>這是執行這兩個程式的結果</p>
- 沒有-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
- 使用 -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
請注意,可以在 Numba 程式碼中使用 fastmath,如下所示(預設為 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)
值得注意的幾點:
- -ffast-math 顯著提高了效能(單執行緒和多執行緒程式碼大約提高了 300%),但它可能會產生錯誤的結果
- Fastmath 也適用於 Numba,但應避免使用,其原因與在任何追求準確性的應用程式中應避免的原因相同
- 順序 C 單執行緒程式碼的效能類似單執行緒 PDL 和 Numpy
- 有點令人驚訝的是,當使用正確(非快速)數學時,順序代碼比非順序代碼快大約 20%。
- 毫不奇怪,多執行緒程式碼比單執行緒程式碼更快:)
- 我仍然無法解釋 numbas 如何為這個相當簡單的函數提供比 C 程式碼高 50% 的效能。
標題:「效能追求第三部分:C Force」
日期:2024-07-07
在本系列的前两期中,我们考虑了 Perl 中浮动操作的性能,
Python 和 R 在一个玩具示例中计算函数 cos(sin(sqrt(x))),其中 x 是一个 非常大 50M 双精度浮点数数组。
将算术密集型部分委托给 C 的混合实现是性能最高的实现之一。在本期中,我们将稍微偏离主题,看看玩具示例的纯 C 代码实现的性能。
C 代码将提供有关内存局部性对于性能重要性的进一步见解(默认情况下,C 数组中的元素存储在内存中的顺序地址中,以及数字 API,例如 PDL 或 numpy 与此类容器的接口)相对于容器,
例如Perl 数组不将其值存储在内存中的连续地址中。最后但同样重要的是,C 代码实现将允许我们评估与低级编译器(在本例中为 gcc)的浮点运算相关的标志是否会影响性能。
这一点值得强调:普通人在“管道”安装或构建内联文件时完全依赖于编译器标志的选择。如果一个人不触及这些标志,那么人们就会幸福地不知道他们可能会错过什么,或者他们可能会避免的陷阱。
简陋的 C 文件 makefile 允许人们明确地进行此类性能评估。
下面完整列出了我们玩具示例的 C 代码。该代码相当不言自明,因此除了指出它包含
的四个函数之外,不会花时间解释- 昂贵函数的非顺序计算:所有三个浮点操作都使用一个线程在单个循环内进行
- 昂贵函数的顺序计算:3 个浮点函数计算中的每一个都使用一个线程在单独的循环中进行
- 非顺序 OpenMP 代码:非顺序代码的线程版本
- 顺序 OpenMP 代码:顺序代码的线程化
在这种情况下,人们可能希望编译器足够聪明,能够识别平方根映射到汇编中的打包(矢量化)浮点操作,以便可以使用适当的 SIMD 指令对一个函数进行矢量化(请注意,我们做了不使用 OpenMP 代码的 simd 程序)。
也许矢量化带来的加速可以抵消重复访问(或不访问)相同内存位置所造成的性能损失。
#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 <p>一个关键问题是使用快速浮动编译器标志(一种以速度换取代码准确性的技巧)是否会影响性能。 <br> 这是没有这个编译器标志的 makefile<br> </p> <pre class="brush:php;toolbar:false">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 $ <p>这是带有此标志的:<br> </p> <pre class="brush:php;toolbar:false">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 $ <p>这是运行这两个程序的结果</p>
- 没有-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
- 使用 -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
请注意,可以在 Numba 代码中使用 fastmath,如下所示(默认为 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)
值得注意的几点:
- -ffast-math 显着提高了性能(单线程和多线程代码大约提高了 300%),但它可能会生成错误的结果
- Fastmath 在 Numba 中也适用,但应避免使用,其原因与在任何追求准确性的应用程序中应避免的原因相同
- 顺序 C 单线程代码的性能类似于单线程 PDL 和 Numpy
- 有点令人惊讶的是,当使用正确(非快速)数学时,顺序代码比非顺序代码快大约 20%。
- 毫不奇怪,多线程代码比单线程代码更快:)
- 我仍然无法解释 numbas 如何为这个相当简单的函数提供比 C 代码高 50% 的性能。
以上是追求性能第三部分:C Force的詳細內容。更多資訊請關注PHP中文網其他相關文章!

C 學習者和開發者可以從StackOverflow、Reddit的r/cpp社區、Coursera和edX的課程、GitHub上的開源項目、專業諮詢服務以及CppCon等會議中獲得資源和支持。 1.StackOverflow提供技術問題的解答;2.Reddit的r/cpp社區分享最新資訊;3.Coursera和edX提供正式的C 課程;4.GitHub上的開源項目如LLVM和Boost提陞技能;5.專業諮詢服務如JetBrains和Perforce提供技術支持;6.CppCon等會議有助於職業

C#適合需要高開發效率和跨平台支持的項目,而C 適用於需要高性能和底層控制的應用。 1)C#簡化開發,提供垃圾回收和豐富類庫,適合企業級應用。 2)C 允許直接內存操作,適用於遊戲開發和高性能計算。

C 持續使用的理由包括其高性能、廣泛應用和不斷演進的特性。 1)高效性能:通過直接操作內存和硬件,C 在系統編程和高性能計算中表現出色。 2)廣泛應用:在遊戲開發、嵌入式系統等領域大放異彩。 3)不斷演進:自1983年發布以來,C 持續增加新特性,保持其競爭力。

C 和XML的未來發展趨勢分別為:1)C 將通過C 20和C 23標準引入模塊、概念和協程等新特性,提升編程效率和安全性;2)XML將繼續在數據交換和配置文件中佔據重要地位,但會面臨JSON和YAML的挑戰,並朝著更簡潔和易解析的方向發展,如XMLSchema1.1和XPath3.1的改進。

現代C 設計模式利用C 11及以後的新特性實現,幫助構建更靈活、高效的軟件。 1)使用lambda表達式和std::function簡化觀察者模式。 2)通過移動語義和完美轉發優化性能。 3)智能指針確保類型安全和資源管理。

C 多線程和並發編程的核心概念包括線程的創建與管理、同步與互斥、條件變量、線程池、異步編程、常見錯誤與調試技巧以及性能優化與最佳實踐。 1)創建線程使用std::thread類,示例展示瞭如何創建並等待線程完成。 2)同步與互斥使用std::mutex和std::lock_guard保護共享資源,避免數據競爭。 3)條件變量通過std::condition_variable實現線程間的通信和同步。 4)線程池示例展示瞭如何使用ThreadPool類並行處理任務,提高效率。 5)異步編程使用std::as

C 的內存管理、指針和模板是核心特性。 1.內存管理通過new和delete手動分配和釋放內存,需注意堆和棧的區別。 2.指針允許直接操作內存地址,使用需謹慎,智能指針可簡化管理。 3.模板實現泛型編程,提高代碼重用性和靈活性,需理解類型推導和特化。

C 適合系統編程和硬件交互,因為它提供了接近硬件的控制能力和麵向對象編程的強大特性。 1)C 通過指針、內存管理和位操作等低級特性,實現高效的系統級操作。 2)硬件交互通過設備驅動程序實現,C 可以編寫這些驅動程序,處理與硬件設備的通信。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

記事本++7.3.1
好用且免費的程式碼編輯器

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3漢化版
中文版,非常好用