同逻辑素数查找Swift代码远慢于C的原因及优化方案
同逻辑素数计算Swift性能远低于C的原因与优化方案
测试背景
分别使用Swift、C编写逻辑完全一致的素数查找程序,预期Swift性能接近C,但实际测试结果差距明显:计算到第4000个素数时,C语言统计耗时1秒,初始版本Swift耗时38.8秒;修正遍历逻辑(从遍历2到候选数的所有整数,改为仅遍历已找到的素数集合做整除校验)后,Swift仍需4.7秒,耗时为同逻辑C实现的4倍。
核心性能差距原因
- 内存模型差异:C代码中
prime是提前声明在栈上的10万元素固定长度数组,元素访问是直接计算内存偏移的裸操作,无任何额外开销;Swift的Array是存储在堆上的动态数组,默认带写时复制机制,未提前分配容量时每次append都可能触发内存重分配、全量元素拷贝,且默认所有下标访问都带越界检查,天然比C的裸数组访问多一层安全校验成本。 - 编译优化默认差异:日常调试用的Swift Debug模式默认关闭所有性能优化,保留完整运行时类型检查、调试符号,循环逻辑不会被编译为最精简的CPU指令;而C语言哪怕不手动开高等级优化,编译器默认就会折叠冗余变量、简化循环判断,开O2优化后还会自动做指令重排、循环展开,性能差距会被进一步放大。
- 基础操作开销差:Swift的
Range类型遍历在未优化状态下会生成区间迭代的额外调度代码,不像C的三段式for循环直接映射为CPU的比较、跳转原生指令;Swift整数取模运算默认带溢出检查逻辑,未优化时不会直接编译为CPU原生取模指令,比C的整数运算多一层安全判断成本。 - 计时逻辑本身的误差:Swift用的
clock()是微秒级精度,会把Swift运行时初始化、动态库加载的时间都算入耗时;C用的time(NULL)是秒级精度,统计粒度极粗,测出来的1秒本身就有±0.5秒的误差,实际同逻辑C代码计算第4000个素数的耗时远低于1秒,两者的实际性能差没有测试结果显示的4倍那么夸张。
Swift代码可行优化方案
- 调整编译配置:把Swift运行方案切到Release模式,编译器优化等级选择
Optimize for Speed [-O],同时开启Remove Safety Checks选项移除运行时数组越界、整数溢出检查,仅这一步就能把Swift代码性能提升5-10倍,基本追平C的性能水平。 - 数组提前预分配容量:在读取用户输入的
countMax之后,给prime数组调用prime.reserveCapacity(countMax),提前申请好足够的堆内存,彻底避免后续append触发的动态扩容、元素拷贝开销。 - 清理冗余逻辑:删掉完全无意义的
let completedPrimeNumber = prime.map { $0 }拷贝操作,输出时直接遍历原数组即可;简化flag判断逻辑,不用单独写分支重置flag,每次外层循环开始时直接把flag置0就行,减少不必要的分支判断。 - 算法层优化:第一,遍历素数做整除判断时,只需要遍历到值不大于
primeCandidate.squareRoot()的素数即可——如果n存在大于sqrt(n)的因数,对应的配对因数必然小于sqrt(n),已经被遍历过,不需要检查后面的素数;第二,除了2之外所有素数都是奇数,初始化时直接存2、3,候选数从5开始每次步进2,跳过所有偶数,直接减少一半的遍历量。 - 极致性能优化:如果追求和C完全对齐的访问开销,可以用
withUnsafeBufferPointer获取prime数组的连续裸内存指针,用while循环做下标递增遍历,完全绕过Swift数组的默认访问调度。
附实现代码
初始Swift版本(存在遍历逻辑问题)
import CoreFoundation /* var calendar = Calendar.current calender.locale = .init(identifier: "ja.JP") */ var primeCandidate: Int var prime: [Int] = [] var countMax: Int print("いくつ目まで?(最小2、最大100000まで)\n→ ", terminator: "") countMax = Int(readLine()!)! var flagPrint: Int print("表示方法を選んでください。(1:全て順番に表示、2:\(countMax)番目の一つだけ表示)\n→ ", terminator: "") flagPrint = Int(readLine()!)! prime.append(2) prime.append(3) var currentMaxCount: Int = 2 var numberCount: Int primeCandidate = 4 var flag: Int = 0 var ix: Int let startedTime = clock() //let startedTime = time() //.addingTimeInterval(0.0) while currentMaxCount < countMax { for ix in 2..<primeCandidate { if primeCandidate % ix == 0 { flag = 1 break } } if flag == 0 { prime.append(primeCandidate) currentMaxCount += 1 } else if flag == 1 { flag = 0 } primeCandidate += 1 } let endedTime = clock() //let endedTime = Time() //.timeIntervalSince(startedTime) if flagPrint == 1 { print("計算された素数の一覧:", terminator: "") let completedPrimeNumber = prime.map { $0 } print(completedPrimeNumber) //print("\(prime.map)") print("\n\n終わり。") } else if flagPrint == 2 { print("\(currentMaxCount)番目の素数は\(prime[currentMaxCount - 1])です。") } print("\(countMax)番目の素数まで計算。") print("計算経過時間: \(round(Double((endedTime - startedTime) / 100000)) / 10)秒")
对照C语言实现
#include <stdio.h> #include <time.h> //経過時間計算のため int main(void) { int primeCandidate; unsigned int prime[100000]; int countMax; printf("いくつ目まで?(最小2、最大100000まで)\n→ "); scanf("%d", &countMax); int flagPrint; printf("表示方法を選んでください。(1:全て順番に表示、2:%d番目の一つだけ表示)\n→ ", countMax); scanf("%d", &flagPrint); prime[0] = 2; prime[1] = 3; int currentMaxCount = 2; int numberCount; primeCandidate = 4; int flag = 0; int ix; int startedTime = time(NULL); for(;currentMaxCount < countMax;primeCandidate++){ /* for(numberCount = 0;numberCount < currentMaxCount - 1;numberCount++){ if(primeCandidate % prime[numberCount] == 0){ flag = 1; break; } } */ for(ix = 2;ix < primeCandidate;++ix){ if(primeCandidate % ix == 0){ flag = 1; break; } } if(flag == 0){ prime[currentMaxCount] = primeCandidate; currentMaxCount++; } else if(flag == 1){ flag = 0; } } int endedTime = time(NULL); if(flagPrint == 1){ printf("計算された素数の一覧:"); for(int i = 0;i < currentMaxCount - 1;i++){ printf("%d, ", prime[i]); } printf("%d.\n\n終わり", prime[currentMaxCount - 1]); } else if(flagPrint == 2){ printf("%d番目の素数は「%d」です。\n",currentMaxCount ,prime[currentMaxCount - 1]); } printf("%d番目の素数まで計算", countMax); printf("計算経過時間: %d秒\n", endedTime - startedTime); return 0; }
修正后遍历逻辑核心代码段
for ix in 0..<currentMaxCount - 1 { if primeCandidate % prime[ix] == 0 { flag = 1 break } }
内容的提问来源于stack exchange,提问作者Luna
相关产品推荐
相关产品推荐

