OfficeDev / office-js

A repo and NPM package for Office.js, corresponding to a copy of what gets published to the official "evergreen" Office.js CDN, at https://appsforoffice.microsoft.com/lib/1/hosted/office.js.
https://learn.microsoft.com/javascript/api/overview
Other
660 stars 94 forks source link

Word API calls that are near-instant on Windows/Office 365 take longer on Mac #3982

Open smketterercr opened 6 months ago

smketterercr commented 6 months ago

Word API calls that are near-instant on Windows/Office 365 take longer on Mac

We are having performance issues on a browser extension on Word for Mac where no problems exist on Windows (or on Office 365).

We have looked into reducing the count of context.sync calls per the documentation. Each individual operation appears to take a few seconds, which is too slow when you need to do multiple operations that depend on multiple items. This leads to long delays when you are performing a lot of operations on content controls, as an example.

In our Word add-in, once you hit the button to run the function you can see it working, slowly going through and selecting the content controls and filling out the information. The end-to-end process takes about 15-25s to complete, even with attempts at preloading. I'd expect it to take a couple seconds at most, as it does on Windows.

See this Stack Overflow question for another user who is trying to do a similarly heavy operation.

Beyond context.sync, other API calls seem to have a noticeable delay on Mac as well. Getting and setting custom XML and the selectionChanged events appear to take longer on Mac.

Below is an example of a Word.run call which take a while to complete. I have included similar code in the sample repro that can be run through Script Lab. A couple questions:

// Sync list of content controls
await Word.run(async (context) => {
    const contentControls = context.document.contentControls;
    contentControls.load(["id", "tag", "items", "fields"]);
    await context.sync();

    if (contentControls.items.length === 0) {
        console.log("There aren't any content controls in this document so can't register event handlers.");
    } else {
        for (let control of contentControls.items) {
            control.load("tag");
        }
        await context.sync();
        let fields = new Word.FieldCollection();
        for (let control of contentControls.items) {
            try {
                const tag = control.tag ? JSON.parse(decode(control.tag)) : null;
                if (tag) {
                    controlData.push({ ...tag, controlId: control.id });
                } else {
                    fields = control.getRange().fields.load("items");
                }
            } catch (err) {
                console.error(err);
            }
        }
        await context.sync();
        if (fields) {
            for (const field of fields.items) {
                field.load("code");
            }
            await context.sync();
            for (let control of contentControls.items) {
                try {
                    for (const field of fields.items) {
                        const someContentControlCode = field.code.split("CC_CODE ")[1];
                        if (someContentControlCode) {
                            // do stuff
                        }
                    }
                } catch (err) {
                    console.error(err);
                }
            }
        }

        // do stuff
    }
});

Your Environment

Expected behavior

Office.js API calls on Word for Mac should take about as long as they take on other clients.

Current behavior

Office.js API calls on Word for Mac are substantially slower than other clients.

On the live example:

While a 800ms delay isn't huge, in a multi-step process the delays add up.

Link to live example(s)

https://gist.github.com/smketterercr/3099e9354832e1cdd03884fe85e9d967

Provide additional details

Context

We are developing a Word add-in that manipulates citations and footnotes. It works well on Windows but the performance on Mac is too slow for our purposes. Assuming we will need multiple context.sync calls for fetching nested items as well as performing operations on content controls it's unclear if we can optimize this to the point of having acceptable performance (i.e. getting the end-to-end process down to a few seconds).

xuruiyao-msft commented 6 months ago

@smketterercr Thanks for reaching out to us. Some suggestions I can think of to improve performance are as follows:

1. Check the configuration of Win32 and Mac machine, is there a relatively big gap?

2. Optimize your code: Each sync is an extra round-trip to the host application; and when that application is Office Online, the cost of each of those round-trip adds up quickly.

I'm trying to reduce the await context.sync(), about how to acquire the properties of nested items, refer to OfficeExtension. LoadOption interface grammar](https://learn.microsoft.com/en-us/javascript/api/word/word.contentcontrolcollection?view=word-js-preview#word-word-conte ntcontrolcollection-load-member(3))

Here's an example I revise based on your code repo: optimized code link

I did a test on dev local machine, and the performance comparison before and after code optimization is as follows:

Hey, that took 164.10000000149012ms (before optimization)
Hey, that took 22.69999999552965ms (optimized)

3. In addition, I noticed that there are many two-layer for loops, and the time complexity is O(n^2). Try to avoid using multi-layer for loops

let fields;
for (let control of contentControls.items) {
try {
fields = control.getRange().fields.load("items");
} catch (err) {
console.error(err);
}
}

