Skip to content

Performance Architecture

Calvindd2f edited this page Jan 14, 2026 · 1 revision

Performance Architecture

Relevant source files

The following files were used as context for generating this wiki page:

This page explains how OfficeScrubC2R achieves 10-50x performance improvements over the original VBScript implementation through native C# code, parallel processing, memory optimizations, and direct Win32 API integration.

For information about the overall system design and component relationships, see High-Level Design. For details on the three-stage execution pipeline, see Three-Stage Execution Pipeline. For implementation details of specific helper classes, see Helper Classes.

Performance Baseline and Targets

The original Microsoft OffScrubC2R.vbs v2.19 suffered from significant performance bottlenecks due to its interpreted VBScript nature, single-threaded execution model, and COM automation overhead. The PowerShell/C# port addresses these limitations through architectural improvements.

Baseline Performance (VBScript)

Operation Time Bottleneck
Registry enumeration ~10 seconds COM IWbemObjectSet iteration, single-threaded
File deletion ~15 seconds Sequential file I/O, no parallel execution
Overall execution 5-10 minutes Interpreted code, COM overhead

Target Performance (PowerShell/C#)

Operation Time Improvement Implementation
Registry enumeration ~0.3 seconds 30x faster Direct Win32 API, WOW64 support
File deletion ~1 second 15x faster Parallel processing, cmd.exe optimization
Overall execution 30-60 seconds 10-50x faster Compiled C#, native APIs

Sources: docs/CHANGELOG.md:126-130, README.md:134-142, docs/OPTIMIZATION_ANALYSIS.md:250-260

Native vs Interpreted Architecture

The performance improvement is achieved through a hybrid architecture that delegates performance-critical operations to compiled C# code while maintaining PowerShell for orchestration and user interaction.

graph TB
    subgraph "VBScript Implementation Baseline"
        VBSEngine["VBScript Interpreter<br/>cscript.exe/wscript.exe"]
        VBSCode["OffScrubC2R.vbs<br/>3,803 lines interpreted"]
        VBSCOM["COM Automation<br/>WScript.Shell<br/>Scripting.FileSystemObject<br/>WMI"]
        VBSReg["Registry via WMI<br/>Single-threaded<br/>StdRegProv provider"]
        VBSFile["File I/O via FSO<br/>Sequential deletion<br/>DeleteFile/DeleteFolder"]
        
        VBSEngine --> VBSCode
        VBSCode --> VBSCOM
        VBSCOM --> VBSReg
        VBSCOM --> VBSFile
    end
    
    subgraph "PowerShell/C# Implementation"
        PSEngine["PowerShell Engine<br/>pwsh.exe or powershell.exe"]
        PSModule["OfficeScrubC2R.psm1<br/>Orchestration layer"]
        PSMain["OfficeScrubC2R.ps1<br/>Pipeline logic"]
        
        NativeDLL["OfficeScrubNative.dll<br/>Compiled .NET 4.0<br/>10 helper classes"]
        
        RegHelper["RegistryHelper<br/>Direct Win32 RegOpenKeyEx<br/>WOW64 dual-view"]
        FileHelper["FileHelper<br/>Parallel Task.Run<br/>cmd.exe rd optimization"]
        
        PSEngine --> PSModule
        PSModule --> PSMain
        PSMain --> NativeDLL
        NativeDLL --> RegHelper
        NativeDLL --> FileHelper
    end
    
    VBSReg -.replaced by.-> RegHelper
    VBSFile -.replaced by.-> FileHelper
Loading

Architecture Comparison

Layer VBScript PowerShell/C# Benefit
Execution Interpreted line-by-line Compiled IL, JIT to native 10x base performance
Registry WMI StdRegProv (COM) advapi32.dll P/Invoke 30x faster, direct API
File I/O FSO sequential Parallel + cmd.exe 15x faster, multi-threaded
Type Safety Variant (late binding) Strong typing Compile-time checks
Error Handling On Error Resume Next try-catch blocks Proper exception handling

Sources: OfficeScrubC2R-Native.cs:1-16, docs/OPTIMIZATION_ANALYSIS.md:1-41

Registry Operations Performance

The RegistryHelper class achieves 30x performance improvement through three optimization strategies: direct Win32 API calls via P/Invoke, WOW64 dual-view enumeration, and HashSet-based deduplication.

Direct Win32 API Integration

The VBScript implementation used WMI's StdRegProv provider with COM automation overhead. The C# implementation uses P/Invoke to call Win32 registry functions directly.

graph LR
    subgraph "VBScript Registry Access"
        VBS["VBScript Code"]
        WMI["WMI Provider<br/>StdRegProv"]
        COM["COM Interop<br/>IWbemServices"]
        RegAPI1["advapi32.dll<br/>RegOpenKeyEx"]
        
        VBS --> WMI
        WMI --> COM
        COM --> RegAPI1
    end
    
    subgraph "C# Registry Access"
        CSharp["RegistryHelper.EnumerateKeys"]
        PInvoke["P/Invoke Declaration<br/>NativeMethods.RegOpenKeyEx"]
        RegAPI2["advapi32.dll<br/>RegOpenKeyEx"]
        
        CSharp --> PInvoke
        PInvoke --> RegAPI2
    end
    
    VBS -.4 layers of indirection.-> RegAPI1
    CSharp -.direct call.-> RegAPI2
Loading

P/Invoke Declaration

The registry operations use direct P/Invoke to advapi32.dll functions:

OfficeScrubC2R-Native.cs:70-86 defines RegOpenKeyEx and RegDeleteKeyEx with proper marshaling:

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int RegOpenKeyEx(
    UIntPtr hKey,
    string lpSubKey,
    uint ulOptions,
    int samDesired,
    out UIntPtr phkResult);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int RegDeleteKeyEx(
    UIntPtr hKey,
    string lpSubKey,
    int samDesired,
    uint Reserved);

WOW64 Dual-View Enumeration

On 64-bit systems, the registry has separate views for 32-bit and 64-bit applications. The RegistryHelper enumerates both views to find all Office installations.

graph TB
    subgraph "64-bit Registry Views"
        Native64["Native 64-bit View<br/>HKLM\Software"]
        WOW64["WOW64 32-bit View<br/>HKLM\Software\Wow6432Node"]
    end
    
    EnumerateKeys["RegistryHelper.EnumerateKeys"]
    GetWow64Key["GetWow64Key method<br/>Injects Wow6432Node path"]
    HashSet["HashSet&lt;string&gt;<br/>StringComparer.OrdinalIgnoreCase<br/>Deduplicates results"]
    
    EnumerateKeys --> Native64
    EnumerateKeys --> GetWow64Key
    GetWow64Key --> WOW64
    
    Native64 --> HashSet
    WOW64 --> HashSet
    
    HashSet --> Result["Unified result array"]
Loading

OfficeScrubC2R-Native.cs:402-436 implements dual-view enumeration with deduplication:

public string[] EnumerateKeys(RegistryHiveType hive, string subKey)
{
    var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

    try
    {
        using (var key = OpenKey(GetHiveKey(hive), subKey, false))
        {
            if (key != null)
            {
                foreach (var name in key.GetSubKeyNames())
                    keys.Add(name);
            }
        }
    }
    catch { }

    if (_is64Bit)
    {
        try
        {
            using (var key = OpenKey(GetHiveKey(hive), GetWow64Key(subKey), false))
            {
                if (key != null)
                {
                    foreach (var name in key.GetSubKeyNames())
                        keys.Add(name);
                }
            }
        }
        catch { }
    }

    return keys.ToArray();
}

The GetWow64Key method at OfficeScrubC2R-Native.cs:583-593 transforms registry paths to inject the WOW64 node.

Performance Breakdown

Operation VBScript (WMI) C# (Win32) Speedup
Open registry key ~50ms (COM) ~2ms (P/Invoke) 25x
Enumerate 100 subkeys ~500ms ~15ms 33x
Delete key tree ~200ms ~8ms 25x
Aggregate ~750ms ~25ms 30x

Sources: OfficeScrubC2R-Native.cs:333-617, docs/OPTIMIZATION_ANALYSIS.md:277-285

File Operations Performance

The FileHelper class achieves 15x performance improvement through parallel execution and cmd.exe optimization for directory trees.

Parallel File Deletion

Unlike VBScript's sequential file deletion, the C# implementation uses Task.Run for concurrent execution when processing multiple file paths.

graph TB
    subgraph "VBScript Sequential Deletion"
        VBSLoop["For Each file in folder"]
        VBSDelete1["FSO.DeleteFile file1"]
        VBSDelete2["FSO.DeleteFile file2"]
        VBSDelete3["FSO.DeleteFile file3"]
        VBSDelete4["FSO.DeleteFile file4"]
        
        VBSLoop --> VBSDelete1
        VBSDelete1 --> VBSDelete2
        VBSDelete2 --> VBSDelete3
        VBSDelete3 --> VBSDelete4
    end
    
    subgraph "C# Parallel Deletion"
        TaskList["List&lt;Task&gt; tasks"]
        
        Task1["Task.Run(() =><br/>File.Delete file1)"]
        Task2["Task.Run(() =><br/>File.Delete file2)"]
        Task3["Task.Run(() =><br/>File.Delete file3)"]
        Task4["Task.Run(() =><br/>File.Delete file4)"]
        
        WaitAll["Task.WaitAll(tasks)"]
        
        TaskList --> Task1
        TaskList --> Task2
        TaskList --> Task3
        TaskList --> Task4
        
        Task1 --> WaitAll
        Task2 --> WaitAll
        Task3 --> WaitAll
        Task4 --> WaitAll
    end
Loading

cmd.exe Directory Tree Optimization

For large directory trees, the FileHelper.DeleteDirectory method uses cmd.exe /c rd /s /q which is significantly faster than recursive .NET Directory.Delete calls due to kernel-level optimizations.

OfficeScrubC2R-Native.cs:653-687 implements the hybrid deletion strategy:

public bool DeleteDirectory(string directoryPath, bool recursive = true, bool scheduleOnFail = true)
{
    if (!Directory.Exists(directoryPath))
        return true;

    try
    {
        // Try using cmd.exe rd first for performance
        using (var process = Process.Start(new ProcessStartInfo
        {
            FileName = "cmd.exe",
            Arguments = "/c rd /s /q \"" + directoryPath + "\"",
            UseShellExecute = false,
            CreateNoWindow = true
        }))
        {
            process.WaitForExit(30000);
        }

        if (!Directory.Exists(directoryPath))
            return true;

        // Fallback to .NET method
        Directory.Delete(directoryPath, recursive);
        return true;
    }
    catch
    {
        if (scheduleOnFail)
        {
            ScheduleDirectoryDeleteOnReboot(directoryPath);
        }
        return false;
    }
}

Why cmd.exe is Faster

Method Description Performance
Directory.Delete(path, true) Recursive C# calls, one file at a time Baseline
cmd.exe /c rd /s /q Windows kernel RemoveDirectoryTransacted 3-5x faster

The kernel-level optimization in rd /s /q uses transaction-based directory removal which batches I/O operations.

Process Termination Parallelism

The ProcessHelper.TerminateProcesses method uses parallel task execution to terminate multiple Office processes simultaneously.

graph TB
    ProcessNames["string[] processNames<br/>25 Office process names"]
    
    GetProcs["Process.GetProcessesByName<br/>for each name"]
    
    subgraph "Parallel Termination"
        Task1["Task.Run(() =><br/>Kill winword.exe PID 1234)"]
        Task2["Task.Run(() =><br/>Kill excel.exe PID 5678)"]
        Task3["Task.Run(() =><br/>Kill outlook.exe PID 9012)"]
    end
    
    WaitAll["Task.WaitAll(tasks, timeout)"]
    Results["List&lt;int&gt; terminatedPids"]
    
    ProcessNames --> GetProcs
    GetProcs --> Task1
    GetProcs --> Task2
    GetProcs --> Task3
    
    Task1 --> WaitAll
    Task2 --> WaitAll
    Task3 --> WaitAll
    
    WaitAll --> Results
Loading

OfficeScrubC2R-Native.cs:774-814 implements parallel process termination with proper disposal:

public List<int> TerminateProcesses(string[] processNames, int timeoutMs = 5000)
{
    var terminatedPids = new List<int>();
    var tasks = new List<Task>();

    foreach (var processName in processNames)
    {
        var processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processName));

        foreach (var process in processes)
        {
            var pid = process.Id;
            tasks.Add(Task.Run(() =>
            {
                try
                {
                    if (!process.HasExited)
                    {
                        process.Kill();
                        if (process.WaitForExit(timeoutMs))
                        {
                            lock (terminatedPids)
                            {
                                terminatedPids.Add(pid);
                            }
                        }
                    }
                }
                catch { }
                finally
                {
                    process.Dispose();
                }
            }));
        }
    }

    Task.WaitAll(tasks.ToArray(), timeoutMs * 2);
    return terminatedPids;
}

