Hi,
I've got a performance problem during a test I've done with multi_index
on MSVC 6.0 SP 6.
I'm using BOOST 1.32.0 .
The search performance is comparable with several maps, but
insertion is dramatically slow : 5mn 30s for 10 000 employees.
Here is my test code :
===
/* Boost.MultiIndex basic example.
*
* Copyright 2003-2004 Joaquín M López Muñoz.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See http://www.boost.org/libs/multi_index for library home page.
*/
#include "stdafx.h"
#if !defined(NDEBUG)
#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
#endif
#include
employee_set;
template
Stanislas RENAN
Hi,
I've got a performance problem during a test I've done with multi_index on MSVC 6.0 SP 6. I'm using BOOST 1.32.0 . The search performance is comparable with several maps, but insertion is dramatically slow : 5mn 30s for 10 000 employees.
Here is my test code :
[...]
#if !defined(NDEBUG) #define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING #define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE #endif
I guess here's the culprit. If you're timing the program in debug mode then the safe mode will kick in, and this is abismally slower than an unchecked STL. Apart from this, measuring things in debug mode makes little sense, so you should: 1. disable safe mode AND 2. compile in release In your particular example, release build automatically disables safe mode (because of the #if !defined(NDEBUG) directive.) I've run your program in release mode in MSVC 6.0 SP5 and my results are: Insertion multi = 62 STL = 63 Recherche multi = 297 STL = 234 Which are more reasonable. Can you reproduce these numbers? HTH Joaquín M López Muñoz Telefónica, Investigación y Desarrollo
Joaquin M Lopez Munoz
Stanislas RENAN
writes: Hi,
I've got a performance problem during a test I've done with multi_index on MSVC 6.0 SP 6. I'm using BOOST 1.32.0 . The search performance is comparable with several maps, but insertion is dramatically slow : 5mn 30s for 10 000 employees.
Here is my test code :
[...]
#if !defined(NDEBUG) #define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING #define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE #endif
I've taken a closer look at your program, and I think the comparison is not exactly fair. After populating your EmployeeMapper, their index sizes are: m_byId size:10000 m_byName size:10000 m_byAge size:100 while all indices of a multi_index_container have the same size (in this case, 10000). To balance this, I've changed employee e(i, // index numérique unique os.str(), // nom, chaîne unique alea(100)); // valeur potentiellement dupliquée to employee e(i, // index numérique unique os.str(), // nom, chaîne unique i); // unique and got the following results: Insertion multi = 47 STL = 78 Recherche multi = 234 STL = 266 If, instead of this change, I allow your EmployeeMapper to contain duplicate values for the age index by turning m_byAge into a multimap (and doing the necessary changes, vg. multimap does not have an operator[]) the numbers are: Insertion multi = 62 STL = 94 Recherche multi = 297 STL = 312 Joaquín M López Muñoz Telefónica, Investigación y Desarrollo
Joaquin M Lopez Munoz a écrit :
Insertion multi = 47 STL = 78 Recherche multi = 234 STL = 266
If, instead of this change, I allow your EmployeeMapper to contain duplicate values for the age index by turning m_byAge into a multimap you're right, my test program is broken.
(and doing the necessary changes, vg. multimap does not have an operator[]) the numbers are:
Insertion multi = 62 STL = 94 Recherche multi = 297 STL = 312
I've made the changes and see no noticeable difference in figures
before :
multi 16
STL 0
multi 422
STL 468
after :
Insertion
multi = 16
STL = 15
Recherche
multi = 438
STL = 484
switching to release mode, I give the arimthmic average value of 5 runs
because figures are small (so from notes and calculations) :
multi : 3
STL : 0
multi : 137
STL : 131
I see no real difference in performance between both of them (the
accuracy of the measures is not very good).
Here are the changes in the code :
std::multimap
Stanislas RENAN
I've made the changes and see no noticeable difference in figures
before : multi 16 STL 0 multi 422 STL 468
after : Insertion multi = 16 STL = 15 Recherche multi = 438 STL = 484
switching to release mode, I give the arimthmic average value of 5 runs because figures are small (so from notes and calculations) : multi : 3 STL : 0 multi : 137 STL : 131
I see no real difference in performance between both of them (the accuracy of the measures is not very good).
Here are the changes in the code : std::multimap
m_byAge; typedef std::multimap ::iterator EMAgeIt; and
m_byAge.insert(std::make_pair
(e2->age(), e2));
You seem to have made the same changes as I, yet our numbers do not coindice. Could you please send me your changed code? Thanks! Searching should be roughly the same, anyway, but I expect insertion to be noticeably faster in the case of multi_index_container.
The next problem I have now is compilation time. This example has taken 50s to compile on my computer in debug mode, in its first version.
Now, with the 2 debug defines commented, and the multimap it takes : - 41s to compile in release mode with MI - 3s to compile with all BOOST references enclosed in #if defined() that evaluates to false. So, on this example, it's 13 times longer.
I wonder why compilation time is so long, and if I can do something to make things faster. I'm getting upset with compilation times on a very small test, so I wonder what it's gonna be in real programs. Maybe it's an absolute addition, just 40s, whatever the number of references to MI (any BOOST ?) objects. But even if it's right, if each file compiled suffers from this additon, it could make compilation time very long. That may be considered an acceptable price.
We've got an application here that take several hours to compile. If we use BOOST library, is this (long) time gonna be 13 times longer ? That would probably be an unacceptable price.
I'd tend to think you would more likely pay a roughly fixed penalty per header, instead of a x13 factor in compilation times, especially if your code takes already long to compile. But this may well be wishful thinking; please read below.
Maybe there is a document or a discussion regarding this issue ? I've found nothing neither on the web site nor on the list. Or am I missing something anew ?
Unfortunately, this is a known issue, and one for which I have no easy workaround. Boost.MultiIndex is heavily templatized, and this makes the compiler work real hard. I've been reported that MSVC 7.0 and 7.1 (aka .NET 2002 and 2003) are faster when dealing with Boost.MultiIndex, but I guess switching compiler is not an option to you. Another possibility (not tested) is that you include Boost.MultiIndex headers inside your precompiled header stdafx.h. MSVC++ 6.0 precompiled headers support is a little fragile, so I won't be surprised if this resulted in spurious crashing and/or the need for full rebuilds. In any case, if you try this option please let me know. To sum it up, compilation times can be a barrier for adoption, especially in somewhat older compilers, and you are wise to test this before embarking into using Boost.MultiIndex. If you finally decide to drop it, at least keep in mind this is going to be better with newer compilers. To my discharge, allow me to say that this is the best I've been able to come up with: after all, template stuff was not thought in the late nineties to be as extensively abused as it is now. Joaquín M López Muñoz Telefónica, Investigación y Desarrollo
You seem to have made the same changes as I, yet our numbers do not coindice. Could you please send me your changed code? Thanks! There is a difference between our 2 codes : I haven't changed
Joaquin M Lopez Munoz a écrit : the way age is set, thus it is not unique, and using get() on this index should return a set (generally speaking) of employees. I've not used equal_range() which makes the code broken anew. Using it (but not returning a range) gives approximativelly the same figures.
Searching should be roughly the same, anyway, but I expect insertion to be noticeably faster in the case of multi_index_container. I post the code at the end of the message. I also post the patch using equal_range().
I'd tend to think you would more likely pay a roughly fixed penalty per header, instead of a x13 factor in compilation times, especially if your code takes already long to compile. But this may well be wishful thinking; please read below. I've made the same guess, but have not yet tested it, and I'm not sure to have the time to test it in existing real projects, as it is going to take a too much time to implement. I'm not in the development team of existing project I've talked about.
Unfortunately, this is a known issue, and one for which I have no easy workaround. Boost.MultiIndex is heavily templatized, and this makes the compiler work real hard. I've been reported that MSVC 7.0 and 7.1 (aka .NET 2002 and 2003) are faster when dealing with Boost.MultiIndex, but I guess switching compiler is not an option to you. The problem is that to suggest using BOOST, and multiindex in particular, I would have to proove that it is better (quicker, more flexible, more standardized, ... or all of them) than the existing, and even if this is true, transition from old to code using boost would not be automatically done. I primarilly aim at using it in new projects, if there is an estimated gain, after having examinated most of advantages/drawbacks. Buying a new compiler for testing purposes is not going to convince my boss.
Another possibility (not tested) is that you include Boost.MultiIndex headers inside your precompiled header stdafx.h. MSVC++ 6.0 precompiled headers support is a little fragile, so I won't be surprised if this resulted in spurious crashing and/or the need for full rebuilds. In any case, if you try this option please let me know. According to one of the developper of the project I've talked about, precompiled headers have been deactivated because of problems with them.
keep in mind this is going to be better with newer compilers. For sure, but the problem is similar to the adoption of the STL a few years ago, in companies using old compilers for their old projects, with broken (or non-existing) STL support and hand-made containers. There must be a real gain to incite people to change their tools and practices.
To my discharge, allow me to say that this is the best I've been able to come up with: after all, template stuff was not thought in the late nineties to be as extensively abused as it is now. My purpose was not to criticize your work, which I have not yet studied in depth in order to evaluate the advantages and drawbacks. For now, to refocus on MI, I have roughly the four elements : 1-it is complex to write it the first times, but it can be encapsulated, as STL is in my example ; 2-it is as efficient as the STL is, but not more in my example ; 3-it stresses the compiler a lot, which adds compilation time yet to be evaluated ; 4-it avoids writing encapsulating classes if we are familiar with it, which cannot be done with basic containers of the STL.
If point 2 is changed from "as efficient" to "more efficient", it
certainly will weight on decisions.
I admit that my test example is basic, and may not show the capabilities
of MI, but that's a real life example.
I have not treated the removal of elements. I have thought about adding
iterators on other indexes for each element in the maps, but it adds
2 iterators (I've got 3 indexes) and a container for each element.
But, hey, what I'm talking about here is just what you've done in your
performance tests, which addresses most of the situations.
I've written my test code having in mind populating and searching.
Removal wasn't necessary.
The problem of index synchronisation is the typical example of what a
programmer doesn't want to solve, to focus on the business part of the
application.
I guess using MI may efficiently address this type of problems, I just
need to evaluate its cost, and if there is a gain in real-life practise
(where multi indexed data is not so often used).
Here is a copy of the code I've used for the previous figures :
===
/* Boost.MultiIndex basic example.
*
* Copyright 2003-2004 Joaquín M López Muñoz.
* Distributed under the Boost Software License, Version 1.0.
* (See accompanying file LICENSE_1_0.txt or copy at
* http://www.boost.org/LICENSE_1_0.txt)
*
* See http://www.boost.org/libs/multi_index for library home page.
*/
#include "stdafx.h"
#define COMPILE_BOOST
//#undef COMPILE_BOOST
#if defined (COMPILE_BOOST)
#if !defined(NDEBUG)
//#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
//#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
#endif
#include
employee_set;
template
Stanislas RENAN
Joaquin M Lopez Munoz a écrit :
I post the code at the end of the message. I also post the patch using equal_range().
I haven't tried the equal_range patch. As for your code without the patch, I have built it unmodified and here are my figures: MSVC++ 6.0 SP5, machine P4 1.6 GHz 261 KB Release mode Case A: tailleEnsemble=1000 multi = 0 STL = 0 Recherche multi = 156 STL = 140 Sometimes, multi insertion jumps to 15, some other times it is STL insertion that jumps to 15. This case is too fast to obtain significant results. Case B: tailleEnsemble=10000 Insertion multi = 62 STL = 78 Recherche multi = 297 STL = 281 Some variations on succesive runs, but the tendency (less insertion time, somewhat slower on search) remains. Case C: tailleEnsemble=100000 Insertion multi = 1188 STL = 1406 Recherche multi = 437 STL = 454 In some runs, multi search is the same or slightly larger than STL search. multi insertion remains always below the STL case. Another issue you haven't considered is memory comsumption. Your EmployeeMapper class takes the following per element: * one employee object * 13 B (map node) + 1 int + 1 pointer (ID index) * 13 B (map node) + 1 std::string + 1 pointer (Name index) * 13 B (map node) + 1 int + 1 pointer (age index) that is, sizeof(employee) + sizeof(string) + 59 bytes per element. Boost.MultiIndex takes: * one employee object * 13 B (index node) (ID index) * 13 B (index node) (Name index) * 13 B (index node) (age index) which sums sizeof(employee) + 39 bytes per element. Of course, EmployeeMapper can do better (having underlying sets instead of maps so as to not duplicate the keys), but even so you won't attain the memory efficiency of Boost.MultiIndex by at least 8 bytes per element. As for building times, I have roughly Debug mode: approx. 15 s. Release mode: approx. 15 s If I undefine COMPILE_BOOST, your program does not compile, so I can't compare.
For now, to refocus on MI, I have roughly the four elements : 1-it is complex to write it the first times, but it can be encapsulated, as STL is in my example ;
I'm sorry I couldn't make it simpler. The idea is to leverage as much STL expertise as possible, so that the programmer hasn't to learn a new interface.
2-it is as efficient as the STL is, but not more in my example ;
See above.
3-it stresses the compiler a lot, which adds compilation time yet to be evaluated ;
Yep.
4-it avoids writing encapsulating classes if we are familiar with it, which cannot be done with basic containers of the STL.
OK
If point 2 is changed from "as efficient" to "more efficient", it certainly will weight on decisions. I admit that my test example is basic, and may not show the capabilities of MI, but that's a real life example.
Well, of course it is your job to decide whether Boost.MultiIndex is any use to you, and it might not be so. I try not to evangelize (too much), but IMHO multi_index_container has real advantages that might come up when you need to turn your EmployeeMapper into an industrial-strength component: * It is exception safe. * Removal is taken care of (you talk about this yourself later). * Includes updating functions. * Has a key extraction model that you either has to rewrite or else mimic as you have done with maps, thus incurring some memory overhead. * Perhaps most importantly, adding a new index is simple. With a homemade component, adding a new index involves lots of additional (admittedly boilerplate) coding. Heck, this is what templates are about, avoiding code repetition. On the downside, compilation times increase significantly :( In any case, I'd be happy to know what your final decision regarding Boost.MultiIndex adoption is. If you join the party, count me in for as much support as you need. Best, Joaquín M López Muñoz Telefónica, Investigación y Desarrollo
participants (2)
-
Joaquin M Lopez Munoz
-
Stanislas RENAN