Lldb is slow to resolve local vars

Thanks Adrian again. I've used a bad terminology or incorrectly described what I did.

Why are you removing module map files?

I'm removing modulemap files generated by CocoaPods for me. Turns out if I remove them and let Xcode generate default ones, lldb works faster. For now I do this for frameworks that only contain Swift code though.

CocoaPods modulemap:

framework module Gallery {
  umbrella header "Gallery-umbrella.h"

  export *
  module * { export * }
}

module Gallery.Swift {
    header "Gallery-Swift.h"
    requires objc
}

I delete this ^^ and let Xcode generate a default modulemap which looks like:

framework module Gallery {
    header "Gallery-Swift.h"
    requires objc
}

Again, I do this for 171 Swift frameworks. This speeds up lldb quite a bit.

should strive to just make everything a Clang module

Again thanks for this, I've eliminated bridging header entirely. Now there are only clang modules. This gave ~11 seconds improvement (61 ses -> ~50).

Now, back to CocoaPods' modulemaps. After I've deleted them, lldb perf has improved: 50 sec -> 36 sec.

Since the only difference I spotted is this part:

framework module Gallery {
  umbrella header "Gallery-umbrella.h"

  export *
  module * { export * }
}

, and because -umbrella.h headers contain @import UIKit; wrapped with some #if-s, I decided that this is what causes slowdowns of lldb. Is this a correct theory?

I do not understand what you mean with "unwrap" here.

I thought that lldb would preprocess #imports like clang does (expand them recursivaly to a megabytes of text). My tests showed this is not a case, or at least this is not a bottleneck at all, and you confirmed that I shouldn't be doing this.

I do not understand what you mean with "unwrap" here.

I thought that lldb would preprocess #import s like clang does (expand them recursivaly to a megabytes of text). My tests showed this is not a case, or at least this is not a bottleneck at all, and you confirmed that I shouldn't be doing this.

To be clear, LLDB doesn't process header files directly at all. LLDB embeds Clang and asks it to import modules and Clang does the same thing it does during normal compilation. Unless Clang is somehow in PCH mode, there should not be a difference between @import and #import. So there might be something I'm missing here and I'd like to understand that. Do you also see compile-time improvements with your changes, too, or does it get faster only during debug time?

Finally, are you using 10.3 or the latest 10.4 beta? Does that make a difference?

Do you also see compile-time improvements with your changes, too, or does it get faster only during debug time?

No difference in clean build time at all. Only debugger speed improvement.

Finally, are you using 10.3 or the latest 10.4 beta?

This is on Xcode 11.3.1. Can't confirm now on latest beta, project is not buildable in Xcode 11.4. I will try to make it work and report back.

Xcode 11.4b3 is always shows "Internal error" banner when lldb pauses on a breakpoint. After a while lldb-rpc-server crashes. So I'm unable to check for lldb speed.

Can you please send me a crash log for lldb-rpc-server? Either attach it here or send a feedback ticket through feedback.apple.com?

Both SourceKitService and lldb-rpc-server crashed in 11.4b3:

Thanks you! I have not seen this kind of crash before. It's happening while deserializing a Clang module. If things are working correctly this shouldn't help, but I wonder if this also reproduces after you completely erase the DerivedData directory and rebuild.

Definitely reproducible after erasing derived data.

Is there any chance you could share a project with me that reproduces the crash?

I can't share my project unfortunately, and I wasn't able generate a sample project to reproduce this crash.

Our CI measurements show there IS some build time improvements after I iteratively removed modulemap files generated by CocoaPods:

All three measurements do build main app and its dependencies.

Legend: unit_tests are multiple unit test bundles (per module/framework), functional_tests - single UI test bundle, component_tests - single application test bundle.

Can't seem to load that image. Could you summarize with a table?

Odd, seems to load now.

In any event, were you able to remove such models automatically using CocoaPods, or did you do it manually in some way that prevented CocoaPods from regenerating them?

We've added a hook using CocoaPods Podfile APIs, so this patching happens as part of pod install and takes ~1 second.

Mind sharing your hook?

Hi @Dave_Lee, How can I disable CarbonBlack?

I can't share sources, but the idea is the following:

  • Subscribe to post_install in Podfile
  • Patch build settings for both top level and dependency targets, something like that:
def patch_build_flags(installer)
  installer.pbx_pod_targets.each do |pbx_pod_target|
    pbx_pod_target.xcbuild_configurations.each do |xcbuild_configuration|
      optimize_support_files(xcbuild_configuration.build_settings)
    end
  end

  installer.aggregate_targets.each do |aggregate_target|
    aggregate_target.each_xcconfig do |xcconfig, build_configuration|
      optimize_support_files(xcconfig.attributes)
    end
  end
end

def optimize_support_files(settings)
    settings['EXCLUDED_SOURCE_FILE_NAMES'] = '*-dummy.m'

    modulemap_file_relative_path = settings['MODULEMAP_FILE']
    if modulemap_file_relative_path != nil
        modulemap_file_relative_path = "#{repo_root}/<YourProjectName>/Pods/#{modulemap_file_relative_path}"
        target_support_files_path = File.expand_path("..", modulemap_file_relative_path)
        basename = File.basename(modulemap_file_relative_path, File.extname(modulemap_file_relative_path))

        umbrella_header_path = "#{target_support_files_path}/#{basename}-umbrella.h"
        update_uikit_import(umbrella_header_path) if File.file?(umbrella_header_path)

        @pods_with_disabled_modulemap = @pods_with_disabled_modulemap || []
        if @pods_with_disabled_modulemap.include?(basename)
            log "[build_settings_modification]: Removing modulemap for #{basename}"
            erase_file_contents(modulemap_file_relative_path)
            settings.delete('MODULEMAP_FILE')
        end
        
        @pods_modulemap_files = @pods_modulemap_files || {}
        if @pods_modulemap_files[basename]
            modulemap_path = "../#{@pods_modulemap_files[basename]}"
            settings['MODULEMAP_FILE'] = modulemap_path
            log "[build_settings_modification]: Using #{modulemap_path} for #{basename}"
        end
    end
end

We've also developed some syntax sugar around pods in Podfile for explicitness:

configure 'DeepLinks', { :path => '../', :disable_modulemap => true } # removes modulemap file entirely
configure 'Toolkit', { :path => '../', :testspecs => ['UnitTests'], :modulemap_file => '../Toolkit/Toolkit.modulemap' } # use provided modulemap file

We store modulemap settings per pod in global variable (thanks Ruby) and then modify file system state in post install.

Terms of Service

Privacy Policy

Cookie Policy