So, @JamesMcNellis found the main problem , i.e. the fact that it was lstrcpynWused in the pool allocator VirtualAlloc, instead it wmemcpywas used in the pool allocator new[].
I changed the source code with uniform use wmemcpyand skipped tests several times and calculated the average execution time for each test (excluding the first run).
I also added a pool allocator to the test tests HeapAllocand a simple one vector<wstring>.
Now the results:
--- Tests summary ---
VirtualAlloc : 781.671 ms
HeapAlloc : 806.597 ms
new[] : 889.792 ms
STL strings : 1491.36 ms
So it seems the fastest (as expected). VirtualAlloc
( VS2010 SP1/VC10):
#include <string.h>
#include <wchar.h>
#include <algorithm>
#include <exception>
#include <iostream>
#include <new>
#include <ostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <windows.h>
using namespace std;
class StringPoolUsingVirtualAlloc
{
public:
StringPoolUsingVirtualAlloc()
: m_pchNext(nullptr),
m_pchLimit(nullptr),
m_phdrCur(nullptr)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
m_dwGranularity = static_cast<DWORD>(
RoundUp( sizeof(HEADER) + MIN_CBCHUNK, si.dwAllocationGranularity
));
}
~StringPoolUsingVirtualAlloc()
{
HEADER* phdr = m_phdrCur;
while (phdr)
{
HEADER * phdrPrev = phdr->m_phdrPrev;
VirtualFree(phdr, 0, MEM_RELEASE);
phdr = phdrPrev;
}
}
const wchar_t* DuplicateString(const wstring& source)
{
return AllocString(source.c_str(), source.c_str() + source.length());
}
private:
union HEADER
{
struct
{
HEADER* m_phdrPrev;
SIZE_T m_cb;
};
wchar_t alignment;
};
enum
{
MIN_CBCHUNK = 32000,
MAX_CHARALLOC = 1024*1024
};
wchar_t* m_pchNext;
wchar_t* m_pchLimit;
HEADER* m_phdrCur;
DWORD m_dwGranularity;
static SIZE_T RoundUp(SIZE_T cb, SIZE_T units)
{
return ((cb + units - 1) / units) * units;
}
wchar_t* AllocString(const wchar_t* pchBegin, const wchar_t* pchEnd)
{
SIZE_T cchTotal = pchEnd - pchBegin + 1;
if (cchTotal > MAX_CHARALLOC)
throw length_error("String too big.");
wchar_t* psz = m_pchNext;
if (m_pchNext + cchTotal <= m_pchLimit)
{
m_pchNext += cchTotal;
wmemcpy(psz, pchBegin, cchTotal);
return psz;
}
SIZE_T cbAlloc = RoundUp(cchTotal * sizeof(wchar_t) + sizeof(HEADER), m_dwGranularity);
BYTE* pbNext = reinterpret_cast<BYTE*>(
VirtualAlloc(nullptr, cbAlloc, MEM_COMMIT, PAGE_READWRITE));
if (pbNext == nullptr)
throw bad_alloc();
m_pchLimit = reinterpret_cast<wchar_t*>(pbNext + cbAlloc);
HEADER* phdrCur = reinterpret_cast<HEADER*>(pbNext);
phdrCur->m_phdrPrev = m_phdrCur;
phdrCur->m_cb = cbAlloc;
m_phdrCur = phdrCur;
m_pchNext = reinterpret_cast<wchar_t*>(phdrCur + 1);
return AllocString(pchBegin, pchEnd);
}
StringPoolUsingVirtualAlloc(const StringPoolUsingVirtualAlloc &);
StringPoolUsingVirtualAlloc & operator=(const StringPoolUsingVirtualAlloc &);
};
class StringPoolUsingHeapAlloc
{
public:
StringPoolUsingHeapAlloc()
: m_pchNext(nullptr),
m_pchLimit(nullptr),
m_phdrCur(nullptr)
{
m_heap = HeapCreate(0, 0, 0);
if (m_heap == nullptr)
throw runtime_error("Can't create an heap with HeapCreate().");
SYSTEM_INFO si;
GetSystemInfo(&si);
m_dwGranularity = static_cast<DWORD>(
RoundUp( sizeof(HEADER) + MIN_CBCHUNK, si.dwAllocationGranularity
));
}
~StringPoolUsingHeapAlloc()
{
HEADER* phdr = m_phdrCur;
while (phdr)
{
HEADER * phdrPrev = phdr->m_phdrPrev;
HeapFree(m_heap, 0, phdr);
phdr = phdrPrev;
}
HeapDestroy(m_heap);
}
const wchar_t* DuplicateString(const wstring& source)
{
return AllocString(source.c_str(), source.c_str() + source.length());
}
private:
union HEADER
{
struct
{
HEADER* m_phdrPrev;
SIZE_T m_cb;
};
wchar_t alignment;
};
enum
{
MIN_CBCHUNK = 32000,
MAX_CHARALLOC = 1024*1024
};
HANDLE m_heap;
wchar_t* m_pchNext;
wchar_t* m_pchLimit;
HEADER* m_phdrCur;
DWORD m_dwGranularity;
static SIZE_T RoundUp(SIZE_T cb, SIZE_T units)
{
return ((cb + units - 1) / units) * units;
}
wchar_t* AllocString(const wchar_t* pchBegin, const wchar_t* pchEnd)
{
SIZE_T cchTotal = pchEnd - pchBegin + 1;
if (cchTotal > MAX_CHARALLOC)
throw length_error("String too big.");
wchar_t* psz = m_pchNext;
if (m_pchNext + cchTotal <= m_pchLimit)
{
m_pchNext += cchTotal;
wmemcpy(psz, pchBegin, cchTotal);
return psz;
}
SIZE_T cbAlloc = RoundUp(cchTotal * sizeof(wchar_t) + sizeof(HEADER), m_dwGranularity);
BYTE* pbNext = static_cast<BYTE*>( HeapAlloc(m_heap, 0, cbAlloc) );
if (pbNext == nullptr)
throw bad_alloc();
m_pchLimit = reinterpret_cast<wchar_t*>(pbNext + cbAlloc);
HEADER* phdrCur = reinterpret_cast<HEADER*>(pbNext);
phdrCur->m_phdrPrev = m_phdrCur;
phdrCur->m_cb = cbAlloc;
m_phdrCur = phdrCur;
m_pchNext = reinterpret_cast<wchar_t*>(phdrCur + 1);
return AllocString(pchBegin, pchEnd);
}
StringPoolUsingHeapAlloc(const StringPoolUsingHeapAlloc &);
StringPoolUsingHeapAlloc & operator=(const StringPoolUsingHeapAlloc &);
};
class StringPoolUsingNew
{
public:
StringPoolUsingNew()
: m_pchNext(NULL),
m_pchLimit(NULL),
m_currChunk(NULL)
{
}
~StringPoolUsingNew()
{
for (auto it = m_chunks.begin(); it != m_chunks.end(); ++it)
delete *it;
}
const wchar_t* DuplicateString(const wstring& source)
{
return AllocString(source.c_str(), source.c_str() + source.length());
}
private:
class Chunk
{
public:
explicit Chunk(size_t maxCharCount)
{
m_data = new wchar_t[maxCharCount];
m_maxCharCount = maxCharCount;
}
~Chunk()
{
delete [] m_data;
}
wchar_t* Begin() { return m_data; }
const wchar_t* Begin() const { return m_data; }
size_t Length() const { return m_maxCharCount; }
private:
Chunk(const Chunk&);
Chunk& operator=(const Chunk&);
wchar_t * m_data;
size_t m_maxCharCount;
};
static const size_t kMinChunkCharCount = 16000;
static const size_t kMaxCharAlloc = 1024*1024;
wchar_t* m_pchNext;
wchar_t* m_pchLimit;
Chunk* m_currChunk;
vector<Chunk*> m_chunks;
wchar_t* AllocString(const wchar_t* pchBegin, const wchar_t* pchEnd)
{
const size_t cchTotal = pchEnd - pchBegin + 1;
if (cchTotal > kMaxCharAlloc)
throw length_error("String too big.");
wchar_t* dest = m_pchNext;
if (m_pchNext + cchTotal <= m_pchLimit)
{
m_pchNext += cchTotal;
const size_t copyCount = cchTotal - 1;
if (copyCount != 0)
wmemcpy(dest, pchBegin, copyCount);
dest[copyCount] = L'\0';
return dest;
}
const size_t newChunkSize = max(cchTotal, kMinChunkCharCount);
Chunk* newChunk = new Chunk(newChunkSize);
m_chunks.push_back(newChunk);
m_pchNext = newChunk->Begin();
m_pchLimit = newChunk->Begin() + newChunk->Length();
m_currChunk = newChunk;
return AllocString(pchBegin, pchEnd);
}
StringPoolUsingNew(const StringPoolUsingNew&);
StringPoolUsingNew& operator=(const StringPoolUsingNew&);
};
class StringPoolVectorOfString
{
public:
StringPoolVectorOfString()
{
}
~StringPoolVectorOfString()
{
}
const wchar_t* DuplicateString(const wstring& source)
{
m_strings.push_back(source);
return m_strings.back().c_str();
}
private:
vector<wstring> m_strings;
StringPoolVectorOfString(const StringPoolVectorOfString&);
StringPoolVectorOfString& operator=(const StringPoolVectorOfString&);
};
long long Counter()
{
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return li.QuadPart;
}
long long Frequency()
{
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
return li.QuadPart;
}
template <typename Container>
void PrintFirst(const Container & c, const size_t firstN)
{
const size_t n = min(firstN, c.size());
for (size_t i = 0; i < n; i++)
wcout << "#" << (i+1) << ": " << c[i] << '\n';
wcout << endl;
}
template <typename Allocator>
void VerifyAllocator(const vector<wstring>& source, const size_t firstN, const char* allocatorName)
{
const size_t n = min(firstN, source.size());
Allocator alloc;
vector<const wchar_t*> v;
for (size_t i = 0; i < n; i++)
{
v.push_back( alloc.DuplicateString(source[i]) );
}
wcout << allocatorName << " :\n";
PrintFirst(v, n);
}
template <typename Allocator>
double TestAllocator(const vector<wstring>& source, const char* allocatorName)
{
wcout << "Testing " << allocatorName << " : ";
long long start = Counter();
{
Allocator alloc;
vector<const wchar_t*> v;
for (auto it = source.begin(); it != source.end(); ++it)
{
v.push_back( alloc.DuplicateString(*it) );
}
}
long long finish = Counter();
const double time = (finish - start) * 1000.0 / Frequency();
wcout << time << " ms\n";
return time;
}
double Average(const vector<double>& data)
{
if (data.empty())
throw invalid_argument("Can't compute average of empty vector.");
double sum = data[0];
const size_t count = data.size();
for (size_t i = 1; i < count; ++i)
{
sum += data[i];
}
return (sum / count);
}
int main()
{
static const int kExitOk = 0;
static const int kExitError = 1;
try
{
wcout << '\n';
wcout << "Testing VirtualAlloc vs. HeapAlloc vs. new[] allocators vs STL strings.\n";
wcout << "-----------------------------------------------------------------------\n\n";
wcout << "Preparing some strings for testing...\n";
const auto shuffled = []() -> vector<wstring>
{
const wstring lorem[] = {
L"Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
L"Maecenas porttitor congue massa. Fusce posuere, magna sed",
L"pulvinar ultricies, purus lectus malesuada libero,",
L"sit amet commodo magna eros quis urna.",
L"Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus.",
L"Pellentesque habitant morbi tristique senectus et netus et",
L"malesuada fames ac turpis egestas. Proin pharetra nonummy pede.",
L"Mauris et orci."
};
vector<wstring> v;
#ifdef _DEBUG
static const int kLoopCount = 10;
#else
static const int kLoopCount = 400*1000;
#endif
for (long long i = 0; i < kLoopCount; ++i)
{
for (auto it = begin(lorem); it != end(lorem); ++it)
{
v.push_back((*it) + L" (#" + to_wstring(i) + L")");
}
}
random_shuffle(v.begin(), v.end());
return v;
}();
wcout << "Total string count: " << shuffled.size() << "\n\n";
wcout << "Some verification output ...\n\n";
wcout << "Original array of strings :\n";
PrintFirst(shuffled, 5);
VerifyAllocator<StringPoolUsingVirtualAlloc>(shuffled, 5, "VirtualAlloc");
VerifyAllocator<StringPoolUsingHeapAlloc>(shuffled, 5, "HeapAlloc");
VerifyAllocator<StringPoolUsingNew>(shuffled, 5, "new[]");
VerifyAllocator<StringPoolVectorOfString>(shuffled, 5, "vector<wstring>");
vector<double> timeVirtualAlloc;
vector<double> timeHeapAlloc;
vector<double> timeNew;
vector<double> timeStlString;
static const int kTestCount = 10;
wcout << "\nWarm up... discard first tests execution.\n";
TestAllocator<StringPoolUsingVirtualAlloc>(shuffled, "VirtualAlloc");
TestAllocator<StringPoolUsingHeapAlloc>(shuffled, "HeapAlloc");
TestAllocator<StringPoolUsingNew>(shuffled, "new[]");
TestAllocator<StringPoolVectorOfString>(shuffled, "vector<wstring>");
for (int i = 0; i < kTestCount; i++)
{
wcout << "\nTest loop #" << (i+1) << ":\n";
timeVirtualAlloc.push_back( TestAllocator<StringPoolUsingVirtualAlloc>(shuffled, "VirtualAlloc") );
timeHeapAlloc.push_back( TestAllocator<StringPoolUsingHeapAlloc>(shuffled, "HeapAlloc") );
timeNew.push_back( TestAllocator<StringPoolUsingNew>(shuffled, "new[]") );
timeStlString.push_back( TestAllocator<StringPoolVectorOfString>(shuffled, "vector<wstring>") );
}
wcout << "\n\n--- Tests summary ---\n";
wcout << "VirtualAlloc : " << Average(timeVirtualAlloc) << " ms\n";
wcout << "HeapAlloc : " << Average(timeHeapAlloc) << " ms\n";
wcout << "new[] : " << Average(timeNew) << " ms\n";
wcout << "STL strings : " << Average(timeStlString) << " ms\n";
wcout << '\n';
return kExitOk;
}
catch (const exception& e)
{
wcerr << "\n*** ERROR: " << e.what() << '\n';
return kExitError;
}
}