I am always a sucker for performance. Recently I migrated a .NET GDI+ app to Swift. There were several reasons for it:
- GDI+ is no longer supported on MacOS with .NET 7.0
- I thought using the native MacOS graphics libraries would be faster than running .NET on MacOS
- I just wanted to learn Swift and its Graphics API
To my surprise the C# code runs way faster than Swift. The difference is not because of GDI+ vs Swift’s CoreGraphics but because of the more basic non-graphics operations. Think of the basic statements that come with a programming language. When I profiled the code, I found performance of GDI+ vs CoreGraphics was the same. The part where the performance differed was in executing non-graphics code. I wasn’t using any libraries at all in this code – just using the statements that come with the programming language.
I kept on digging further into the rabbit hole. What do we find? Let’s create a 2D array of bools and initialize it to random values. Below is the code in C# and Swift.
C#
public static int test(int width, int height) {
var rand = new Random();
var mask = new bool[height][];
for (int i = 0; i < mask.Length; i++)
{
mask[i] = new bool[width];
}
int count = 0;
for(int row = 0; row < height; row++) {
for(int col = 0; col < width; col++) {
if (rand.Next(0, 2) == 1) {
mask[row][col] = true;
count++;
}
}
}
return count;
}
Swift
static func test(_ width: Int, _ height: Int) -> UInt {
var mask = Array(repeating: Array(repeating: false, count: width), count: height)
var count: UInt = 0
for row in 0..<height {
for col in 0..<width {
if (Bool.random()) {
mask[row][col] = true
count += 1
}
}
}
return count
}
What do we find when we run this code? When I did 100 iterations of above code with width = height = 3840 on a M1 Mac mini with 8GB RAM running Mac OS 13.1 (Ventura) here are the runtimes I got:
C# (.NET 6.0): 00:00:20.41 s
Swift (version 5.7.2): 157.7 s
The difference is staggering. C# is almost 8x faster than Swift! Let me know what you think.
For reference below is C# code to do 100 iterations:
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
int width = Int32.Parse(args[0]);
int height = Int32.Parse(args[1]);
int n = Int32.Parse(args[2]);
for (int i = 0; i < n; i++) {
Array2D.test(width, height);
}
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatch.Elapsed;
Console.WriteLine("completed {0} iterations", n);
// Format and display the TimeSpan value.
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine("RunTime " + elapsedTime);
and corresponding Swift code:
let width = Int(CommandLine.arguments[1])!
let height = Int(CommandLine.arguments[2])!
let n = Int(CommandLine.arguments[3])!
print("running \(n) loops of \(width)x\(height) 2d array test...")
let clock = SuspendingClock()
let t1 = clock.now
for i in 1...n {
// print("iteration \(i) of \(n)")
test(width, height)
}
let t2 = clock.now
print("\(t2-t1)")