Skip to content

Performance Issue: Windows vs Mac Performance Discrepancy #6414

@mhmostafa88

Description

@mhmostafa88

Collected performance data showing Windows (Microsoft Edge) is ~93% slower than Mac (Safari) for Office.js operations, even with undo stack clearing optimizations.

Your Environment

  • Platform [PC desktop, Mac, iOS, Office on the web]: PC desktop (Windows) and Mac desktop
  • Host [Excel, Word, PowerPoint, etc.]: Excel
  • Office version number: [To be detected automatically in Script Lab test - see Link to live example]
  • Operating System: Windows (Microsoft Edge) and macOS (Safari)
  • Browser (if using Office on the web): Microsoft Edge (Windows), Safari (Mac)

Expected behavior

Office.js operations should perform similarly across platforms (Windows vs Mac). With undo stack clearing optimizations implemented per Microsoft documentation, performance should be optimized on both platforms.

Current behavior

Windows (Microsoft Edge) shows ~93% slower performance compared to Mac (Safari) for identical Office.js operations, even with undo stack clearing optimizations applied. This performance gap persists across all measured operations.

Performance Results (100 iterations each):

Mac (Safari):

  • Average: 154.16 ms
  • Minimum: 121.00 ms
  • Maximum: 350.00 ms
  • Std Dev: 33.86 ms

Windows (Microsoft Edge):

  • Average: 297.17 ms (92.8% slower than Mac)
  • Minimum: 226.80 ms (87.4% slower than Mac)
  • Maximum: 598.10 ms (70.9% slower than Mac)
  • Std Dev: 48.31 ms (42.7% higher variance)

Steps to reproduce

  1. Open Script Lab in Excel on Windows (Microsoft Edge)
  2. Load the provided Script Lab test (see Link to live example)
  3. Click "Run Test" to execute 100 iterations with undo stack clearing
  4. Note the average, minimum, and maximum performance metrics
  5. Repeat the same test on Mac (Safari)
  6. Compare the performance results between platforms

Link to live example(s)

  1. Script Lab test file:
    • Test automatically detects platform (Windows/Mac)
    • Runs 100 iterations with NamedItems undo stack clearing method
    • Displays performance statistics (min, max, average, std dev)
    • Includes Office version detection

Copy the YAML file below and import it into script lab

