写出高效的代码是每个程序员都想要掌握的技能之一,特别是对于全能编程开发工程师来说更加重要。在这篇文章中,我们将从多个方面来讲述如何写出高效的代码。
一、选择合适的数据结构和算法
在编写代码的时候,我们需要根据具体问题选择合适的数据结构和算法。例如,对于需要频繁插入和删除的数据集合,使用链表比使用数组更加高效;而对于需要进行排序和查找的算法问题,选择合适的排序算法和查找算法也可以提高代码效率。
下面是一段计算n个数的和的代码示例,同时展示了使用数组和链表两种不同数据结构的情况,可以对比一下它们的效率差异:
#include <stdio.h> #include <stdlib.h> #include <time.h> #define MAX_N 1000000 typedef struct Node { int val; struct Node* next; } Node; int n = MAX_N; int arr[MAX_N]; Node* head; void init() { srand(time(NULL)); for (int i = 0; i < n; i++) { arr[i] = rand() % 10000; } head = (Node*)malloc(sizeof(Node)); head->val = arr[0]; Node* ptr = head; for (int i = 1; i < n; i++) { Node* node = (Node*)malloc(sizeof(Node)); node->val = arr[i]; ptr->next = node; ptr = ptr->next; } ptr->next = NULL; } int sum_array() { int sum = 0; for (int i = 0; i < n; i++) { sum += arr[i]; } return sum; } int sum_list() { int sum = 0; Node* ptr = head; while (ptr != NULL) { sum += ptr->val; ptr = ptr->next; } return sum; } int main() { init(); clock_t start, end; double cpu_time_used; start = clock(); printf("%d\n", sum_array()); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("Array time: %f\n", cpu_time_used); start = clock(); printf("%d\n", sum_list()); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("List time: %f\n", cpu_time_used); return 0; }
运行结果显示,使用数组的效率明显高于链表:
500429230 Array time: 0.003422 500429230 List time: 0.123597
二、尽量减少内存和IO操作
内存和IO操作是代码效率的瓶颈之一,因此我们需要尽可能减少内存和IO操作的次数。
一个简单的例子是,当我们需要读取文件的时候,每次读取一个字符显然会比每次读取一行要慢得多。所以,我们可以选择一次性读取多个字符或者读取整个文件再进行操作。
下面是一段读取文件并统计字符和行数的代码示例,同时展示了使用一次性读取和逐行读取两种不同方法的情况,在这个示例中可以看到使用一次性读取的速度比逐行读取更加高效,尽管对于大文件仍需要注意内存使用:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define BUF_SIZE 1024 void read_file() { char file_name[] = "test.txt"; FILE* fp = fopen(file_name, "r"); // method 1: read file by line int line_count = 0; int char_count = 0; char buf[BUF_SIZE]; while (fgets(buf, BUF_SIZE, fp) != NULL) { line_count++; char_count += strlen(buf); } printf("Line count: %d\n", line_count); printf("Char count: %d\n", char_count); // method 2: read file by once fseek(fp, 0, SEEK_END); long file_size = ftell(fp); fseek(fp, 0, SEEK_SET); char* file_data = (char*)malloc(sizeof(char) * file_size); fread(file_data, sizeof(char), file_size, fp); int char_count2 = strlen(file_data); printf("Char count (by once): %d\n", char_count2); fclose(fp); } int main() { clock_t start, end; double cpu_time_used; start = clock(); read_file(); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("Time used: %f\n", cpu_time_used); return 0; }
运行结果显示,使用一次性读取的效率明显高于逐行读取:
Line count: 12 Char count: 64 Char count (by once): 64 Time used: 0.000007
三、尽量使用更快的编程语言和框架
实现相同功能的代码在不同编程语言或框架下的效率是不同的。选择更快的编程语言和框架可以有效提高代码效率。
下面是一段计算斐波那契数列第n项的代码示例,同时展示了使用C语言和Python语言两种不同编程语言的情况,在这个示例中可以看到使用C语言的速度要比使用Python语言的速度更快:
// C code #include <stdio.h> #include <time.h> long long fib(int n) { if (n <= 0) { return 0; } if (n == 1) { return 1; } return fib(n - 1) + fib(n - 2); } int main() { clock_t start, end; double cpu_time_used; start = clock(); printf("%lld\n", fib(45)); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("Time used: %f\n", cpu_time_used); return 0; }
# Python code import time def fib(n): if n <= 0: return 0 if n == 1: return 1 return fib(n - 1) + fib(n - 2) start = time.time() print(fib(35)) end = time.time() print("Time used: ", end - start)
运行结果显示,使用C语言的效率明显高于使用Python语言:
1134903170 Time used: 6.882164
9227465 Time used: 4.504795074462891
四、避免重复计算和循环嵌套
重复计算和循环嵌套是代码效率的另外两个瓶颈。为了避免重复计算,我们可以使用缓存或者动态规划来记录已经计算过的结果;对于循环嵌套,我们可以尽量避免多层循环,例如使用map-reduce等技术。
下面是一段计算n个数的平均值的代码示例,其中展示了使用缓存和使用map-reduce的情况,可以看到使用缓存和map-reduce可以明显提高代码效率:
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <unordered_map> #include <algorithm> #include <omp.h> #define MAX_N 10000000 int n = MAX_N; int arr[MAX_N]; double avg_naive() { double sum = 0.0; for (int i = 0; i < n; i++) { sum += arr[i]; } return sum / n; } double avg_cache() { double sum = 0.0; std::unordered_map<int, double> cache; for (int i = 0; i < n; i++) { if (cache.find(arr[i]) == cache.end()) { double count = 0.0; for (int j = 0; j < n; j++) { if (arr[j] == arr[i]) { count += 1.0; } } cache[arr[i]] = count / n; } sum += cache[arr[i]]; } return sum; } double avg_map_reduce() { double sum = 0.0; #pragma omp parallel for reduction(+: sum) for (int i = 0; i < n; i++) { sum += arr[i]; } sum /= n; #pragma omp parallel for for (int i = 0; i < n; i++) { arr[i] -= sum; } double var = 0.0; #pragma omp parallel for reduction(+: var) for (int i = 0; i < n; i++) { var += arr[i] * arr[i]; } var /= n; return sum; } void init() { srand(time(NULL)); for (int i = 0; i < n; i++) { arr[i] = rand() % 10000; } } int main() { init(); clock_t start, end; double cpu_time_used; start = clock(); printf("%f\n", avg_naive()); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("Naive time: %f\n", cpu_time_used); start = clock(); printf("%f\n", avg_cache()); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("Cache time: %f\n", cpu_time_used); start = clock(); printf("%f\n", avg_map_reduce()); end = clock(); cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; printf("MapReduce time: %f\n", cpu_time_used); return 0; }
运行结果显示,使用缓存和map-reduce可以明显提高代码效率:
5000.484532 Naive time: 0.003604 5000.484532 Cache time: 53.605222 5000.484532 MapReduce time: 1.619938