Home > Software engineering >  (Edit) I wrote same code with Swift and C lang(Find Prime number), but C lang is much faster then Sw
(Edit) I wrote same code with Swift and C lang(Find Prime number), but C lang is much faster then Sw

Time:07-11

(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?

When I found until 4000th Prime number, C lang finished calculating with only one second. But, Swift finished with 38.8 seconds. It's much much slower than I thought.

Here is a code I wrote.

Do there any solutions to fast up Swift's code? (Sorry for the Japanese comment or text in the code.)

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.

CodePudding user response:

The fundamental cause

As with most of these "why does this same program in 2 different languages perform differently?", the answer is almost always: "because they're not the same program."

They might be similar in high-level intent, but they're implemented differently enough that you can distinguish their performance.

Sometimes they're different in ways you can control (e.g. you use an array in one program and a hash set in the other) or sometimes in ways you can't (e.g. you're using CPython and you're experiencing the overhead of interpretation and dynamic method dispatch, as compared to compiled C function calls).

Some example differences

In this case, there's a few notable differences I can see:

  1. The prime array in your C code uses unsigned int, which is typically akin to UInt32. Your Swift code uses Int, which is typically equivalent to Int64. It's twice the size, which doubles memory usage and decreases the efficacy of the CPU cache.
  2. Your C code pre-allocates the prime array on the stack, whereas your Swift code starts with an empty Array, and repeatedly grows it as necessary.
  3. Your C code doesn't pre-initialize the contents of the prime array. Any junk that might be leftover in the memory is still there to be observed, whereas the Swift code will zero-out all the array memory before use.
  4. All Swift arithmetic operations are checked for overflow. This introduces a branch within every single , %, etc. That's good for program safety (overflow bugs will never be silent and will always be detected), but sub-optimal in performance-critical code where you're certain that overflow is impossible. There's non-checked variants of all the operators that you can use, such as & , &-, etc.

The general trend

In general, you'll notice a trend that Swift optimizes for safety and developer experience, whereas C optimizes for being close to the hardware. Swift optimizes for allowing the developer to express their intent about the business logic, whereas C optimizes for allowing the developer to express their intent about the final machine code that runs.

There are typically "escape hatches" in Swift that let you sacrifice safety or convenience for C-like performance. This sounds bad, but arguably, you can view C just being exclusively using these escape hatches. There's no Array, Dictionary, automatic reference counting, Sequence algorithms, etc. E.g. what Swift calls UnsafePointer is just a "pointer" in C. "Unsafe" comes with the territory.

Improving the performance

You could get pretty far in hitting performance parity by:

  1. Pre-allocating a sufficiently large array with [Array.reserveCapacity(_:)](https://developer.apple.com/documentation/swift/array/reservecapacity(_:)). See this note in the Array documentation:

    Growing the Size of an Array

    Every array reserves a specific amount of memory to hold its contents. When you add elements to an array and that array begins to exceed its reserved capacity, the array allocates a larger region of memory and copies its elements into the new storage. The new storage is a multiple of the old storage’s size. This exponential growth strategy means that appending an element happens in constant time, averaging the performance of many append operations. Append operations that trigger reallocation have a performance cost, but they occur less and less often as the array grows larger.

    If you know approximately how many elements you will need to store, use the reserveCapacity(_:) method before appending to the array to avoid intermediate reallocations. Use the capacity and count properties to determine how many more elements the array can store without allocating larger storage.

    For arrays of most Element types, this storage is a contiguous block of memory. For arrays with an Element type that is a class or @objc protocol type, this storage can be a contiguous block of memory or an instance of NSArray. Because any arbitrary subclass of NSArray can become an Array, there are no guarantees about representation or efficiency in this case.

  2. Use UInt32 or Int32 instead of Int.

  3. If necessary drop down to UnsafeMutableBuffer<UInt32> instead of Array<UInt32>. This is closer to the simple pointer implementation used in your C example.

  4. You can used unchecked arithmetic operators like & , &-, &% and so on. Obviously, you should only do this when you're absolutely certain that overflow is impossible. Given how many thousands of silent overflow related bugs have come and gone, this is almost always a bad bet, but the loaded gun is available for you if you insist.

These aren't things you should generally do. They're merely possibilities that exist if they're necessary to improve performance of critical code.

For example, the Swift convention is to generally use Int unless you have a good reason to use something else. For example, Array.count returns an Int, even though it can never be negative, and is unlikely to ever need to be more than UInt32.max.

CodePudding user response:

You've forgotten to turn on the optimizer. Swift is much slower without optimization than C, but on things like this is roughly the same when optimized:

➜  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秒

This is without doing any work to improve your code (probably the most significant would be to pre-allocate the buffer like you do in C that doesn't actually matter).

  • Related