(There has some Edit in below)
Well, I wrote exactly the same code with Swift and C lang. It's a code to find a Prime number and show that.
I expect that Swift lang's Code is much faster than C lang's program, but It doesn't.

Is there any reason Swift lang is much slower than C lang code?

当我找到第4000个素数时,C郎只用了一秒钟就完成了计算.

这是我写的代码.

有什么解决方案可以加快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)秒")

Clang

#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;
}


**Add**
I found some reason for one.
for ix in 0..<currentMaxCount - 1 {
        if primeCandidate % prime[ix] == 0 {
            flag = 1
            break
        }
    }

I wrote a code to compare all numbers. That was a mistake. But, I fix with code with this, also Swift finished calculating in 4.7 secs. It's 4 times slower than C lang also.

推荐答案

根本原因

正如大多数人所说,"为什么同一个程序在两种不同的语言中表现不同?",答案几乎总是:"因为它们不是同一个程序."

它们在高层意图上可能类似,但它们的实现差异很大,因此可以区分它们的性能.

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

一些示例差异

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

  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.见Array documentation中的注释:

    增加数组的大小

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

    如果您知道大约需要存储多少个元素,请在附加到数组之前使用reserveCapacity(_:)方法,以避免中间重新分配.使用capacity和count属性确定数组在不分配较大存储空间的情况下可以再存储多少个元素.

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

  2. UInt32Int32代替Int.

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

  4. 可以使用未经判断的算术运算符,如&+&-&%等.显然,只有在确定溢出是不可能的情况下,才应该这样做.考虑到有数千个与silent溢出相关的bug来了又go ,这几乎总是一个糟糕的赌注,但如果你坚持的话,上膛的枪是可以给你的.

这些不是你通常应该做的事情.如果需要提高关键代码的性能,它们只是存在的可能性.

例如,Swift惯例通常使用Int,除非您有充分的理由使用其他内容.例如,Array.count返回Int,即使它永远不可能是负数,也不可能需要大于UInt32.max.

C++相关问答推荐

strftime函数中%s的历史意义是什么?为什么没有记录?

使用SWI—Prolog的qsave_program生成二进制文件有什么好处?'

va_copy的使用是未定义的行为吗?

测量ARM MCU中断延迟的问题

当我运行/调试C程序时,Malloc()似乎正在将&q;r\r...&q;赋值给一个指针,我不确定为什么?

我无法让LLDB正确运行我的可执行文件

在C23中使用_GENERIC实现带有右值的IS_POINTER(P)?

在列表中插入Int指针(C)

为什么GDB/MI进程的FIFO循环中有read()阻塞

使用scanf在C中读取和存储文件中的值

C-使用指针返回修改后的整数数组

在句子中转换单词的问题

C指针概念分段故障

可变宏不能编译

STM32 FATFS用户手册(Um1721)中的代码正确吗?

";错误:寄存器的使用无效;当使用-masm=intel;在gcc中,但在AT&;T模式

添加/删除链表中的第一个元素

我们可以在不违反标准的情况下向标准函数声明添加属性吗?

inline 关键字导致 Clion 中的链接器错误

在 printf() 格式说明符中使用字段宽度变量