name: Undo Stack Clearing Performance Test (NamedItems Method)
description: Tests performance of NamedItems undo stack clearing method - 100 iterations
host: EXCEL
api_set: {}
script:
  content: |
    /**
     * Undo Stack Clearing Performance Test - NamedItems Method
     *
     * Runs 100 iterations with undo stack clearing using NamedItems method
     * and automatically displays min, max, and average performance.
     *
     * Reference: https://learn.microsoft.com/en-us/office/dev/add-ins/excel/excel-add-ins-undo-capabilities
     */

    $("#run-test").on("click", () => tryCatch(runTest));

    interface TestResult {
      duration: number;
      platform: string;
    }

    const results: TestResult[] = [];

    // ============================================================
    // Undo Stack Clearing Method (Best Performing)
    // ============================================================

    /**
     * NamedItems Method - Best performing method per Script Lab tests
     */
    async function clearUndoStackNames(context: Excel.RequestContext): Promise<void> {
      const namedItems = context.workbook.names;
      namedItems.load(['count']);
      await context.sync();
    }

    // ============================================================
    // Test Operations (Common workload)
    // ============================================================

    async function performTestOperations(context: Excel.RequestContext): Promise<void> {
      const sheet = context.workbook.worksheets.getItem("UndoTest");
      
      // Clear any existing data
      const usedRange = sheet.getUsedRangeOrNullObject();
      usedRange.load("isNullObject");
      await context.sync();
      if (!usedRange.isNullObject) {
        usedRange.clear();
      }
      
      sheet.activate();
      await context.sync();

      // Operation 1: Write 1000 rows of data
      const data = [];
      for (let i = 0; i < 1000; i++) {
        data.push([`Row ${i}`, Math.random() * 1000, new Date().toISOString(), `=B${i+1}*2`]);
      }
      const writeRange = sheet.getRange("A1").getResizedRange(999, 3);
      writeRange.values = data;
      await context.sync();

      // Operation 2: Read values back
      writeRange.load("values");
      await context.sync();

      // Operation 3: Apply formatting
      writeRange.format.font.name = "Calibri";
      writeRange.format.font.size = 11;
      sheet.getRange("A1:D1").format.font.bold = true;
      await context.sync();

      // Operation 4: Create named range
      const namedRangeName = `TestRange_${Date.now()}`;
      try {
        context.workbook.names.add(namedRangeName, writeRange);
        await context.sync();
        // Clean up
        context.workbook.names.getItem(namedRangeName).delete();
        await context.sync();
      } catch (e) {
        // Ignore if fails
      }

      // Operation 5: Calculate
      context.application.calculate(Excel.CalculationType.full);
      await context.sync();
    }

    // ============================================================
    // Test Function
    // ============================================================

    async function runTest() {
      const platform = getPlatform();
      const iterations = 100;
      
      log(`🚀 Starting performance test (${iterations} iterations on ${platform})...`);
      log("=".repeat(60));
      log("");

      results.length = 0; // Clear previous results

      for (let i = 0; i < iterations; i++) {
        await Excel.run(async (context) => {
          const start = performance.now();

          // Clear undo stack first using NamedItems method
          await clearUndoStackNames(context);

          // Then perform operations
          await performTestOperations(context);

          const duration = performance.now() - start;
          results.push({ 
            duration, 
            platform: platform
          });

          // Progress indicator every 10 iterations
          if ((i + 1) % 10 === 0) {
            log(`Progress: ${i + 1}/${iterations} iterations completed...`);
          }
        });

        // Brief pause between iterations
        await new Promise(r => setTimeout(r, 50));
      }

      log("");
      log("=".repeat(60));
      log("✅ Test complete! Calculating statistics...");
      log("");

      // Calculate and display statistics automatically
      displayStatistics();
    }

    function getPlatform(): string {
      // Detect platform from user agent
      const ua = navigator.userAgent || "";
      if (ua.includes("Windows")) return "Windows";
      if (ua.includes("Mac")) return "Mac";
      return "Unknown";
    }

    function displayStatistics() {
      if (results.length === 0) {
        log("⚠️ No results to display.");
        return;
      }

      const durations = results.map(r => r.duration);
      const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
      const min = Math.min(...durations);
      const max = Math.max(...durations);
      const platform = results[0].platform;

      // Calculate standard deviation
      const variance = durations.reduce((sum, d) => sum + Math.pow(d - avg, 2), 0) / durations.length;
      const stdDev = Math.sqrt(variance);

      log("📊 PERFORMANCE STATISTICS");
      log("=".repeat(60));
      log(`Platform: ${platform}`);
      log(`Iterations: ${results.length}`);
      log("");
      log(`Average: ${avg.toFixed(2)} ms`);
      log(`Minimum: ${min.toFixed(2)} ms`);
      log(`Maximum: ${max.toFixed(2)} ms`);
      log(`Std Dev: ${stdDev.toFixed(2)} ms`);
      log("");
      log("=".repeat(60));
    }

    function log(message: string) {
      console.log(message);
      const output = document.getElementById("output");
      const timestamp = new Date().toLocaleTimeString();
      output.innerHTML += `[${timestamp}] ${message}\n`;
      output.scrollTop = output.scrollHeight;
    }

    async function ensureTestSheetExists(context: Excel.RequestContext): Promise<void> {
      const sheet = context.workbook.worksheets.getItemOrNullObject("UndoTest");
      sheet.load("isNullObject");
      await context.sync();

      if (sheet.isNullObject) {
        context.workbook.worksheets.add("UndoTest");
        await context.sync();
      }
    }

    async function tryCatch(callback: () => Promise<void>) {
      try {
        await callback();
      } catch (error) {
        log(`❌ Error: ${error.message}`);
        console.error(error);
      }
    }

    // Initialize test sheet when script loads
    Office.onReady(async () => {
      await Excel.run(ensureTestSheetExists);
      const platform = getPlatform();
      log(`✅ Test sheet ready (Platform: ${platform})`);
      log(`Click "Run Test" to start 100 iterations with undo stack clearing`);
    })
  language: typescript
