Skip to content

Record performance#23

Merged
rockname merged 2 commits intomainfrom
signpost
Feb 15, 2025
Merged

Record performance#23
rockname merged 2 commits intomainfrom
signpost

Conversation

@rockname
Copy link
Owner

@rockname rockname commented Jan 19, 2025

Running SwordCommand Locally

  1. Select the "SwordCommand" scheme
  2. Open the scheme options
  3. Set an output directory to the "Arguments Passed On Launch" in the "Arguments" tab
  4. Set a path where you would like to execute SwordCommand to the "Working Directory" in the "Options" tab
  5. Hit "Run"

📝 Output directory and Working Directory should be different not to include a sword generated file in parsing source files.

Instruments

Environments

  • Apple M1 Pro
  • Memory 32 GB
  • macOS Sonoma 14.6.1
  • Xcode 16.2
  • 10000 swift files
    • 10 components (1 component / 9 subcomponents)
    • 5000 dependencies (500 per component)
    • 5000 modules (500 per component / 1 provider per module)
📝 Generate swift files using a script like below:
let subcomponentFileCount = 9
let dependencyFileCount = 500
let outputDirectoryName = "LargeProjectSample/Package/Sources/LargeDependencies/Generated"

let componentFileTemplate = """
import Sword

public struct AppComponentArgument {
  public let value: String
} 

@Component(arguments: AppComponentArgument.self)
public final class AppComponent {
}
"""

let subcomponentFileTemplate = """
import Sword

public struct {subcomponent_name}Argument {
  public let value: String
} 

@Subcomponent(of: {parent_component_name}.self, arguments: {subcomponent_name}Argument.self)
public final class {subcomponent_name} {
}
"""

let dependencyFileTemplate = """
import Sword

@Dependency(registeredTo: {component_name}.self)
public final class {dependency_name} {
  private let componentArgument: {component_name}Argument

  private var text: String {
    componentArgument.value
  }

  @Injected
  public init(componentArgument: {component_name}Argument) {
    self.componentArgument = componentArgument
  }

  public func example() {
    printText()
  }

  private func printText() {
    print(text)
  }
}
"""

let injectableDependencyFileTemplate = """
import Sword

@Dependency(registeredTo: {component_name}.self)
public final class {dependency_name} {
  private let componentArgument: {component_name}Argument
  private let injectedDependency1: {injected_dependency_name1}
  private let injectedDependency2: {injected_dependency_name2}

  private var text: String {
    componentArgument.value
  }

  @Injected
  public init(componentArgument: {component_name}Argument, injectedDependency1: {injected_dependency_name1}, injectedDependency2: {injected_dependency_name2}) {
    self.componentArgument = componentArgument
    self.injectedDependency1 = injectedDependency1
    self.injectedDependency2 = injectedDependency2
  }

  public func example() {
    printText()
    callInjectedDependency1Example()
    callInjectedDependency2Example()

  }

  private func printText() {
    print(text)
  }

  private func callInjectedDependency1Example() {
    injectedDependency1.example()
  }

  private func callInjectedDependency2Example() {
    injectedDependency2.example()
  }
}
"""

let moduleFileTemplate = """
import Sword

public struct {provided_dependency_name} {
  private var text: String {
    "provider example"
  }

  public func example() {
    printText()
  }

  private func printText() {
    print(text)
  }
}

@Module(registeredTo: {component_name}.self)
public struct {module_name} {

  @Provider
  public static func provider() -> {provided_dependency_name} {
      {provided_dependency_name}()
  }
}
"""

