On Fri, 17 Nov 2017 23:34:47 +0200 Eran Ifrah wrote:
EI> As part of an overall code performance that I am conducting on CodeLite IDE
EI> I was surprised to discover that one of the bottlenecks was wxArrayString
Thanks for doing this, it was indeed unexpected to me too.
EI> From my investigation, I noticed that the Add() method is the bottleneck
EI> The code was spending considerably amount of time increasing the internal
EI> buffer.
EI>
EI>
EI> For example, running this simple code:
EI>
EI> #include <stdio.h>
EI> #include <vector>
EI> #include <wx/arrstr.h>
EI> #include <wx/init.h>
EI>
EI> int main(int argc, char** argv)
EI> {
EI> wxInitializer init(argc, argv);
EI> wxArrayString arr;
EI> wxString str;
EI> str << "hello_World";
EI> for(size_t i = 0; i < 500000; ++i) {
EI> arr.push_back(str); // does not matter if I call here push_back() or Add()
EI> }
EI> return 0;
EI> }
I've modified this to use wxStopWatch which is slightly more precise, but
I can still reproduce about the same numbers: with wxArrayString it takes
800ms, with std::vector -- 40ms.
EI> My question:
EI> Why don't we simply wrap std::vector<wxString> with wxArrayString?
This is done in wxUSE_STD_CONTAINERS build, which is enabled by wxUSE_STL
too and if there are no backwards compatibility concerns, it's a good idea
to use it. Unfortunately we still don't use it by default, but maybe we
should change this for 3.2...
On Sat, 18 Nov 2017 03:24:08 +0100 Maarten Bent wrote:
MB> Some profiling results.
MB>
MB> wxArrayString::Add
MB> [65.8%] wxScopedArray<wxString> oldStrings(Grow(nInsert));
MB> [33.1%] return ret;
MB>
MB> Looking at wxArrayString::Grow
MB> [12.1%] wxString *pNew = new wxString[m_nSize];
MB> [0.2%] for ( size_t j = 0; j < m_nCount; j++ )
MB> [53.4%] pNew[j] = m_pItems[j];
MB>
MB> It looks like every time memory is allocated for wxArrayString, all the
MB> data is copied.
MB> And the old array is destructed.
Thanks for taking time to profile it but, for once, the results are not
really surprising. This code basically does nothing else than allocating
memory in a loop, so of course the execution time is dominated by doing it
and copying data into the new buffer.
There are 2 possible optimizations here:
1. Use realloc() instead of new[] to avoid copying as much as possible.
wxVector<> does this, but the same benchmark shows that it's only
slightly faster than wxArrayString, so, clearly, we can't always grow
our memory pool in place.
2. Move strings to the new storage instead of copying them. I wouldn't
expect this to result in any big gains with this particular benchmark
because "hello_World" should fir in the internal string buffer, but
for longer strings this should be a big gain. Of course, it could only
be done in C++11 mode but this is not really an important constraint
nowadays.
On Sat, 18 Nov 2017 10:56:45 +0200 Teodor Petrov wrote:
TP> And the main problem is that growing is done linearly and not in a
TP> quadratic fashion. And worse there is a cap
TP> on the max grow size - ARRAY_MAXSIZE_INCREMENT = 4096.
Indeed, we're too conservative here. With the following patch:
---------------------------------- >8 --------------------------------------
diff --git a/src/common/arrstr.cpp b/src/common/arrstr.cpp
index ccc25f23ab..cd599f6b22 100644
--- a/src/common/arrstr.cpp
+++ b/src/common/arrstr.cpp
@@ -140,6 +140,7 @@ wxString *wxArrayString::Grow(size_t nIncrement)
return NULL;
}
else {
+#if 0
// otherwise when it's called for the first time, nIncrement would be 0
// and the array would never be expanded
// add 50% but not too much
@@ -150,6 +151,9 @@ wxString *wxArrayString::Grow(size_t nIncrement)
if ( nIncrement < ndefIncrement )
nIncrement = ndefIncrement;
m_nSize += nIncrement;
+#else
+ m_nSize *= 2;
+#endif
wxString *pNew = new wxString[m_nSize];
// copy data to new location
---------------------------------- >8 --------------------------------------
I get ~30ms on the benchmark, i.e. faster than std::vector<>. Of course,
the idea for not doing this from the beginning was to avoid consuming too
much RAM but maybe it's not such a good trade-off any more, even if it ever
was.
TP> So you cannot do a manual resize from user
TP> code like: array.Grow(array.size()*2);
This really shouldn't be necessary, if you know the size of your data in
advance (even approximately), calling reserve() is by far the fastest
solution, e.g. this benchmark takes ~8ms if I do this.
Regards,
VZ