Hacker News new | ask | show | jobs
by austinz 3736 days ago
It's possible to write non-GUI command-line Swift programs that run on Linux (and certain other platforms). In terms of 'close to the metal', Swift is probably much closer to Rust or C++ than C# or Java, and is at least at the same level as Go.
2 comments

Is your 'close to the metal' claim based on any concrete experience with Swift? I like Swift as a language but I found its String/unicode support way too slow for the text processing stuff I do on the server side. I also tried using arrays instead, but in my text parsing micro benchmarks Swift is always four times slower than Go, Java, C++ and even two times slower than JavaScript/V8.

I don't really see a reason why this should be the case, so I hope it will improve over time.

But using mandatory reference counting does limit the sort of tasks for which Swift will ultimately be suitable. I wouldn't put it in the same category as C++ and Rust for that reason.

My (highly speculative) guess is that your String issues were due to either/both:

- Swift performance issues involving working with strings, some of which have been ameliorated by later releases;

- 'Hidden' bridging between NSString and String, which can be expensive and might have something to do with some of String's APIs actually being Foundation APIs in disguise.

There is much to be done with regards to Swift performance, though. If you have any sample benchmarks I would be interested in writing/running them myself, just for personal edification.

In terms of 'close to the metal', I freely admit this is a ill-defined term I am using for my own benefit. Swift code doesn't execute on something akin to the JVM or CLR; it still requires a heavier runtime than (e.g.) Rust. It is AOT-compiled to machine code. (But then again, this is possible to some degree for Java as far as I can tell, although it is certainly not the most popular way to run Java code.)

I agree that mandatory RC for classes and boxed types makes Swift impractical for a certain definition of 'systems software' typically written in C++ and occasionally Rust. For a different definition, one that encompasses the garbage-collected Go, Swift is theoretically suitable. The language designer has mentioned that he would personally like to see Swift gain some form of borrowing/lifetimes, although even if that did happen Swift would not get it for years to come.

I tried to avoid bridging as far as possible and I'm working directly with UTF-16 because that's the fastest view by far. I'm using the latest release version of Xcode on a Mac.

The simplest benchmark I used is a function that does the equivalent of

  str.split(/\s*,\s*/)
So it splits a string around a separator and removes any whitespace around the items. For a million strings the numbers I get are as follows:

Swift: 3.5 seconds

Java: 0.31 seconds (after warmup)

So that makes Java more than 10 times as fast as Swift.

Here's the source code. I would be more than happy if you could tell me that I'm doing something horribly wrong (which I frequently do):

    import Foundation

    let N = 1000000

    func generateTestData() -> [String] {
        var a = [String]()
        for _ in 0..<N {
            a.append(",,abc, 123  ,x, , more more more,\u{A0}and yet more, ")
        }
        return a
    }

    func splitAndTrim(s: String, sep: UInt16) -> [String] {
        var result = [String]()
        result.reserveCapacity(10)
        
        let space = NSCharacterSet.whitespaceAndNewlineCharacterSet()
        let cs = s.utf16
        let eos = cs.endIndex
        var begin = eos
        var end = eos
        
        for i in cs.startIndex..<eos {
            let c = cs[i]
            if c == sep {
                result.append(begin == eos ? "" : String(cs[begin..<end.successor()]))
                begin = eos
                end = eos
            } else if !space.characterIsMember(c) {
                if begin == eos {
                    begin = i
                }
                end = i
            }
        }
        
        result.append(begin == eos ? "" : String(cs[begin..<end.successor()]))
        return result
    }

    func doSplits(data: [String]) -> Int {
        var count = 0
        let sep = ",".utf16.first!
        for s in data {
            let parts = splitAndTrim(s, sep: sep)
            count += parts.count
        }
        return count
    }

    let data = generateTestData()
    let start = NSDate()
    let sum = doSplits(data)
    print("elapsed: \(NSDate().timeIntervalSinceDate(start))")
    print("sum: \(sum)")
And here's the Java code:

    import java.util.List;
    import java.util.ArrayList;

    public class ParseTest {
    
        static int N = 1000000;
    
        public static void main(String[] args) {
            List<String> data = generateTestData();
            for (int i = 0; i < 3; ++i) {
                long start = System.currentTimeMillis();
                int sum = doSplits(data);
                System.out.println("elapsed: " +
                        ((System.currentTimeMillis() - start) / 1000.0 + " seconds"));
                System.out.println("sum: " + sum);
            }
        }
    
        static int doSplits(List<String> a) {
            int count = 0;
            for (String s : a) {
                List<String> parts = splitAndTrim(s, ',');
                count += parts.size();
            }
            return count;
        }
    
        static List<String> splitAndTrim(String s, char sep) {
            List<String> result = new ArrayList<>(10);
            int eos = s.length();
            int begin = eos;
            int end = eos;
    
            for (int i = 0; i != eos; ++i) {
                char c = s.charAt(i);
                if (c == sep) {
                    result.add(begin == eos ? "" : s.substring(begin, end + 1));
                    begin = eos;
                    end = eos;
                } else if (!Character.isSpaceChar(c)) {
                    if (begin == eos) {
                        begin = i;
                    }
                    end = i;
                }
            }
    
            result.add(begin == eos ? "" : s.substring(begin, end + 1));
            return result;
        }
    
    
        static List<String> generateTestData() {
            List<String> a = new ArrayList<>();
            for (int i = 0; i < N; i++) {
                a.add(",,abc, 123  ,x, , more more more,\u00A0and yet more, ");
            }
            return a;
        }
    
    }
Can this headless Swift be compiled to a simple, self-contained, statically-linked, no-runtime-required binary like Go? (A real question, not some sort of advocacy.)
Any AOT compiler to native code allows for self-contained, statically-linked, no-runtime-required binaries.

There is nothing special in Go about it.