do {
    try FileManager.default.createDirectory(
        atPath: outputDirectoryName,
        withIntermediateDirectories: true,
        attributes: nil
    )

    let componentName = "AppComponent"
    let componentFileContent = componentFileTemplate
    let componentFilePath = (outputDirectoryName as NSString).appendingPathComponent("\(componentName).swift")
    try componentFileContent.write(toFile: componentFilePath, atomically: true, encoding: .utf8)

    for moduleIndex in 0..<dependencyFileCount {
        let moduleName = "\(componentName)Module\(moduleIndex)"
        let providedDependencyName = "\(moduleName)ProvidedDependency"
        let moduleFileContent = makeModuleFileContent(
            moduleName: moduleName,
            componentName: componentName,
            providedDependencyName: providedDependencyName
        )
        let moduleFilePath = (outputDirectoryName as NSString).appendingPathComponent("\(moduleName).swift")
        try moduleFileContent.write(toFile: moduleFilePath, atomically: true, encoding: .utf8)
    }

    for dependencyIndex in 0..<dependencyFileCount {
        let dependencyName = "\(componentName)Dependency\(dependencyIndex)"
        let moduleName = "\(componentName)Module\(dependencyIndex)"
        let providedDependencyName = "\(moduleName)ProvidedDependency"
        let dependencyFileContent = if dependencyIndex == 0 {
            makeDependencyFileContent(dependencyName: dependencyName, componentName: componentName)
        } else {
            makeDependencyFileContent(
                dependencyName: dependencyName,
                componentName: componentName, 
                injectedDependencyName1: "\(componentName)Dependency\(dependencyIndex-1)",
                injectedDependencyName2: providedDependencyName
            )
        }
        let dependencyFilePath = (outputDirectoryName as NSString).appendingPathComponent("\(dependencyName).swift")
        try dependencyFileContent.write(toFile: dependencyFilePath, atomically: true, encoding: .utf8)
    }

    for i in 0..<subcomponentFileCount {
        let subcomponentName = "Subcomponent\(i)"
        let parentComponentName = if i == 0 {
            "AppComponent"
        } else {
            "Subcomponent\(i-1)"
        }
        let subcomponentFileContent = subcomponentFileTemplate
            .replacingOccurrences(of: "{subcomponent_name}", with: subcomponentName)
            .replacingOccurrences(of: "{parent_component_name}", with: parentComponentName)
        let filePath = (outputDirectoryName as NSString).appendingPathComponent("\(subcomponentName).swift")
        try subcomponentFileContent.write(toFile: filePath, atomically: true, encoding: .utf8)

        for moduleIndex in 0..<dependencyFileCount {
            let moduleName = "\(subcomponentName)Module\(moduleIndex)"
            let providedDependencyName = "\(moduleName)ProvidedDependency"
            let moduleFileContent = makeModuleFileContent(
                moduleName: moduleName,
                componentName: subcomponentName,
                providedDependencyName: providedDependencyName
            )
            let moduleFilePath = (outputDirectoryName as NSString).appendingPathComponent("\(moduleName).swift")
            try moduleFileContent.write(toFile: moduleFilePath, atomically: true, encoding: .utf8)
        }

        for dependencyIndex in 0..<dependencyFileCount {
            let dependencyName = "\(subcomponentName)Dependency\(dependencyIndex)"
            let moduleName = "\(subcomponentName)Module\(dependencyIndex)"
            let providedDependencyName = "\(moduleName)ProvidedDependency"
            let dependencyFileContent = if dependencyIndex == 0 {
                makeDependencyFileContent(dependencyName: dependencyName, componentName: subcomponentName)
            } else {
                makeDependencyFileContent(
                    dependencyName: dependencyName,
                    componentName: subcomponentName, 
                    injectedDependencyName1: "\(subcomponentName)Dependency\(dependencyIndex-1)",
                    injectedDependencyName2: providedDependencyName
                )
            }
            let dependencyFilePath = (outputDirectoryName as NSString).appendingPathComponent("\(dependencyName).swift")
            try dependencyFileContent.write(toFile: dependencyFilePath, atomically: true, encoding: .utf8)
        }
    }

    print("Successfully generated Swift files in '\(outputDirectoryName)' folder.")
} catch {
    print("Error: \(error)")
    exit(1)
}

func makeDependencyFileContent(
    dependencyName: String,
    componentName: String,
    injectedDependencyName1: String? = nil,
    injectedDependencyName2: String? = nil
) -> String {
    if let injectedDependencyName1, let injectedDependencyName2 {
        injectableDependencyFileTemplate
            .replacingOccurrences(of: "{dependency_name}", with: dependencyName)
            .replacingOccurrences(of: "{component_name}", with: componentName)
            .replacingOccurrences(of: "{injected_dependency_name1}", with: injectedDependencyName1)
            .replacingOccurrences(of: "{injected_dependency_name2}", with: injectedDependencyName2)
    } else {
        dependencyFileTemplate
            .replacingOccurrences(of: "{dependency_name}", with: dependencyName)
            .replacingOccurrences(of: "{component_name}", with: componentName)
    }
}

func makeModuleFileContent(moduleName: String, componentName: String, providedDependencyName: String) -> String {
    moduleFileTemplate
        .replacingOccurrences(of: "{module_name}", with: moduleName)
        .replacingOccurrences(of: "{component_name}", with: componentName)
        .replacingOccurrences(of: "{provided_dependency_name}", with: providedDependencyName)
}

Measurement result

Total: 22.30 seconds

Name Duration
parseSourceFiles 954.11ms
makeComponentTree 36.67μs
makeDependencies 5.89ms
makeModules 6.64ms
makeBindingGraph 18.40s
exportBindingGraph 1.32s

@rockname rockname force-pushed the signpost branch 2 times, most recently from 444046f to 0d17705 Compare February 11, 2025 02:20
@rockname rockname mentioned this pull request Feb 15, 2025
@rockname rockname merged commit 3ccc180 into main Feb 15, 2025
3 checks passed
@rockname rockname deleted the signpost branch February 15, 2025 04:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant