Window Creation/Setup is Slow (Issue #26318)

60 views
Skip to first unread message

Ryan Ogurek

unread,
Mar 26, 2026, 11:53:26 AM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
ryancog created an issue (wxWidgets/wxWidgets#26318)

This is something I've observed on wxOSX and wxMSW: creating a substantial number of controls starts to take a non-trivial amount of time pretty quickly.

I've recently been reviewing it as I'm doing GUI work on my application, and decided to look a bit deeper.

With ~70 windows (all simple, wxStaticText, wxSpinCtrl(Double), wxButton, wxCheckBox, wxStaticLine, wxStaticBox, and a couple wxPanels) it takes between 300-350ms to build and display the UI on macOS. That is, create all the windows inside of a wxFrame and lay them out.

Of course, with 70 windows I wouldn't consider it a terribly complicated setup. Control-heavy, maybe, but not completely exceptional, and it's enough of a hitch that it's noticeable when using the application. This is with wxWidgets built for debug, my application built for release. Building wx takes a little while on my macOS machine and I don't have a great setup for profiling/debugging on Windows since I cross-compile, so forgive me for not having a whole bunch of configurations.

Seems to be fairly similar on Windows, somewhere in that 350ms range. These aren't the same computer though, so that's not a great comparison...

Interestingly, on wxGTK things are nice and snappy and there's no tangible delay.

Based on what I've found I think the Windows stuff is its own thing, and with the tools I've setup/am semi-familiar with, I'm focusing on macOS for now:

Of the 350ms, ~70% of it is spent in my application's UI build() function (which comprises a 50% (of total) chunk to build/create the windows, 17% chunk to do layout, and 4% to destroy the old UI that was there), but a very large 26% of the time was spent in CA::Transaction::commit() (which calls wxOSX_drawRect).

I noticed that SetSize(), GetSize() and friends seem to be pretty expensive. SetInitialSize() is called in wxWindowMac::SetPeer(), SetInitialSize() and and Move() is called in wxSpinCtrlGenericBase::Create, and SetInitialSize() appears in a few other Creates, as I'm sure y'all know.

Removing those calls (just in SetPeer() and the spin ctrl create) dropped the time by ~100ms, to ~230ms. Obviously this isn't a solution, but I don't use initial sizes, so I figured I'd see.

However, looking in the profile trace for the actual culprit:
SetSize() calls DoSetSize() which calls DoMoveWindow(), GetSize(), and GetPosition(). From there, DoGetSize(), MacInvalidateBorders(), MacGet[Dir]Border(), and SendSizeEvent() (which calls also calls GetSize(). All of the aforementioned result in calls to CUICopyMeasurements, and it's inside there that basically all the actual time is being spent. I'm not savvy with Instruments to get an actual number, but looking through, that function (CUICopyMeasurements) is where most of the time is spent in the whole 350ms.

Even in the CA::Transaction::commit(), most of the flame's edges are covered in CUICopyMeasurements rather than any of the actual work.

Most (All of?) these calls to CUICopyMeasurements come from wxWidgetCocoaImpl::GetLayoutInset():

void wxWidgetCocoaImpl::GetLayoutInset(int &left , int &top , int &right, int &bottom) const
{
    NSEdgeInsets insets = [m_osxView alignmentRectInsets];
    left = insets.left;
    top = insets.top;
    right = insets.right;
    bottom = insets.bottom;
}

So it seems alignmentRectInsets is the actual culprit, as far as API use is concerned, and the aforementioned wxWindowMac::MacGet[Dir]BorderSize() is what calls GetLayoutInset().

So, I make GetLayoutInset() always return 0 and bypass the alignmentRectInsets call (and restored the SetInitialSize() I removed earlier.
That (absolutely breaks all the UI spacing, to no surprise, but) brings things down to ~130ms...

I still see CUICopyMeasurements in a few places, looking like it's still at least 5% of total CPU time, and I'm sure there's other stuff going on (looks like -[NSView frame] is another culprit), but that's a remarkable improvement I think, on the surface.

So then the question is:

Can that be cached? AppKit documentation describes alignmentRectInsets as the inset from "ornamentation", I'm not sure if it can be known if the size of that ornamentation would change (my naive assumption/though would be that it doesn't actually change for native controls, thinking outer shadows and stuff), and only fetch it in that case.

I'll end off my thoughts/findings here for now, and leave this issue with what I may find and for any comments on it.


Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318@github.com>

oneeyeman1

unread,
Mar 26, 2026, 2:32:15 PM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
oneeyeman1 left a comment (wxWidgets/wxWidgets#26318)

@ryancog ,
Can you post the code you used for testing, including the calls to get the time?

Thx.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4137298160@github.com>

Ryan Ogurek

unread,
Mar 26, 2026, 3:41:56 PM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

@oneeyeman1 My testing wasn't very scientific. I was just testing on my own application, switching tabs.

The timing was based off the windowing in Instruments, like so:

CleanShot.2026-0...@2x.png (view on web)

I did notice Instruments adds some overhead, but unless it was in high sample mode, it seemed minimal enough for what I was looking for.

My code has a fair bit of abstraction from normal wxWidgets stuff, but the profiling shows its not where the slow downs are, so I should be able to put together something to reproduce this w/o any abstractions.

I can put something together shortly.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4137731474@github.com>

Ryan Ogurek

unread,
Mar 26, 2026, 4:43:28 PM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

Here's a sample that creates a layout very similar to the panel I was testing with:

// wxWidgets Lots(?) o' Controls

#include <chrono>
#include <iostream>

#include <wx/app.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/event.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/spinctrl.h>
#include <wx/statbox.h>
#include <wx/stattext.h>
#include <wx/statline.h>

class Frame : public wxFrame {
public:
    Frame();

private:
    void rebuild();

    wxPanel *mPanel;
    wxSizer *mInnerSizer;
};

class Sample : public wxApp {
public:
    bool OnInit() override {
        auto *frame{new Frame};
        frame->Show();
        return true;
    }
};

// NOLINTNEXTLINE(misc-use-internal-linkage)
wxIMPLEMENT_APP(Sample);

Frame::Frame() : wxFrame(nullptr, wxID_ANY, "Sample") {
    mPanel = new wxPanel(this);
    auto *sizer{new wxBoxSizer(wxVERTICAL)};

    auto *button{new wxButton(mPanel, wxID_ANY, "Rebuild")};
    button->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { rebuild(); });
    sizer->Add(button, 0, wxEXPAND | wxALL, 10);

    mInnerSizer = new wxBoxSizer(wxVERTICAL);
    sizer->Add(mInnerSizer, 1, wxEXPAND | wxALL, 10);

    mPanel->SetSizer(sizer);

    rebuild();
}

void Frame::rebuild() {
    auto timeStart{std::chrono::steady_clock::now()};

    mInnerSizer->Clear(true);

    auto timeCleared{std::chrono::steady_clock::now()};

    auto* root = new wxBoxSizer(wxHORIZONTAL);

    auto* leftCol = new wxBoxSizer(wxVERTICAL);

    auto* group1 = new wxStaticBoxSizer(wxHORIZONTAL, mPanel, "Group 1");

    auto* leftStack = new wxBoxSizer(wxVERTICAL);
    leftStack->Add(new wxChoice(group1->GetStaticBox(), wxID_ANY), 0, wxEXPAND);
    leftStack->AddSpacer(5);
    leftStack->Add(new wxChoice(group1->GetStaticBox(), wxID_ANY), 0, wxEXPAND);

    group1->Add(leftStack, 1, wxEXPAND);
    group1->AddSpacer(10);
    group1->Add(new wxCheckBox(group1->GetStaticBox(), wxID_ANY, "Check 1"), 0, wxALIGN_CENTER_VERTICAL);
    group1->AddSpacer(10);
    group1->Add(new wxCheckBox(group1->GetStaticBox(), wxID_ANY, "Check 2"), 0, wxALIGN_CENTER_VERTICAL);

    leftCol->Add(group1, 0, wxEXPAND);

    leftCol->AddSpacer(10);

    auto* row = new wxBoxSizer(wxHORIZONTAL);

    auto* group2 = new wxStaticBoxSizer(wxVERTICAL, mPanel, "Group 2");

    auto* r21 = new wxBoxSizer(wxHORIZONTAL);
    r21->Add(new wxStaticText(group2->GetStaticBox(), wxID_ANY, "Label 1"), 0, wxALIGN_CENTER_VERTICAL);
    r21->AddSpacer(5);
    r21->Add(new wxSpinCtrl(group2->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group2->Add(r21, 0, wxEXPAND);

    group2->AddSpacer(5);
    group2->Add(new wxStaticLine(group2->GetStaticBox()), 0, wxEXPAND);
    group2->AddSpacer(5);

    auto* r22 = new wxBoxSizer(wxHORIZONTAL);
    r22->Add(new wxStaticText(group2->GetStaticBox(), wxID_ANY, "Label 2"), 0, wxALIGN_CENTER_VERTICAL);
    r22->AddSpacer(5);
    r22->Add(new wxChoice(group2->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group2->Add(r22, 0, wxEXPAND);

    group2->AddSpacer(5);

    auto* xyz = new wxBoxSizer(wxHORIZONTAL);
    xyz->Add(new wxSpinCtrl(group2->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    xyz->AddSpacer(5);
    xyz->Add(new wxSpinCtrl(group2->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    xyz->AddSpacer(5);
    xyz->Add(new wxSpinCtrl(group2->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group2->Add(xyz, 0, wxEXPAND);

    group2->AddSpacer(5);
    group2->Add(new wxStaticLine(group2->GetStaticBox()), 0, wxEXPAND);
    group2->AddSpacer(5);

    group2->Add(new wxCheckBox(group2->GetStaticBox(), wxID_ANY, "Check 3"), 0);
    group2->AddSpacer(5);

    group2->Add(new wxStaticLine(group2->GetStaticBox()), 0, wxEXPAND);
    group2->AddSpacer(5);

    group2->Add(new wxButton(group2->GetStaticBox(), wxID_ANY, "Button 1"), 0, wxEXPAND);

    row->Add(group2, 1, wxEXPAND);

    row->AddSpacer(10);

    auto* group3 = new wxStaticBoxSizer(wxVERTICAL, mPanel, "Group 3");

    auto* r31 = new wxBoxSizer(wxHORIZONTAL);
    r31->Add(new wxStaticText(group3->GetStaticBox(), wxID_ANY, "Label 3"), 0, wxALIGN_CENTER_VERTICAL);
    r31->AddSpacer(5);
    r31->Add(new wxSpinCtrl(group3->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group3->Add(r31, 0, wxEXPAND);

    group3->AddSpacer(5);

    auto* r32 = new wxBoxSizer(wxHORIZONTAL);
    r32->Add(new wxCheckBox(group3->GetStaticBox(), wxID_ANY, "Check 4"), 0, wxALIGN_CENTER_VERTICAL);
    r32->AddSpacer(5);
    r32->Add(new wxSpinCtrl(group3->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group3->Add(r32, 0, wxEXPAND);

    group3->AddSpacer(5);
    group3->Add(new wxStaticLine(group3->GetStaticBox()), 0, wxEXPAND);
    group3->AddSpacer(5);

    group3->Add(new wxCheckBox(group3->GetStaticBox(), wxID_ANY, "Check 5"), 0);
    group3->AddSpacer(5);

    group3->Add(new wxSpinCtrl(group3->GetStaticBox(), wxID_ANY), 0, wxEXPAND);
    group3->AddSpacer(5);
    group3->Add(new wxSpinCtrl(group3->GetStaticBox(), wxID_ANY), 0, wxEXPAND);

    group3->AddSpacer(5);
    group3->Add(new wxStaticLine(group3->GetStaticBox()), 0, wxEXPAND);
    group3->AddSpacer(5);

    group3->Add(new wxSpinCtrl(group3->GetStaticBox(), wxID_ANY), 0, wxEXPAND);
    group3->AddSpacer(5);
    group3->Add(new wxCheckBox(group3->GetStaticBox(), wxID_ANY, "Check 6"), 0);

    row->Add(group3, 1, wxEXPAND);

    leftCol->Add(row, 0, wxEXPAND);

    leftCol->AddSpacer(10);

    auto* group4 = new wxStaticBoxSizer(wxHORIZONTAL, mPanel, "Group 4");

    auto* left = new wxBoxSizer(wxVERTICAL);
    left->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 7"));
    left->AddSpacer(5);
    left->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 8"));
    left->AddSpacer(5);
    left->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 9"));
    left->AddSpacer(5);
    left->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 10"));

    auto* right = new wxBoxSizer(wxVERTICAL);
    right->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 11"));
    right->AddSpacer(5);
    right->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 12"));
    right->AddSpacer(5);
    right->Add(new wxCheckBox(group4->GetStaticBox(), wxID_ANY, "Check 13"));
    right->AddSpacer(5);
    right->Add(new wxButton(group4->GetStaticBox(), wxID_ANY, "Button 2"), 0, wxEXPAND);

    group4->Add(left, 1, wxEXPAND);
    group4->AddSpacer(10);
    group4->Add(right, 1, wxEXPAND);

    leftCol->Add(group4, 0, wxEXPAND);

    root->Add(leftCol, 0, wxEXPAND);
    root->AddSpacer(10);

    auto* rightCol = new wxBoxSizer(wxVERTICAL);

    auto* group5 = new wxStaticBoxSizer(wxVERTICAL, mPanel, "Group 5");

    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 14"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 15"));

    group5->AddSpacer(5);
    group5->Add(new wxStaticLine(group5->GetStaticBox()), 0, wxEXPAND);
    group5->AddSpacer(5);

    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 16"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 17"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 18"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 19"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 20"));

    group5->AddSpacer(5);
    group5->Add(new wxStaticLine(group5->GetStaticBox()), 0, wxEXPAND);
    group5->AddSpacer(5);

    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 21"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 22"));
    group5->AddSpacer(5);
    group5->Add(new wxCheckBox(group5->GetStaticBox(), wxID_ANY, "Check 23"));

    rightCol->Add(group5, 0, wxEXPAND);

    rightCol->AddSpacer(10);

    auto* group6 = new wxStaticBoxSizer(wxVERTICAL, mPanel, "Group 6");

    auto* r61 = new wxBoxSizer(wxHORIZONTAL);
    r61->Add(new wxStaticText(group6->GetStaticBox(), wxID_ANY, "Label 4"), 0, wxALIGN_CENTER_VERTICAL);
    r61->AddSpacer(5);
    r61->Add(new wxSpinCtrl(group6->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group6->Add(r61, 0, wxEXPAND);

    group6->AddSpacer(5);

    auto* r62 = new wxBoxSizer(wxHORIZONTAL);
    r62->Add(new wxStaticText(group6->GetStaticBox(), wxID_ANY, "Label 5"), 0, wxALIGN_CENTER_VERTICAL);
    r62->AddSpacer(5);
    r62->Add(new wxSpinCtrl(group6->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group6->Add(r62, 0, wxEXPAND);

    group6->AddSpacer(5);

    auto* r63 = new wxBoxSizer(wxHORIZONTAL);
    r63->Add(new wxStaticText(group6->GetStaticBox(), wxID_ANY, "Label 6"), 0, wxALIGN_CENTER_VERTICAL);
    r63->AddSpacer(5);
    r63->Add(new wxSpinCtrl(group6->GetStaticBox(), wxID_ANY), 1, wxEXPAND);
    group6->Add(r63, 0, wxEXPAND);

    rightCol->Add(group6, 1, wxEXPAND);

    root->Add(rightCol, 1, wxEXPAND);

    mInnerSizer->Add(root, 1, wxEXPAND);

    auto timeCreated{std::chrono::steady_clock::now()};

    Fit();
    SetMinSize(GetSize());

    auto timeFit{std::chrono::steady_clock::now()};

    mPanel->Layout();

    auto timeLayout{std::chrono::steady_clock::now()};

    std::cout << "\n\n";
    std::cout << "Time To Clear: " << std::chrono::duration_cast<std::chrono::microseconds>(timeCleared - timeStart).count() << "us\n";
    std::cout << "Time To Create: " << std::chrono::duration_cast<std::chrono::microseconds>(timeCreated - timeCleared).count() << "us\n";
    std::cout << "Time To Fit: " << std::chrono::duration_cast<std::chrono::microseconds>(timeFit - timeCreated).count() << "us\n";
    std::cout << "Time To Layout: " << std::chrono::duration_cast<std::chrono::microseconds>(timeLayout - timeFit).count() << "us\n";
}

Ugly but gets the point across I suppose. The times I see here match pretty well what I see with my actual application. The times printed out though aren't the full story since there's the CoreAnimation drawing stuff that eats a substantial amount of time. Instruments shows it pretty clearly for this sample also, though.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4138083999@github.com>

Ryan Ogurek

unread,
Mar 26, 2026, 5:11:16 PM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

I suppose I should share those times, hah:

Time To Clear: 13194us
Time To Create: 157476us
Time To Fit: 1757us
Time To Layout: 42214us

Total hang time Instruments is reporting is 315ms


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4138233108@github.com>

VZ

unread,
Mar 26, 2026, 9:35:55 PM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
vadz left a comment (wxWidgets/wxWidgets#26318)

Under Windows you would typically use wxWindowUpdateLocker to prevent anything (layout, repainting, ...) being done until the window is thawed but in wxOSX it seems to only disable drawing. It could be worth checking if we can disable all the native size calls while the window (or its (grand)parent) is frozen and then applying them all at once when it is thawed.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4139445349@github.com>

Ryan Ogurek

unread,
Mar 26, 2026, 10:45:35 PM (8 days ago) Mar 26
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

Ah, yes. I must have lost it when I revised my initial post, but I did try wxWindowUpdateLocker to no avail.

It didn’t get put into the sample I provided, but it does lock around the updates in my application, and the numbers are not substantially different. Without looking closer I’d call it margin of error.

I’ll need to figure out how to get proper tracing on Windows, because, likewise, the update locker doesn’t seem to do much there, so I guess something else is contributing.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4139753148@github.com>

Ryan Ogurek

unread,
Mar 31, 2026, 5:31:38 AM (4 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

It could be worth checking if we can disable all the native size calls while the window (or its (grand)parent) is frozen and then applying them all at once when it is thawed.

I was thinking about your comment... I guess I'm not sure what you mean.

Most of the time isn't taken up by actually moving the windows on screen, it's repeatedly measuring the same thing. The insets measurement function is called several times throughout the movement, but only because of GetSize and friends. Worse, the Get'ers for Top/Left/Right border fetch all of the borders, but don't store it, so there's a huge amount of redundancy.

Perhaps I don't understand, but you'd have to defer all SetSize and GetSize for that to make a difference, and even then there's redundancy in each individual GetSize.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4161221490@github.com>

Ryan Ogurek

unread,
Mar 31, 2026, 6:04:29 AM (4 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed

Closed #26318 as completed.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issue/26318/issue_event/24068957884@github.com>

Ryan Ogurek

unread,
Mar 31, 2026, 6:04:32 AM (4 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

My other question about all this would be what's going on with CoreAnimation... if that draw function is being called multiple times for some reason, or if just the single (set of?) draw(s) is taking that long.

It does look like the draw is also being substantially slowed down by similar Get calls, in any case.

CleanShot.2026-0...@2x.png (view on web)

It's probably not the only slowdown, but it's a large, low-hanging fruit that seems worth addressing first.

This should probably be cached in the wxWidgetCocoaImpl, if it were to be? wxWidgetCocoaImpl::GetLayoutInset() is the notable override. The other thing is that there's nowhere that all 4 values are requested from wxWidgetCocoaImpl, since the only call to GetLayoutInset() is from MacGet*BorderSize() (of which there are almost always multiple calls to the various directions).so adding/replacing it with Get(Left|Right|Top|Bottom)LayoutInset() that tries the cache and then


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4161441834@github.com>

Ryan Ogurek

unread,
Mar 31, 2026, 6:10:25 AM (4 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed

Reopened #26318.


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issue/26318/issue_event/24069135844@github.com>

Ryan Ogurek

unread,
Mar 31, 2026, 11:41:34 AM (3 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

Using the sample I provided, looking at the "Hangs" instrument in Instruments:

With the current master da02b00 I'm seeing 270ms +/- <5ms
With the changes on da08c57 I'm seeing 104ms +/- <10ms

On da08c57:

The rebuild() function takes 55% of CPU time, with CA::Transaction::commit() taking 37%, and the rest being AppKit and ObjC overhead.

rebuild() w/ cached:

CleanShot.2026-0...@2x.png (view on web)

Current master:

CleanShot.2026-0...@2x.png (view on web)

wxOSX_drawRect() takes up 30% (compared to 65%) relative to CA::Transaction::commit(), and most of that time is spent in -[NSControl drawRect:].

CA::Transaction::commit() w/ cached:
CleanShot.2026-0...@2x.png (view on web)

Current master:

CleanShot.2026-0...@2x.png (view on web)


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4163586561@github.com>

Maarten

unread,
Mar 31, 2026, 3:20:36 PM (3 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed
MaartenBent left a comment (wxWidgets/wxWidgets#26318)

I’ll need to figure out how to get proper tracing on Windows, because, likewise, the update locker doesn’t seem to do much there, so I guess something else is contributing (maybe the same sizing, even?)

If you are using Visual Studio Community Edition, you can analyse the CPU usage with the Performance Profiler. I don't see anything that stands out. Lots of internal win32 stuff, but also some moving and sizing in wx code.

flamegraph.png (view on web)


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4164897281@github.com>

Ryan Ogurek

unread,
Mar 31, 2026, 3:58:03 PM (3 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed
ryancog left a comment (wxWidgets/wxWidgets#26318)

If you are using Visual Studio Community Edition, you can analyse the CPU usage with the Performance Profiler.

Yeah, I don’t have any symbols though since the compile is done from Linux with MinGW. I know it’s possible to get PDBs out of it, I just don’t have that setup (yet).

I don't see anything that stands out. Lots of internal win32 stuff, but also some moving and sizing in wx code.

How long is it taking for you (by the CPU spike, unless you’ve a better way to tell on Windows), if you by chance measured it?


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4165104947@github.com>

Maarten

unread,
Mar 31, 2026, 4:24:19 PM (3 days ago) Mar 31
to wx-...@googlegroups.com, Subscribed
MaartenBent left a comment (wxWidgets/wxWidgets#26318)

It doesn't give exact times (or I can't find it), but from the timeline it seems the rebuild function takes about 150ms (in red). I clicked it again at 4s, and it seems similar. The green areas after are event handling and painting.

cpu-times.png (view on web) click-rebuild.png (view on web)


Reply to this email directly, view it on GitHub, or unsubscribe.

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26318/4165278395@github.com>

Reply all
Reply to author
Forward
0 new messages