Why cxx-interop slows compilation 3-4 times?

There is an open source library of astronomic algorithms called SwiftAA which is essentially a Swift wrapper for another well-known open source AA+ library written in C++. It uses C as an intermediate layer between Swift and C++ (it used to be Objective-C but they switched to pure C later).

I want to add a new feature to SwiftAA which haven't been ported from AA+ yet and I tempted to try new C++ interoperability feature, since it allows me to use C++ directly from Swift code without intermediary levels. I enabled C++ interoperability and I manage to get it working. However, I noticed that the compilation time jumped 3-4 times after I enable cxx-interop.

Compiling current version of SwiftAA with Xcode 15.4 takes about 32 seconds on my fairly old MacBook Pro 16" 2019 (Intel). But compiling the same thing with just the C++ interoperability enabled takes 113 seconds! I tried Xcode 16 beta 3 without much success (31 s without interop and 104 s with interop). Note: if you're going to try it on your machine, please delete DerivedData before every test.

So, why enabling C++ interoperability increases compilation time so drastically? Do you have any ideas how to improve it?

1 Like

Does anybody knows if compilation time is expected to rise so dramatically with cxx-interop or it is something unique in this project which makes compilation so slow. What's your mileage?

I don't use that Interop feature, but the docs have some info on compiler performance and diagnosing slowness. I've never used the internal compiler flags listed there, but you may want to dump some of those stats and try to get a better picture.

Thanks for bringing up this document, I wasn't aware of its existence. Here is what I found so far.

  1. I tried to turn on whole-module optimization and I found it doesn't improve things much (107s vs 113s).

  2. I tried to profile compilation with Instruments (CPU Counters template) and I found the most active swift-frontend process spends most of the time in clang::ParseAST(clang::Sema&, bool, bool) function (see attached screenshot). The heaviest trace contained what looks like a 20-times deep recursion into clang::ASTReader::ReadASTCore(...) function (again, see screenshot).

  3. Both -debug-time-function-bodies and -debug-time-expression-type-checking didn't show any particularly slow functions/expressions. There were only 2 functions around 500ms, all others were below 15ms.

  4. -print-clang-stats doesn't looks suspicious to me (header only):

*** AST File Statistics:
  11450/871209 source location entries read (1.314266%)
  58431/175893 types read (33.219627%)
  59789/347702 declarations read (17.195473%)
  108844/154863 identifiers read (70.284058%)
  1/100986 macros read (0.000990%)
  361/16155 selectors read (2.234602%)
  26128/428712 statements read (6.094534%)
  1/100986 macros read (0.000990%)
  3079/31530 lexical declcontexts read (9.765303%)
  4142/11270 visible declcontexts read (36.752441%)
  0/13971 method pool entries read (0.000000%)
  1 / 819 identifier table lookups succeeded (0.122100%)
  1. While testing all those scenarios I noticed peculiar behavior of Xcode compilation progress indicator (Building 123/456 files) along with CPU info in Activity Monitor. After starting the build process Xcode invokes a lot of swift-frontend processes which all consume a lot of CPU and the progress indicator moves quickly. Then the progress indicator stops moving and I see only one swift-frontend process actually using the CPU significantly. This continues for about a minute and then things get back to normal and compilation ends successfully.

Does anybody have any idea what's going on?

@egor.zhdan, any idea?

The slowdown is likely happening because enabling C++ interop means the compiler can't use the precompiled Clang modules, since it is building those modules in a different language mode (Objective-C++ vs Objective-C) and some of the symbols are treated differently.

After the initial build, subsequent builds should be much faster, even when the project needs to be rebuilt entirely – I would expect the majority of the time to be spent on re-building system modules in Objective-C++ mode. Are incremental builds that enable C++ interop also substantially slower?

Most of the time incremental builds are fast, but sometimes they hit something that makes them really slow.

What is interesting that Xcode's Report Navigator attributes all the slowness to the target SwiftAA, which uses only Swift and not to AABridge, which uses C/C++.