wxAnimation::Load is very slow (Issue #26457)

14 views
Skip to first unread message

BazaarAcc32

unread,
8:17 AM (6 hours ago) 8:17 AM
to wx-...@googlegroups.com, Subscribed
BazaarAcc32 created an issue (wxWidgets/wxWidgets#26457)

I'm loading a webp file into wxAnimation. The file size is almost 11MB. The webp file contains an animation and consists of 234 frames. However, the wxAnimation::Load method takes about 5 seconds to complete. For example, Google Chrome/Edge loads and displays the same image in about a second.

I'm using the following image: https://colinbendell.github.io/webperf/animated-gif-decode/2.webp

Platform: Windows 11
wxWidgets Version: 3.3.0
Build: Release
Compiler: MSVC, from Visual Studio 2022

Here's the test code
#include <wx/wx.h>
#include <wx/webrequest.h>
#include <wx/stopwatch.h>
#include <wx/mstream.h>
#include <wx/animate.h>

class MyFrame : public wxFrame
{
public:
MyFrame()
: wxFrame(nullptr,
wxID_ANY,
"wxWidgets WebP Animation Timing",
wxDefaultPosition,
wxSize(600, 400))
{
Bind(wxEVT_WEBREQUEST_STATE,
&MyFrame::OnWebRequestState,
this);

    StartDownload();
}

private:
void StartDownload()
{
m_stopwatch.Start();

    wxWebSession session = wxWebSession::New();

    m_request = session.CreateRequest(
        this,
        "https://colinbendell.github.io/webperf/animated-gif-decode/2.webp"
    );

    m_request.Start();
}

void OnWebRequestState(wxWebRequestEvent& evt)
{
    switch (evt.GetState())
    {
    case wxWebRequest::State_Completed:
    {
        long downloadMs = m_stopwatch.Time();

        wxInputStream* stream = m_request.GetResponse().GetStream();
        if (!stream)
        {
            wxMessageBox("Failed to get response stream", "Error");
            return;
        }

        wxStopWatch decodeWatch;

        wxAnimation animation;
        bool ok = animation.Load(*stream, wxANIMATION_TYPE_WEBP);

        long decodeMs = decodeWatch.Time();

        if (!ok)
        {
            wxMessageBox("Failed to load animation", "Error");
            return;
        }

        wxString text;
        text.Printf(
            "Download time: %ld ms\n"
            "wxAnimation load time: %ld ms, frame count: %ld",
            downloadMs,
            decodeMs,
            animation.GetFrameCount()
        );

        wxMessageBox(text, "Timing Result");

        Close();
        break;
    }

    case wxWebRequest::State_Failed:
    {
        wxMessageBox("Download failed", "Error");
        Close();
        break;
    }

    default:
        break;
    }
}

private:
wxStopWatch m_stopwatch;
wxWebRequest m_request;
};

class MyApp : public wxApp
{
public:
bool OnInit() override
{
wxInitAllImageHandlers();

    auto* frame = new MyFrame();
    frame->Show();

    return true;
}

};

wxIMPLEMENT_APP(MyApp);


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/issues/26457@github.com>

PB

unread,
9:23 AM (4 hours ago) 9:23 AM
to wx-...@googlegroups.com, Subscribed
PBfordev left a comment (wxWidgets/wxWidgets#26457)

Out of curiosity, I tried a truly minimal example, using a local file (to avoid possible influence of network):

#include <wx/wx.h>
#include <wx/animate.h>
#include <wx/stopwatch.h>

class MyApp : public wxApp
{
    bool OnInit() override
    {
        const wxString fileName= "2.webp";
        wxStopWatch stopWatch;
        wxAnimation animation;

        wxAnimation::InitStandardHandlers();

        stopWatch.Start();
        if ( !animation.LoadFile(fileName, wxANIMATION_TYPE_WEBP) )
            wxLogError("Failed to load animation from '%s'.", fileName);
        else
            wxLogMessage("Animation '%s' loaded in %ld ms.", fileName, stopWatch.Time());

        return false;
    }
}; wxIMPLEMENT_APP(MyApp);

The time reported is about 6 s, on a not very fast computer running Windows 10. OTOH, both FireFox and Edge start playing the animation immediately after the file being dropped on their frame.

My guess (without any profiling or looking at the code) is that the browsers start playing the animation before loading all its frames.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

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

Maarten

unread,
11:12 AM (3 hours ago) 11:12 AM
to wx-...@googlegroups.com, Subscribed
MaartenBent left a comment (wxWidgets/wxWidgets#26457)

Debug mode is usually (very) slow when IO is involved.

But even in Release mode it is slow. Profiling shows this is caused by reading data into the wxMemoryOutputStream, mostly caused by this very small buffer size:
https://github.com/wxWidgets/wxWidgets/blob/c968631837d74af60a8c454d2253d8f0b550949c/src/common/stream.cpp#L42

Increasing it to 65536 (2^16) helps a lot.

For me, the time to load the image changes from about 9 to 3 seconds, where the remaining 3 seconds is loading all the frames inside libwebp (WebPAnimDecoderGetNext()).

Bypassing the wxMemoryOutputStream entirely is slightly faster still:

bool wxWEBPHandler::LoadAnimation(std::vector<wxWebPAnimationFrame>& frames, wxInputStream& stream, bool verbose)
{
#if 1
    size_t size = stream.GetSize();
    std::vector<uint8_t> buffer(size);
    stream.ReadAll(buffer.data(), size);

    WebPData webp_data{};
    webp_data.bytes = buffer.data();
    webp_data.size = buffer.size();
#else
    wxMemoryOutputStream mos;
    stream.Read(mos);
    wxStreamBuffer* mosb = mos.GetOutputStreamBuffer();
    if (mosb == nullptr)
    {
        if (verbose)
        {
            wxLogError(_("WebP: Allocating stream buffer failed."));
        }
        return false;
    }

    WebPData webp_data{};
    webp_data.bytes = reinterpret_cast<uint8_t*>(mosb->GetBufferStart());
    webp_data.size = mosb->GetBufferSize();
#endif

    ...


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

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

PB

unread,
1:07 PM (1 hour ago) 1:07 PM
to wx-...@googlegroups.com, Subscribed
PBfordev left a comment (wxWidgets/wxWidgets#26457)

Debug mode is usually (very) slow when IO is involved.

I did not know that. I know that with MSVC, in the Debug builds, the STL containers are MUCH slower (checked access) and memory (de)allocations may be slower as well, but I/O? Thought that would be cache-size or device-speed limited...

But even in Release mode it is slow. Profiling shows this is caused by reading data into the wxMemoryOutputStream, mostly caused by this very small buffer size:

FWIW, my ad-hoc "benchmark" (MSVS 2026 CMake-built Release DLL, the file is probably loaded from OS cache), using the code from my previous post with changing BUF_TEMP_SIZE:
default 4 kB: 5.8 s
8 kB: 4.1 s
16 kB: 3.3 s
32 kB: 2.9 s
64 kB: 2.6 s
100 kB: 2.6 s

So it would it seem that increasing the buffer to just 32 kB should bring significant speed increase. But these days even 65 kB should not matter, unless the code is run in a loop, which would be rare. IIRC, the thread stack size on Windows is 1 MB...


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

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

Maarten

unread,
1:33 PM (16 minutes ago) 1:33 PM
to wx-...@googlegroups.com, Subscribed
MaartenBent left a comment (wxWidgets/wxWidgets#26457)

I did not know that. I know that with MSVC, in the Debug builds, the STL containers are MUCH slower (checked access) and memory (de)allocations may be slower as well, but I/O? Thought that would be cache-size or device-speed limited...

My, bad that is what I was thinking about. I usually read data into stl vectors/arrays and mixed them up.

I'm preparing a PR to replace wxMemoryOutputStream with just a buffer. The input stream is always fully read, so there is no need to use a stream.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications on the go with GitHub Mobile for iOS or Android.

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

Reply all
Reply to author
Forward
0 new messages