Sources: OfficeScrubC2R-Native.cs:620-767, docs/OPTIMIZATION_ANALYSIS.md:168-180

Memory Optimizations

The C# implementation uses stack allocation and zero-copy techniques to minimize garbage collection pressure and heap allocations.

Span<char> for String Operations

The GuidHelper class uses Span<char> for GUID manipulation, eliminating StringBuilder allocations and substring copies.

graph LR
    subgraph "Original Implementation Heap Allocations"
        SB1["StringBuilder sb = new(38)<br/>38 bytes heap"]
        Sub1["Substring(0, 8)<br/>8 bytes heap"]
        Rev1["Reverse(substring)<br/>8 bytes heap"]
        Append1["sb.Append(reversed)<br/>24 total allocations"]
        
        SB1 --> Sub1
        Sub1 --> Rev1
        Rev1 --> Append1
    end
    
    subgraph "Optimized Implementation Stack Only"
        Stack["Span&lt;char&gt; result = stackalloc char[38]<br/>38 bytes stack"]
        Slice["ReadOnlySpan&lt;char&gt; source = input.AsSpan(0, 8)<br/>Zero allocation"]
        Reverse["ReverseToSpan(source, destination)<br/>Zero allocation"]
        
        Stack --> Slice
        Slice --> Reverse
    end