template:
  content: |
    <div class="container">
      <h2>Undo Stack Clearing Performance Test</h2>
      <p>NamedItems Method - 100 Iterations</p>
      <p id="platform-info" style="color: #666; font-size: 12px;"></p>

      <div class="button-group">
        <button id="run-test" class="ms-Button ms-Button--primary">
          <span class="ms-Button-label">🚀 Run Test (100 iterations)</span>
        </button>
      </div>

      <h3>Output:</h3>
      <pre id="output"></pre>
    </div>

    <script>
      // Detect and display platform
      window.addEventListener('DOMContentLoaded', () => {
        const ua = navigator.userAgent || "";
        let platform = "Unknown";
        if (ua.includes("Windows")) platform = "Windows";
        else if (ua.includes("Mac")) platform = "Mac";
        
        const platformInfo = document.getElementById("platform-info");
        if (platformInfo) {
          platformInfo.textContent = `Running on: ${platform}`;
        }
      });
    </script>
  language: html
style:
  content: |
    .container { padding: 10px; font-family: 'Segoe UI', sans-serif; }
    h2 { color: #217346; margin-bottom: 5px; }
    h3 { margin-top: 15px; margin-bottom: 5px; }
    p { color: #666; margin-bottom: 15px; }
    .button-group { margin-bottom: 10px; }
    .ms-Button { margin: 3px; padding: 8px 12px; }
    .ms-Button--primary { background: #217346; color: white; }
    #output {
      background: #1e1e1e;
      color: #d4d4d4;
      padding: 10px;
      height: 400px;
      overflow-y: auto;
      font-size: 11px;
      white-space: pre-wrap;
      font-family: 'Consolas', monospace;
      border-radius: 4px;
    }
  language: css
libraries: |
  https://appsforoffice.microsoft.com/lib/1/hosted/office.js
  @types/office-js
  office-ui-fabric-js@1.4.0/dist/css/fabric.min.css
  office-ui-fabric-js@1.4.0/dist/css/fabric.components.min.css
  core-js@2.4.1/client/core.min.js
  @types/core-js
  jquery@3.6.0
  @types/jquery@3.5.1

Provide additional details

  1. Test Methodology:

    • Uses NamedItems undo stack clearing method (workbook.names.load(['count'])) - identified as best performing method through testing
    • Each iteration performs: writing 1,000 rows, reading values, applying formatting, creating/deleting named ranges, and full calculation
    • Undo stack is cleared before each iteration to ensure consistent baseline
    • 100 iterations per platform for statistical significance
  2. Performance Gap Analysis:

    • Windows average is 1.93x slower than Mac (297.17 ms vs 154.16 ms)
    • Windows shows higher variance (std dev 48.31 ms vs 33.86 ms), indicating inconsistent performance
    • Performance gap exists across all metrics (min, max, average)
  3. Related Issue:

Context

This performance discrepancy significantly impacts user experience for Windows users of our Excel add-in. All Office.js operations (data insertion, formatting, named range management, calculations) are nearly 2x slower on Windows compared to Mac, leading to:

  • Slower perceived responsiveness
  • User complaints about performance
  • Reduced productivity for Windows users
  • Competitive disadvantage vs Mac users

We've implemented Microsoft-recommended optimizations (undo stack clearing) but the platform gap remains. This suggests a deeper platform-specific performance issue that needs investigation.

Useful logs

  • Console errors: None observed - operations complete successfully, just slower
  • Screenshots: Performance statistics output from Script Lab test
  • Test file: Script Lab test file provided above

Performance Data:

Platform: Mac
Excel Version: 16.98.608.0
Office.js API Version: 1.18
Iterations: 100
Average: 154.16 ms
Minimum: 121.00 ms
Maximum: 350.00 ms
Std Dev: 33.86 ms

WINDOWS USER # 1
Platform: Windows
Excel Version: 16.0.19426.20218
Office.js API Version: 1.18
Iterations: 100
Average: 297.17 ms
Minimum: 226.80 ms
Maximum: 598.10 ms
Std Dev: 48.31 ms

WINDOWS USER # 2
Platform: Windows
Excel Version: 16.0.19426.20218
Office.js API Version: 1.18
Iterations: 100
Average: 328.76 ms
Minimum: 249.10 ms
Maximum: 1025.20 ms
Std Dev: 78.90 ms

This is related to a previous issue i reported earlier here #5993

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions