Skip to content

Commit

Permalink
wxrc: MakePackageCPP: Faster&leaner compilation of C++ source code …
Browse files Browse the repository at this point in the history
…generated by our resource compiler.

In order for C++-compilation to run much faster and consume much less memory
(in the presence of thousands of input resource files)
(especially with g++-14, at least with optimization and debugging information i.e. `-O2 -g`)
and for the resulting machine code to occupy less space (on disk and in memory),
we avoid (via an intermediary function with parameters of type "pointers to arrays of characters")
the generation of (thousands of pieces of) machine code which constructs and destructs `wxString` arguments
(especially since (anyway) our arguments (in the generated C++ source code)
are pointers to arrays of characters, not yet strings).

(Details and benchmarks can be found in C++ comments in this commit.)
  • Loading branch information
DoctorNoobingstoneIPresume authored and DoctorNoobingstoneIPresume committed Mar 27, 2024
1 parent edd288e commit 524b53d
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 9 deletions.
43 changes: 43 additions & 0 deletions utils/wxrc/GenerateBigXRCFile.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
set -e -o pipefail

# Usage example:
#
# The following command line:
# './GenerateBigXRCFile.sh' 2000 >'MyInputFileReferencingManyResources.xrc'
# will generate an XRC file referencing 2000 small PNG images.
#
# Compiling this XRC file using `wxrc` and further compiling the generated C++ source code
# can help us tune our code and avoid slow C++ compilation process
# (when thousands of input resource files have been given as input).

GenerateExampleImages ()
{
local folder_images='ExampleImages'
mkdir -p "${folder_images}/"

local i
local n="${1:-10000}"

printf '<?xml version="1.0" encoding="UTF-8"?>\n'
printf '<resource xmlns="http://www.wxwidgets.org/wxxrc" version="2.5.3.0">\n'
for ((i = 0; i < n; ++i)); do
local pathname_image; printf -v pathname_image "%s/Example_%04Xh.png" "${folder_images}" "$((i))"

if ((1)); then
rm -f "${pathname_image}"
printf >>"${pathname_image}" "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52"
printf >>"${pathname_image}" "\x00\x00\x00\x10\x00\x00\x00\x10\x08\x02\x00\x00\x00\x90\x91\x68"
printf >>"${pathname_image}" "\x36\x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0E\xC4\x00\x00\x0E"
printf >>"${pathname_image}" "\xC4\x01\x95\x2B\x0E\x1B\x00\x00\x00\x1A\x49\x44\x41\x54\x28\xCF"
printf >>"${pathname_image}" "\x63\x6C\x60\xF8\xCF\x40\x0A\x60\x62\x20\x11\x8C\x6A\x18\xD5\x30"
printf >>"${pathname_image}" "\x74\x34\x00\x00\xC5\xBF\x01\x9F\x22\x91\xFF\xBD\x00\x00\x00\x00"
printf >>"${pathname_image}" "\x49\x45\x4E\x44\xAE\x42\x60\x82"
fi

printf ' <object class="wxBitmap" name="Cat_%04Xh">%s</object>\n' "$((i))" "${pathname_image}"
done
printf '</resource>\n'
}

GenerateExampleImages "$@"
94 changes: 85 additions & 9 deletions utils/wxrc/wxrc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -718,13 +718,73 @@ void XmlResApp::MakePackageCPP(const wxArrayString& flist)
"#include <wx/xrc/xmlres.h>\n"
"#include <wx/xrc/xh_all.h>\n"
"\n"
"#if wxCHECK_VERSION(2,8,5) && wxABI_VERSION >= 20805\n"
" #define XRC_ADD_FILE(name, data, size, mime) \\\n"
" wxMemoryFSHandler::AddFileWithMimeType(name, data, size, mime)\n"
"#else\n"
" #define XRC_ADD_FILE(name, data, size, mime) \\\n"
" wxMemoryFSHandler::AddFile(name, data, size)\n"
"#endif\n"

// In order for C++-compilation to run faster and consume less memory
// (in the presence of thousands of input resource files)
// (especially with g++-14):
// ... Here comes a long explanation (with benchmarks too):
//
// In the C++ source code generated by the resource compiler,
// for each binary file (XRC file or file referenced from an XRC file),
// a call to `XRC_ADD_FILE` is placed in the generated file.
//
// Previously, `XRC_ADD_FILE` used to be a function-like macro
// (similar to a function which had all its calls inlined at compile-time),
// simply "forwarding" its arguments to `wxMemoryFSHandler::AddFile`.
//
// But some parameters of `wxMemoryFSHandler::AddFile` are of type `const wxString &`
// while the arguments we have are of type `const wxChar *`,
// and using the macro used to force at every "call" site
// the construction and destruction of temporary `wxString` objects.
//
// When there are thousands of such calls, the compilation of the generated C++ source code
// consumes a lot of time and memory:
//
// For 2,000 binary files:
// g++-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 (`g++-11 -O2 -g`): 42.95 seconds and 1,107,296 KiB of RAM
// g++-14 (GCC) 14.0.1 20240117 (experimental) (`g++ -O2 -g`): 93.44 seconds and 1,149,840 KiB of RAM
//
// For 10,000 binary files:
// Visual C++ 2019 (version 19.29.30151 for x64) (`cl /O2 /Zi`): 46.51 seconds
// g++-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 (`g++-11 -O2 -g`): 151.13 seconds and 3,265,268 KiB of RAM
// g++-14 (GCC) 14.0.1 20240117 (experimental) (`g++ -O2 -g`): 491.27 seconds and 3,489,140 KiB of RAM
//
// For 20,000 binary files:
// Visual C++ 2019 (version 19.29.30151 for x64) (`cl /O2 /Zi`): 56.46 seconds
// g++-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 (`g++-11 -O2 -g`): 1,155.89 seconds and 6,217,464 KiB of RAM
// g++-14 (GCC) 14.0.1 20240117 (experimental) (`g++ -O2 -g`): 2,799.70 seconds and 6,998,852 KiB of RAM
//
// We have now changed `XRC_ADD_FILE` from a function-like macro into an actual function.
// It simply calls `wxMemoryFSHandler::AddFile`, therefore the behaviour is the same.
// But its parameters are not of type `const wxString &`, but of type `const wxChar *` instead,
// exactly matching the arguments in the generated C++ source code
// and not requiring construction and destruction of temporary `wxString` objects
// (or, at compile-time, generation of machine code which performs such constructions and destructions);
// these constructions and destructions are now centralized in the body of `XRC_ADD_FILE`.
//
// The benchmarks of compiling the generated C++ source code have significantly improved:
//
// For 2,000 binary files:
// g++-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 (`g++-11 -O2 -g`): 2.22 seconds and 358,936 KiB of RAM
// g++-14 (GCC) 14.0.1 20240117 (experimental) (`g++ -O2 -g`): 4.52 seconds and 343,872 KiB of RAM
//
// For 10,000 binary files:
// Visual C++ 2019 (version 19.29.30151 for x64) (`cl /O2 /Zi`): 3.23 seconds
// g++-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 (`g++-11 -O2 -g`): 13.59 seconds and 803,592 KiB of RAM
// g++-14 (GCC) 14.0.1 20240117 (experimental) (`g++ -O2 -g`): 31.80 seconds and 858,884 KiB of RAM
//
// For 20,000 binary files:
// Visual C++ 2019 (version 19.29.30151 for x64) (`cl /O2 /Zi`): 3.36 seconds
// g++-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 (`g++-11 -O2 -g`): 43.47 seconds and 1,417,348 KiB of RAM
// g++-14 (GCC) 14.0.1 20240117 (experimental) (`g++ -O2 -g`): 123.11 seconds and 1,697,600 KiB of RAM
//
// (All benchmarks have been performed by x86_64 toolchains generating x86_64 machine code.)
//
// The `GenerateBigXRCFile.sh` script can be used to generate
// many small resource files and an XRC file which references them all,
// in order to conduct similar experiments/benchmarks.
//
"void XRC_ADD_FILE(const wxChar *filename, const void *binarydata, size_t size, const wxChar *mimetype);\n"
"\n");

file.Write("namespace\n{\n");
Expand Down Expand Up @@ -781,8 +841,24 @@ void XmlResApp::MakePackageCPP(const wxArrayString& flist)
}

file.Write("}\n");


file.Write(
"\n"
"void XRC_ADD_FILE(const wxChar *filename, const void *binarydata, size_t size, const wxChar *mimetype)\n"
"{\n"
// It may appear as though this is a simply-forwarding function,
// but in fact it converts several arguments from `const wxChar *` to `const wxString &`,
// i.e. it constructs and destroys several temporary `wxString` objects,
// and therefore we centralize these operations here as opposed to inlining them at all call sites
// (which could cost significant time and memory at compile-time and also memory at run-time
// as described in comments at the beginning of this file).
//
" #if wxCHECK_VERSION(2,8,5) && wxABI_VERSION >= 20805\n"
" return wxMemoryFSHandler::AddFileWithMimeType(filename, binarydata, size, mimetype);\n"
" #else\n"
" return wxMemoryFSHandler::AddFile(filename, binarydata, size);\n"
" #endif\n"
"}\n"
);
}

void XmlResApp::GenCPPHeader()
Expand Down

0 comments on commit 524b53d

Please sign in to comment.