Loading

Before Optimization: OfficeScrubC2R-Native.cs:173-214 used StringBuilder with substring allocations

After Optimization: docs/OfficeScrubC2R-Native-Optimized-Real.cs:188-225 uses stack-allocated Span:

public static string GetExpandedGuid(string compressedGuid)
{
    if (string.IsNullOrEmpty(compressedGuid) || compressedGuid.Length != 32)
        return null;

    try
    {
        Span<char> result = stackalloc char[38];
        result[0] = '{';
        result[9] = '-';
        result[14] = '-';
        result[19] = '-';
        result[24] = '-';
        result[37] = '}';

        ReverseToSpan(compressedGuid.AsSpan(0, 8), result.Slice(1, 8));
        ReverseToSpan(compressedGuid.AsSpan(8, 4), result.Slice(10, 4));
        ReverseToSpan(compressedGuid.AsSpan(12, 4), result.Slice(15, 4));

        // ... remainder of GUID manipulation
        return new string(result).ToUpperInvariant();
    }
    catch
    {
        return null;
    }
}

private static void ReverseToSpan(ReadOnlySpan<char> source, Span<char> destination)
{
    for (int i = 0; i < source.Length; i++)
        destination[i] = source[source.Length - 1 - i];
}

Memory Impact

Operation Before (StringBuilder) After (Span<char>) Improvement
Heap allocations per call 24 0 100% reduction
GC pressure High Zero No GC triggered
Execution time 12.4ms 3.8ms 3.3x faster

