(编辑)我用 Swift 和 C lang(查找素数)编写了相同的代码,但 C lang 比 Swift 快得多



(下面有一些编辑)
好吧,我用 Swift 和 C lang 编写了完全相同的代码。这是一个代码,用于查找质数并显示它。
我希望 Swift lang 的代码比 C lang 的程序快得多,但事实并非如此。

有什么原因 Swift lang 比 C lang 代码慢得多吗?

当我找到直到第4000个素数时,C郎只用了一秒钟就完成了计算。 但是,斯威夫特以 38.8 秒结束。 它比我想象的要慢得多。

这是我写的代码。

有什么解决方案可以加速 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("nn終わり。")

} else if flagPrint == 2 {
print("(currentMaxCount)番目の素数は(prime[currentMaxCount - 1])です。")
}
print("(countMax)番目の素数まで計算。")
print("計算経過時間: (round(Double((endedTime - startedTime) / 100000)) / 10)秒")

#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.nn終わり", 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
}
}

我写了一个代码来比较所有数字。这是一个错误。 但是,我用这个代码修复,Swift 也在 4.7 秒内完成了计算。 它也比 C lang 慢 4 倍。

根本原因

与大多数"为什么2种不同语言的相同程序执行不同?"一样,答案几乎总是:"因为它们不是同一个程序。

它们在高级意图上可能相似,但它们的实现方式不同,因此您可以区分它们的性能。

有时它们以您可以控制的方式不同(例如,您在一个程序中使用数组,在另一个程序中使用哈希集),或者有时以您无法控制的方式(例如,您正在使用 CPython,并且与编译的 C 函数调用相比,您正在经历解释和动态方法调度的开销)。

一些示例差异

在这种情况下,我可以看到一些显着的差异:

  1. C 代码中的prime数组使用unsigned int,这通常类似于UInt32。您的 Swift 代码使用Int,这通常等同于Int64。它的大小是原来的两倍,这使内存使用量翻倍,并降低了 CPU 缓存的效率。
  2. 您的 C 代码在堆栈上预先分配prime数组,而您的 Swift 代码以空Array开头,并根据需要反复增长它。
  3. C 代码不会预先初始化prime数组的内容。内存中可能残留的任何垃圾仍然有待观察,而 Swift 代码将在使用前将所有数组内存清零。
  4. 检查所有 Swift 算术运算是否存在溢出。这在每个+%等中引入了一个分支。这对程序安全有好处(溢出错误永远不会静音,并且总是会被检测到),但在性能关键代码中是次优的,因为您确定溢出是不可能的。您可以使用的所有运算符都有未经检查的变体,例如&+&-等。

大势所趋

一般来说,你会注意到一个趋势,即 Swift 针对安全性和开发人员体验进行了优化,而 C 针对接近硬件进行了优化。Swift 优化允许开发人员表达他们对业务逻辑的意图,而 C 优化允许开发人员表达他们对运行的最终机器代码的意图。

Swift 中通常有"逃生舱口",可以让你牺牲安全性或便利性来换取类似 C 的性能。这听起来很糟糕,但可以说,你可以认为C只是专门使用这些逃生舱口。没有ArrayDictionary、自动引用计数、Sequence算法等。例如,Swift 所说的UnsafePointer只是 C 语言中的一个"指针","不安全"随领土而来。

提高性能

您可以通过以下方式在实现性能奇偶校验方面走得很远:

  1. 使用[Array.reserveCapacity(_:)](https://developer.apple.com/documentation/swift/array/reservecapacity(_:))预先分配足够大的数组。请参阅Array documentation中的注释:

    增大阵列的大小

    每个阵列都会保留特定数量的内存来保存其内容。当您向数组添加元素并且该数组开始超出其保留容量时,该数组将分配更大的内存区域并将其元素复制到新存储中。新存储是旧存储大小的倍数。这种指数增长策略意味着追加元素在恒定时间内发生,平均许多追加操作的性能。触发重新分配的追加操作会产生性能成本,但随着阵列变大,它们发生的频率越来越低。

    如果您知道大约需要存储多少个元素,请在追加到数组之前使用 reserveCapacity(_:) 方法以避免中间重新分配。使用容量和计数属性来确定阵列可以在不分配更大存储的情况下存储多少个元素。

    对于大多数元素类型的数组,此存储是一个连续的内存块。对于元素类型为类或@objc协议类型的数组,此存储可以是连续的内存块或 NSArray 的实例。因为 NSArray 的任何任意子类都可以成为数组,所以在这种情况下无法保证表示或效率。

  2. 使用UInt32Int32代替Int

  3. 如有必要,下拉到UnsafeMutableBuffer<UInt32>而不是Array<UInt32>。这更接近 C 示例中使用的简单指针实现。

  4. 您可以使用未经检查的算术运算符,如&+&-&%等。显然,只有当您绝对确定溢出是不可能的时,才应该这样做。考虑到成千上万的无声溢出相关错误来来去去,这几乎总是一个糟糕的赌注,但如果你坚持,你可以使用上膛的枪。

这些不是你通常应该做的事情。它们只是在提高关键代码性能所必需的情况下存在的可能性。

例如,Swift 约定是通常使用Int除非您有充分的理由使用其他东西。例如,Array.count返回一个Int,即使它永远不会是负数,并且不太可能需要超过UInt32.max个。

您忘记打开优化器了。没有优化的 Swift 比 C 慢得多,但在这样的事情上,优化时大致相同:

➜  x swift -O prime.swift
いくつ目まで?(最小2、最大100000まで)
→ 40000
表示方法を選んでください。(1:全て順番に表示、2:40000番目の一つだけ表示)
→ 2
40000番目の素数は479909です。
40000番目の素数まで計算。
計算経過時間: 5.9秒
➜  x clang -O3 prime.c && ./a.out
いくつ目まで?(最小2、最大100000まで)
→ 40000
表示方法を選んでください。(1:全て順番に表示、2:40000番目の一つだけ表示)
→ 2
40000番目の素数は「479909」です。
40000番目の素数まで計算計算経過時間: 6秒

这不需要做任何工作来改进你的代码(可能最重要的是像你在 C 中所做的那样预先分配缓冲区,这实际上并不重要)。

最新更新