Mainline mt_allocator performance on sparc-sun-solaris2.8
Brad Spencer
spencer@infointeractive.com
Thu Sep 16 02:19:00 GMT 2004
On Mon, Sep 13, 2004 at 12:15:13PM -0500, Benjamin Kosnik wrote:
>
> >This is slower than gcc-3.4.1. Percentage difference between each
> >slot on the gcc-3.4.1 table and gcc-4.0.0 table:
> >
> > -4.867% -15.324% -8.733% -15.224% -13.925% -2.035%
> > -4.995% -4.872% -7.592% -9.126% -3.582% -6.545%
> > -11.423% -6.209% 9.218% -5.644% --3.759% -0.530%
> >
> >This seems fairly significant to me.
>
> I see this too. If you switch from __common_pool_policy to
> __per_type_pool_policy it should go away, due to improved inlining.
Ahh, by using __mt_alloc<type, __per_type_pool_policy>, I suppose.
Hmmmm... I'm not sure how practical this will be unless I can easily
configure the compiler with that as the default allocator.
> I'd
> like to elaborate on this, but before I do can I get your test program?
> I don't see it in any of the posts.
Yes, of course. It was depending on some support libraries, but
I've separated the essence of the timing test and made it standalone.
Sorry for it being a bit sloppy, but it's attached. Oh, and there's
no deep theoretical basis for the algorithm :) I'm just trying to
similate _some_ usage to get a metric.
It's using clock_gettime() for the timing, so on Linux, add -lt, and
on Solaris, add -lposix4. There is no good reason for this.
> You can force single threaded behavior, even from threads enabled
> configs in the current mt_alloc sources.
Gotcha. I still think it's odd that the performance hit would be so
dramatic just by using the thread-aware pool. Here are some new runs
with the attached standalone test. The compiler is the same and the
configure options are the same except for the --enable-threads
selection.
Configured with: ../../gcc-4.0.0/configure --with-dwarf2 --enable-languages=c,c++ --enable-threads=yes --disable-shared --prefix=/hosts/bubbles/spencer/devel/gcc/install-threaded/gcc-4.0.0-cross/sparc-sun-solaris2.8 --with-gnu-as --with-gnu-ld --disable-multilib --enable-concept-checks --enable-clocale=generic --enable-libstdcxx-allocator=mt --target=sparc-sun-solaris2.8 --with-sysroot=/opt/sysroot/sparc-sun-solaris2.8/
Thread model: posix
gcc version 4.0.0 20040912 (experimental)
Number of slots
Size 1 8 64 1024 8192 1048576
---------- ------- ------- ------- ------- ------- -------
8 bytes 0.65408 0.74944 0.83358 0.74248 0.76384 0.79638
128 bytes 0.75224 0.88115 1.0066 0.92986 1.0087 1.0435
1024 bytes 0.89352 0.9052 0.91799 0.90635 0.90713 1.2161
Configured with: ../../gcc-4.0.0/configure --with-dwarf2 --enable-languages=c,c++ --enable-threads=no --disable-shared --prefix=/hosts/bubbles/spencer/devel/gcc/install/gcc-4.0.0-cross/sparc-sun-solaris2.8 --with-gnu-as --with-gnu-ld --disable-multilib --enable-concept-checks --enable-clocale=generic --enable-libstdcxx-allocator=mt --target=sparc-sun-solaris2.8 --with-sysroot=/opt/sysroot/sparc-sun-solaris2.8/
Thread model: single
gcc version 4.0.0 20040912 (experimental)
Number of slots
Size 1 8 64 1024 8192 1048576
---------- ------- ------- ------- ------- ------- -------
8 bytes 2.4847 2.1741 2.3043 2.1742 1.9974 1.9253
128 bytes 3.5108 3.0202 2.9637 2.5956 2.6348 2.205
1024 bytes 0.87176 0.90735 0.94823 0.93351 0.94565 1.1434
I profiled the theaded version, and it's showing the top three
"non-framework" functions as
__gnu_cxx::__pool<(bool)1>::_M_reclaim_memory(char*, unsigned)
__gnu_cxx::__mt_alloc<char, __gnu_cxx::__common_pool_policy<(bool)1> >::allocate(unsigned, void const*)
__gnu_cxx::__pool<(bool)1>::_M_get_thread_id()
[BTW, thanks for pointing out that those were forward declarations in
the other post ;) Doh!]
--
------------------------------------------------------------------
Brad Spencer - spencer@infointeractive.com - "It's quite nice..."
Systems Architect | InfoInterActive Corp. | A Canadian AOL Company
-------------- next part --------------
//*****************************************************************************
// $Id: test_allocator_standalone.cc,v 1.2 2004/09/15 00:25:23 spencer Exp $
//
// This test routine was created as a standalone version from a library test
// case I had.
static const char *st_ID __attribute__((unused)) =
"$Id: test_allocator_standalone.cc,v 1.2 2004/09/15 00:25:23 spencer Exp $";
#include <string>
#include <map>
#include <vector>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
#include <sys/time.h>
using std::cout;
using std::endl;
//=============================================================================
// The allocator base class
template<template<class> class Allocator_>
class AllocatorBase
{
protected:
~AllocatorBase()
{}
public:
// :: --------------------------------------------------------------------
// :: Free Store Operators
// Allocate memory for a new object
void *operator new(const size_t size)
{
return st_alloc.allocate(size);
};
// Deallocate memory for an existing object that has been allocated with
// the operator new above.
void operator delete(void * const obj,
const size_t size)
{
st_alloc.deallocate(static_cast<char *>(obj), size);
}
// Allocate memory for an array of new objects
void *operator new[](const size_t size)
{
return st_alloc.allocate(size);
}
// Deallocate memory for an array of existing objects that was allocated with
// the operator new[] above
void operator delete[](void * const array,
const size_t size)
{
// Again, we just use the standard allocator
return st_alloc.deallocate(static_cast<char *>(array), size);
}
// Placement new is still supported, of course!
//
// See [lib.new.delete.placement] in the standard.
//
void *operator new(const size_t size, void * const ptr) throw()
{
return ptr;
}
// And placement delete (a no-op)
//
// See [lib.new.delete.placement] in the standard.
//
void operator delete(void * const ptr, void *) throw()
{}
private:
// :: --------------------------------------------------------------------
// :: Data Members
// The actual allocator itself is static
typedef Allocator_<char> RealAllocator;
static RealAllocator st_alloc;
};
// Instantiation of the allocator
template<template<class> class Allocator_>
typename AllocatorBase<Allocator_>::RealAllocator
AllocatorBase<Allocator_>::st_alloc;
typedef AllocatorBase<std::allocator> StdAllocatorBase;
//=============================================================================
// This object is used to test the allocator routines. It's a template so we
// can swap the routines in and out, making two different versions simply.
template<typename BaseClass, unsigned int extraSize>
class ObjTemplate : public BaseClass
{
public:
ObjTemplate()
: m_number(st_count++)
{}
private:
unsigned int m_number;
char m_extra[extraSize + 1];
static unsigned int st_count;
};
template<typename BaseClass, unsigned int extraSize>
unsigned int ObjTemplate<BaseClass, extraSize>::st_count = 0U;
//=============================================================================
// Timing Results Types
// Helper for holding baseline times for speed tests
typedef std::map<size_t, double> BaselineIndex;
// numberOfSlots -> multiple (speedup factor)
typedef std::map<size_t, double> SlotsToFactor;
// sizeof(type) -> SlotsToFactor
typedef std::map<size_t, SlotsToFactor> ResultsTable;
//=============================================================================
// The tests
namespace
{
const unsigned int count = 1000000;
const double nano = 1000000000.0;
}
//-----------------------------------------------------------------------------
// Speed test with alloc/free toggle. Returns speed as a multiple of
// baseline. Sets baseline if not set.
template<typename Object>
double
test02(BaselineIndex &index)
{
cout << "Testing flip-flop speed (" << __PRETTY_FUNCTION__ << ')' << endl
<< " with sizeof(type) == " << sizeof(Object) << endl;
struct timespec start;
struct timespec end;
clock_gettime(CLOCK_REALTIME, &start);
for(unsigned int i = 0; i < count; ++i) {
delete new Object;
}
clock_gettime(CLOCK_REALTIME, &end);
// How long?
const double diff =
(double(end.tv_sec) + double(end.tv_nsec) / nano)
- (double(start.tv_sec) + double(start.tv_nsec) / nano);
// If we're the baseline, record this time
const size_t numSlots = 1;
double factor;
if(index.find(numSlots) == index.end()) {
index[numSlots] = diff;
factor = 1.0;
} else {
factor = index[numSlots] / diff;
}
const double per = diff / count;
cout << " " << diff << " for " << count
<< " iterations: " << per << " seconds each"
<< endl << " " << factor << " x baseline" << endl;
return factor;
}
//-----------------------------------------------------------------------------
// Speed test with reandomization. numSlots must be a power of two.
// Returns speed as a multiple of baseline. Sets baseline if not set.
template<typename Object, unsigned int numSlots>
double
test03(BaselineIndex &index)
{
cout << "Randomized alloc/free speed test ("
<< __PRETTY_FUNCTION__ << ')' << endl
<< " with " << numSlots << " slots and sizeof(type) == "
<< sizeof(Object) << ':' << endl;
// Start off with a bunch of empty slots
std::vector<Object *> slot(numSlots, NULL);
struct timespec start;
struct timespec end;
clock_gettime(CLOCK_REALTIME, &start);
for(unsigned int i = 0; i < count; ++i) {
// Pick a random slot
const unsigned int s = rand() % numSlots;
if(slot[s] != NULL) {
delete slot[s];
slot[s] = NULL;
} else {
slot[s] = new Object;
}
}
clock_gettime(CLOCK_REALTIME, &end);
// Clean up all the slots
for(unsigned int s = 0; s < slot.size(); ++s) {
delete slot[s];
}
// How long?
const double diff =
(double(end.tv_sec) + double(end.tv_nsec) / nano)
- (double(start.tv_sec) + double(start.tv_nsec) / nano);
// If we're the baseline, record this time
double factor;
if(index.find(numSlots) == index.end()) {
index[numSlots] = diff;
factor = 1.0;
} else {
factor = index[numSlots] / diff;
}
// Log it
const double per = diff / count;
cout << " " << diff << " for " << count
<< " iterations: " << per << " seconds each"
<< endl << " " << factor << " x baseline" << endl;
return factor;
}
//-----------------------------------------------------------------------------
// Run a variety of speed tests with a given object type
template<typename Object>
SlotsToFactor
speedTest(BaselineIndex &index)
{
// These all end up in the results table, if any
SlotsToFactor table;
// Compute the results and store them. The numbers here are the number of
// slots.
table[1] = test02<Object>(index);
table[8] = test03<Object, 8>(index);
table[64] = test03<Object, 64>(index);
table[1024] = test03<Object, 1024>(index);
table[8192] = test03<Object, 8192>(index);
table[1024 * 1024] = test03<Object, 1024 * 1024>(index);
return table;
}
//=============================================================================
// Types we'll use to test
// A very small object
class EmptyBase {};
typedef ObjTemplate<EmptyBase, 0> NormalObj;
typedef ObjTemplate<StdAllocatorBase, 0> Obj;
// Now big versions of the object but one that still fits in the allocator's
// pools.
static const size_t bigExtra = 128 - sizeof(unsigned int) - 1;
typedef ObjTemplate<EmptyBase, bigExtra> BigNormalObj;
typedef ObjTemplate<StdAllocatorBase, bigExtra> BigObj;
// Now huge versions of the object that don't fit in the allocator's pools.
static const size_t hugeExtra = 1024 - sizeof(unsigned int) - 1;
typedef ObjTemplate<EmptyBase, hugeExtra> HugeNormalObj;
typedef ObjTemplate<StdAllocatorBase, hugeExtra> HugeObj;
//-----------------------------------------------------------------------------
// Driver
int
main()
{
cout << "Standalone mode" << endl;
// We're going to collect results from the speed tests
ResultsTable results;
// Some speed tests. The first in each series is made the baseline.
{
cout << "-----------------------------------------------------------"
<< endl
<< "Small object test" << endl;
BaselineIndex baseline;
speedTest<NormalObj>(baseline);
speedTest<Obj>(baseline);
results[sizeof(Obj)] = speedTest<Obj>(baseline);
cout << endl;
}
{
cout << "-----------------------------------------------------------"
<< endl
<< "Medium object test" << endl;
BaselineIndex baseline;
speedTest<BigNormalObj>(baseline);
results[sizeof(BigObj)] = speedTest<BigObj>(baseline);
cout << endl;
}
{
cout << "-----------------------------------------------------------"
<< endl
<< "Large object test" << endl;
BaselineIndex baseline;
speedTest<HugeNormalObj>(baseline);
results[sizeof(HugeObj)] = speedTest<HugeObj>(baseline);
cout << endl;
}
// Dump the speed test results
if(getenv("GLIBCXX_FORCE_NEW") != NULL) {
cout << "GLIBCXX_FORCE_NEW detected" << endl;
}
cout
<< "Speed test results as number of times faster than baseline:"
<< endl
<< endl;
for(ResultsTable::const_iterator size = results.begin();
size != results.end(); ++size) {
const SlotsToFactor &row = size->second;
// Banner?
if(size == results.begin()) {
cout << " Number of slots" << endl;
cout << "Size ";
for(SlotsToFactor::const_iterator it = row.begin();
it != row.end(); ++it) {
cout << std::setw(7) << it->first << ' ';
}
cout << endl;
cout << "---------- ";
for(SlotsToFactor::const_iterator it = row.begin();
it != row.end(); ++it) {
cout << "------- ";
}
cout << endl;
}
// Print data
cout << std::setw(4) << size->first << " bytes ";
for(SlotsToFactor::const_iterator it = row.begin();
it != row.end(); ++it) {
cout << std::setw(7) << std::setprecision(5)
<< it->second << ' ';
}
cout << endl;
}
return 0;
}
//*****************************************************************************
More information about the Libstdc++
mailing list