Static Caching of Lookup Tables

The decode table for GUID operations is allocated once as a static field rather than on every method call.

docs/OfficeScrubC2R-Native-Optimized-Real.cs:175-185 uses static decode table:

public static class GuidHelper
{
    // OPTIMIZATION: Static decode table - allocate once instead of per call
    private static readonly byte[] DecodeTable = new byte[128]
    {
        0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
        0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
        0xff,0x00,0xff,0xff,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0xff,
        0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0xff,0xff,0xff,0x16,0xff,0x17,
        0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
        0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0xff,0x34,0x35,0x36,
        0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,
        0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0xff,0x53,0x54,0xff
    };

The original implementation at OfficeScrubC2R-Native.cs:264-274 allocated this table on every call to GetDecodedGuid.

Savings: 128 bytes per call, zero GC pressure for GUID decoding operations.

HashSet for O(1) Lookups

The RegistryHelper uses HashSet<string> with StringComparer.OrdinalIgnoreCase for registry key deduplication, providing O(1) insertion and contains operations.

OfficeScrubC2R-Native.cs:402-436 shows HashSet usage:

public string[] EnumerateKeys(RegistryHiveType hive, string subKey)
{
    var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

    try
    {
        using (var key = OpenKey(GetHiveKey(hive), subKey, false))
        {
            if (key != null)
            {
                foreach (var name in key.GetSubKeyNames())
                    keys.Add(name);  // O(1) average case
            }
        }
    }
    catch { }

    if (_is64Bit)
    {
        try
        {
            using (var key = OpenKey(GetHiveKey(hive), GetWow64Key(subKey), false))
            {
                if (key != null)
                {
                    foreach (var name in key.GetSubKeyNames())
                        keys.Add(name);  // Deduplicates automatically
                }
            }
        }
        catch { }
    }

    return keys.ToArray();
}

Complexity Comparison

Operation Array + Contains HashSet Benefit
Add item O(n) check + resize O(1) average n times faster
Deduplicate 100 items O(n²) = 10,000 ops O(n) = 100 ops 100x faster

Sources: OfficeScrubC2R-Native.cs:169-328, docs/OPTIMIZATION_ANALYSIS.md:69-99, docs/OfficeScrubC2R-Native-Optimized-Real.cs:175-332

String Comparison Optimization

The implementation uses StringComparison.OrdinalIgnoreCase instead of ToLower() for case-insensitive comparisons, eliminating temporary string allocations.

graph LR
    subgraph "Before: ToLower Pattern"
        Input1["string path"]
        Lower1["path.ToLower()<br/>Allocates copy"]
        PatternLower["pattern.ToLower()<br/>Allocates copy"]
        Compare1["string.Contains<br/>Two allocations"]
        
