-
Notifications
You must be signed in to change notification settings - Fork 116
Description
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
- Open Script Lab in Excel on Windows (Microsoft Edge)
- Load the provided Script Lab test (see Link to live example)
- Click "Run Test" to execute 100 iterations with undo stack clearing
- Note the average, minimum, and maximum performance metrics
- Repeat the same test on Mac (Safari)
- Compare the performance results between platforms
Link to live example(s)
- 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
-
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
- Uses NamedItems undo stack clearing method (
-
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)
-
Related Issue:
- This follows up on issue Excel Add-In is significantly slower when running on Microsoft Edge #5993 where we initially reported Windows/Edge performance issues
- User confirmed WebView2 is installed on Windows system
- Performance gap persists despite undo stack clearing optimizations
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