This code iterates through all the contentControls, but only uses the last contentControl's fields, which is equivalent to the following code. Time complexity comparison: O(n) vs O(1). let fields = contentControls.items[contentControls.items.length - 1].fields;

4. About function async function assemble(), there are a number of unnecessary calls await context.sync(). I optimized the code with some context.sync() commented off, and tested on dev local machine with the following comparisons:

(Performance before optimization)
The Call took 1228.7000000029802ms**
ContentControlSelectionChanged event detected after 2316.2000000029802 ms
[1074397979]
ContentControlSelectionChanged event detected after 2316.7000000029802 ms
[224063665]
0: -224063665
ContentControlSelectionChanged event detected after 2316.89999999851 ms
[888838870]

(Optimized performance)
The Call took 1.3999999985098839ms**
ContentControlSelectionChanged event detected after 2684ms
[1333175519]
ContentControlSelectionChanged event detected after 2684.5 ms
[1154725558]
ContentControlSelectionChanged event detected after 2684.6999999955297 ms
[478157112]

About the context.sync(), queues up a command to load the specified properties of the object. You must call context.sync() before reading the properties. Each sync is an extra round-trip to the host application, so it's not necessary to call it for un-getProperty method.

Thanks again for reaching out to us. If you have any further question, feel free to contact us.

peter-citeright commented 6 months ago

@xuruiyao-msft thanks for the response. I'm a member of @smketterercr 's team. We appreciate the effort put in for helping us optimize our code. While this will help a ton it still doesn't address the underlying issue of why Mac (with similar specs to Windows) is noticeably unperformant comared to Windows. With the code posted above it's instant on Windows, but on Mac it takes a long time to execute. This is consistent across different Macs.

Is there any plans to look into the performance of Macs in general?

Note: It's not just us that seem to have issues on Mac. Here are some other issues that have had or have issues with Mac specifically:

xuruiyao-msft commented 6 months ago

@peter-citeright Thanks for contacting us. I've created the work item (8653419) to track this issue. It's possible that the issue exists for a long time.

This thread only talks about the Word related issues. I notice these are about Custom Function of Excel and Outlook: https://github.com/OfficeDev/office-js/issues/2874 https://github.com/OfficeDev/office-js/issues/2492 https://github.com/OfficeDev/office-js/issues/2666

Would you please update your findings and discuss on the upper thread separately? Thanks again for reaching out to us. If there's any progress, we'll sync with you.

peter-citeright commented 6 months ago

This thread only talks about the Word related issues. I notice these are about Custom Function of Excel and Outlook

@xuruiyao-msft It seems to be an OfficeJS problem in general not just Word. For some reason OfficeJS is just slow on Macs. More evidence:

Is there any way for us to keep track of the work item? It's crucial that Mac is performant for us so we would like to keep tabs on the progress.

Would you please update your findings and discuss on the upper thread separately?

Any call to context.sync is extremely slow on Mac comparing to Windows. If you run the same code on Windows vs Mac you will see the difference in execution time.

smketterercr commented 6 months ago

@xuruiyao-msft Thank you for those suggestions! I wasn't aware you could load nested items like that, that will be useful!

  1. Check the configuration of Win32 and Mac machine, is there a relatively big gap?

Unfortunately I don't have a Win32 machine to test on... Just curious, is there some sort of compatibility mode that's being used on both Win32 and Mac machines? That might explain some of the differences we're seeing...

  1. Optimize your code

I ran your example in Script Lab in Word for Mac and the optimizations brought the execution time from 300ms down to 77ms for the optimized code.

In Office 365 the non-optimized (original) code takes 35ms and the optimized code takes about 10ms to complete. So the optimized code on Word for Mac is actually still slower than the non-optimized code in Office 365.

The same Office.js calls seem 7-10x slower on Mac than Windows and Office 365... We're testing on machines with similar hardware. There is also an 800ms delay on Mac from when the content control selection event fires, which is adding to the overall perception of latency.

I think we can optimize the process down to a few seconds (potentially longer on larger documents), but no matter what we do it will still be considerably slower than on Windows and Office 365. We'll have to do this to be able to support Mac, which is important to us.

If there's anything else we can look at that might be affecting Mac performance, we're open to hearing about it! We are willing to dive pretty deep if necessary to get something workable here.

xuruiyao-msft commented 6 months ago

@smketterercr @peter-citeright Thanks for reporting this issue to us. I see the performance gap between Mac and Windows. The work item(8653419) is created to track the issue on Word side. Would you please open other request for the performance of Outlook/Excel and other product so that we can track them separately? I'm afraid this could be a general issue not just for word. Besides, I'm afraid that the work item(8653419) is created internally and cannot open to public. If there's any progress, we'll sync with you. Thanks again for reaching out to us.