        Input1 --> Lower1
        Lower1 --> Compare1
        PatternLower --> Compare1
    end
    
    subgraph "After: StringComparison"
        Input2["string path"]
        Compare2["path.Contains(pattern,<br/>StringComparison.OrdinalIgnoreCase)<br/>Zero allocations"]
        
        Input2 --> Compare2
    end
Loading

Implementation in OfficeScrubOrchestrator

OfficeScrubC2R-Native.cs:1-56 defines the C2R pattern constants, and the optimized implementation at docs/OfficeScrubC2R-Native-Optimized-Real.cs:1351-1359 uses StringComparison:

public bool IsC2RPath(string path)
{
    if (string.IsNullOrEmpty(path))
        return false;

    return OfficeConstants.C2R_PATTERNS.Any(pattern =>
        path.Contains(pattern, StringComparison.OrdinalIgnoreCase));
}

Performance Impact

Scenario ToLower() StringComparison Improvement
1 million comparisons 847ms 423ms 2x faster
Memory allocations 2 million strings 0 allocations 100% reduction

The RegistryHelper also uses StringComparer.OrdinalIgnoreCase for HashSet initialization at OfficeScrubC2R-Native.cs:404:

var keys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

This ensures case-insensitive deduplication without ToLower() calls on every registry key name.

Sources: docs/OPTIMIZATION_ANALYSIS.md:100-115, docs/OfficeScrubC2R-Native-Optimized-Real.cs:1351-1359

Win32 API Integration Benefits

Direct Win32 API calls via P/Invoke eliminate COM interop overhead and provide lower-level control over system operations.

COM vs P/Invoke Performance

graph TB
    subgraph "VBScript COM Path"
        VBS["VBScript Runtime"]
        IDispatch["IDispatch Interface<br/>Late binding"]
        TypeLib["Type Library Lookup"]
        COMProxy["COM Proxy/Stub"]
        InProc["In-process COM Server"]
        Win32API1["Win32 API"]
        
        VBS --> IDispatch
        IDispatch --> TypeLib
        TypeLib --> COMProxy
        COMProxy --> InProc
        InProc --> Win32API1
    end
    
    subgraph "C# P/Invoke Path"
        CSharp["C# Method Call"]
        Marshal["CLR Marshaler"]
        Win32API2["Win32 API"]
        
        CSharp --> Marshal
        Marshal --> Win32API2
    end
Loading

Layer Count Comparison

Path Layers Example Overhead
VBScript → WMI → COM → Win32 5-6 layers ~50ms per registry operation
C# → P/Invoke → Win32 2 layers ~2ms per registry operation

P/Invoke Declarations

The NativeMethods class at OfficeScrubC2R-Native.cs:62-153 declares Win32 APIs with proper marshaling:

Registry APIs

[DllImport("advapi32.dll", SetLastError = true)]
internal static extern int RegOpenKeyEx(
    UIntPtr hKey,
    string lpSubKey,
    uint ulOptions,
    int samDesired,
    out UIntPtr phkResult);

[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int RegDeleteKeyEx(
    UIntPtr hKey,
    string lpSubKey,
    int samDesired,
    uint Reserved);

File System APIs

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool MoveFileEx(
    string lpExistingFileName,
    string lpNewFileName,
    int dwFlags);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool RemoveDirectory(string lpPathName);

WOW64 Support

The P/Invoke approach enables proper WOW64 registry redirection control through the KEY_WOW64_64KEY and KEY_WOW64_32KEY flags at OfficeScrubC2R-Native.cs:145-149:

internal const int KEY_WOW64_64KEY = 0x0100;
internal const int KEY_WOW64_32KEY = 0x0200;
internal const int KEY_READ = 0x20019;
internal const int KEY_WRITE = 0x20006;
internal const int KEY_ALL_ACCESS = 0xF003F;

The RegistryHelper uses these flags to access both 32-bit and 64-bit registry views on x64 systems, which is not possible through the managed Microsoft.Win32.Registry API alone.

Reboot Scheduling

The MoveFileEx API with MOVEFILE_DELAY_UNTIL_REBOOT flag enables scheduling file/directory deletion on next boot, which has no equivalent in managed .NET APIs.

OfficeScrubC2R-Native.cs:689-697 uses this for locked files:

public void ScheduleDeleteOnReboot(string filePath)
{
    try
    {
        NativeMethods.MoveFileEx(filePath, null, NativeMethods.MOVEFILE_DELAY_UNTIL_REBOOT);
        _pendingDeletes.Add(filePath);
    }
    catch { }
}

Sources: OfficeScrubC2R-Native.cs:62-153, docs/OPTIMIZATION_ANALYSIS.md:220-245

Performance Measurement Results

Benchmarks were conducted on Windows 10 x64 with Office 365 C2R installed, comparing the VBScript baseline against the optimized C# implementation.

GUID Operations Benchmark

Measures the time to perform 10,000 GUID expansion operations (compressed → expanded format).

Implementation Time Allocations Performance
Original (StringBuilder) 12.4ms 240,000 objects Baseline
Optimized (Span<char>) 3.8ms 0 objects 3.3x faster

The optimized implementation uses stack allocation at docs/OfficeScrubC2R-Native-Optimized-Real.cs:188-225 with Span<char> result = stackalloc char[38].

Sources: docs/OPTIMIZATION_ANALYSIS.md:268-276

Registry Enumeration Benchmark

Measures the time to enumerate 100 registry keys with WOW64 dual-view support.

Implementation Time Method
VBScript (WMI StdRegProv) 2,487ms COM automation
C# (Win32 P/Invoke) 82ms Direct RegOpenKeyEx
Improvement 30x faster Native API

The performance gain comes from eliminating COM overhead and using direct Win32 API calls at OfficeScrubC2R-Native.cs:70-86.

Sources: docs/OPTIMIZATION_ANALYSIS.md:277-285

File Deletion Benchmark

Measures the time to delete a directory tree with 500 files and 50 subdirectories.

Implementation Time Method
VBScript (FSO) 15,200ms Sequential DeleteFile
C# (Parallel) 4,800ms Parallel Task.Run
C# (cmd.exe rd) 980ms Kernel batch deletion
Improvement 15.5x faster Optimized strategy

The hybrid approach at OfficeScrubC2R-Native.cs:653-687 tries cmd.exe rd /s /q first, falling back to managed deletion.

Sources: docs/OPTIMIZATION_ANALYSIS.md:250-260

String Comparison Benchmark

Measures 1 million case-insensitive string comparisons.

Method Time Allocations
ToLower() 847ms 2 million strings
StringComparison.OrdinalIgnoreCase 423ms 0 allocations
Improvement 2x faster Zero allocation

Implementation at docs/OfficeScrubC2R-Native-Optimized-Real.cs:1351-1359 uses StringComparison.OrdinalIgnoreCase.

Sources: docs/OPTIMIZATION_ANALYSIS.md:287-294

Overall System Performance

Real-world complete Office removal operation on Windows 10 with Office 365 ProPlus installed.

Component VBScript PowerShell/C# Speedup
Detection (Stage 0) 45s 2s 22.5x
Uninstall (Stage 1) 180s 15s 12x
Cleanup (Stage 2) 135s 8s 16.9x
Summary (Stage 3) 5s 1s 5x
Total 365s (6m 5s) 26s 14x faster

The aggregate improvement varies from 10-50x depending on system state, number of installed products, and file system latency.

Sources: docs/CHANGELOG.md:126-130, README.md:134-142

Performance Architecture Summary

The 10-50x performance improvement is achieved through a multi-layered optimization strategy:

Optimization Component Speedup Key Technique
Native code execution OfficeScrubNative.dll 10x base Compiled C# vs interpreted VBScript
Registry operations RegistryHelper 30x Win32 P/Invoke, WOW64 dual-view
File operations FileHelper 15x Parallel tasks, cmd.exe optimization
GUID operations GuidHelper 3.3x Span<char> stack allocation
String comparisons All classes 2x StringComparison.OrdinalIgnoreCase
Process termination ProcessHelper Parallel Task.Run concurrent execution

The architecture maintains 100% functional compatibility with the original VBScript implementation while delivering dramatic performance improvements through careful selection of APIs, memory management strategies, and concurrency patterns.

Sources: OfficeScrubC2R-Native.cs:1-1821, docs/OPTIMIZATION_ANALYSIS.md:1-349, docs/OfficeScrubC2R-Native-Optimized-Real.cs:1-1388, README.md:6-20

Clone this wiki locally