5

我正在测试一些字符串池分配器的性能:我考虑了这里介绍的调用Virtual­Alloc然后划分子分配的分配器,以及使用标准 C++(不直接调用任何 Win32 API)和new[].

我希望Virtual­Alloc版本更快,因为我认为开销应该比 C++ 少new[];但我观察到的结果是相反的:使用new[]似乎比使用较低级别的Virtual­Alloc.

我跑了几次测试(代码是用VS2010 SP1编译的),输出是这样的:

String pool using VirtualAlloc: 1280.07 ms
String pool using new[]: 799.193 ms

为什么是这样?为什么new[]似乎比 快VirtualAlloc

测试源代码如下:

////////////////////////////////////////////////////////////////////////////
// Testing VirtualAlloc vs. new[].
////////////////////////////////////////////////////////////////////////////


#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;


//--------------------------------------------------------------------------
// String pool allocator using VirtualAlloc, based on this:
// http://blogs.msdn.com/oldnewthing/archive/2005/05/19/420038.aspx
//--------------------------------------------------------------------------
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;
        }
    }

    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;
            lstrcpynW(psz, pchBegin, static_cast<int>(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 &);
};


//--------------------------------------------------------------------------
// String pool allocator that uses standard C++ (no Win32 stuff) and new[].
//--------------------------------------------------------------------------
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;
    }

    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&);
};


//------------------------------------------------------------------------
//                          Perf Measurement
//------------------------------------------------------------------------

long long Counter() 
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return li.QuadPart;
}

long long Frequency() 
{
    LARGE_INTEGER li;
    QueryPerformanceFrequency(&li);
    return li.QuadPart;
}

void PrintTime(long long start, long long finish, const char * s) 
{
    cout << s << ": " << (finish - start) * 1000.0 / Frequency() << " ms" << endl;
}


//--------------------------------------------------------------------------
// Test
//--------------------------------------------------------------------------
int main()
{
    static const int kExitOk = 0;
    static const int kExitError = 1;
    try
    {
        long long start = 0;
        long long finish = 0;

        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;
            for (long long i = 0; i < 400*1000; ++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;
        }();

        start = Counter();
        {
            StringPoolUsingVirtualAlloc pool;
            vector<const wchar_t*> v;
            for (auto it = shuffled.begin(); it != shuffled.end(); ++it)
            {
                v.push_back( pool.DuplicateString(*it) );
            }
        }
        finish = Counter();
        PrintTime(start, finish, "String pool using VirtualAlloc");

        start = Counter();
        {
            StringPoolUsingNew pool;
            vector<const wchar_t*> v;
            for (auto it = shuffled.begin(); it != shuffled.end(); ++it)
            {
                v.push_back( pool.DuplicateString(*it) );
            }
        }
        finish = Counter();
        PrintTime(start, finish, "String pool using new[]");

        return kExitOk;
    }
    catch (const exception& e)
    {
        cerr << "*** ERROR: " << e.what() << endl;
        return kExitError;
    }
}

////////////////////////////////////////////////////////////////////////////
4

3 回答 3

14

是的,new[]重复调用比重复调用要快得多VirtualAlloc

首先,重要的是要了解做什么new T[N]。操作员通过new调用来分配存储空间operator new[]。至少从 Visual C++ 2010 开始,operator new[]只需调用malloc,它调用 Windows APIHeapAlloc从 CRT 堆分配存储。在 Visual C++ 2012 之前,每个 CRT 都有自己的堆,通过HeapCreate. 在 Visual C++ 2012 中,CRT 使用进程堆,通过GetProcessHeap. 从性能的角度来看,使用哪个堆并不重要。

VirtualAlloc用于将内存页映射到进程的虚拟地址空间。当您需要控制整个页面时使用此功能。例如,如果要分配存储来保存可执行代码,则需要使用VirtualAlloc,以便可以更改该存储的权限以允许执行。 VirtualAlloc未针对通用内存分配进行优化。

为此,您需要一个堆,它一次映射一个大的地址空间区域,然后为来自该映射地址空间的分配请求提供服务。每次请求分配时,堆不必映射和取消映射虚拟页面(同样重要的是,每次执行分配时堆不必将内存归零)。

当我运行您的原始基准测试时,我得到以下结果:

String pool using VirtualAlloc: 1162.45 ms
String pool using new[]: 625.842 ms

VirtualAlloc我用替换了你的用法HeapAlloc。为此,我使用 为分配器创建了一个私有堆HeapCreate(0, 0, 0),然后用对这个私有堆的调用和来自这个私有堆的调用替换了调用VirtualAlloc和调用。(注意我没有使用进程堆,因为正如我上面解释的那样,使用那个堆,所以在这里也使用那个堆可能会改变分配器的性能。)我修改后的分配器的结果如下:VirtualFreeHeapAllocHeapFreenew[]new[]

String pool using HeapAlloc: 919.853 ms
String pool using new[]: 636.515 ms

嗯,这太令人失望了!我们将自定义分配器的性能提高了 21%,但仍然比new[]. 那是怎么回事?

分析器很有帮助地指出了问题所在:您的基准是比较苹果和橙子。new[]基于 - 的分配器用于复制wmemcpy字符串,但VirtualAlloc基于 - 的分配器使用lstrcpyn. wmemcpy简单的调用memcpy,它有一个内在的形式,所以它可以与疯狂的快速内在形式完全内联。 lstrcpyn是一个不能内联的 Windows API 函数。你VirtualAlloc的 -based 分配器没有机会!

我替换了lstrcpynwith的使用wmemcpy。结果如下:

String pool using HeapAlloc: 636.149 ms
String pool using new[]: 655.479 ms

这些是我们所期望的结果:它们的性能大致相同,new[]只是稍微慢了一点,这可能是因为通过operator newand调用的开销很小malloc

于 2013-02-23T04:04:19.013 回答
7

因为new会一次调用VirtualAlloc(或者,更有可能,HeapAlloc)以获得相当多的内存,然后将其用于多次调用new,其中调用VirtualAlloc将完全按照您的要求进行,准确分配您要求的内容。同样,使用它释放内存时delete比 快VirtualFree,因为一次释放了大量内存。

fgetc这与 using is faster than完全相同ReadFile- 当然,如果您一次读取 1 GB,ReadFile可能比调用fgetcgazillion 次要快得多,但是如果您一次读取一个字节,ReadFile则系统上会比using fgetc,它将一次读取多个(可能是 4KB)数据,然后一次从该缓冲区中取出一个字符,直到它为空。

于 2013-02-01T00:58:27.690 回答
2

因此,@JamesMcNellis 发现了主要问题,即在基于 -的池分配器lstrcpynW中使用的事实,而不是在基于 - 的池分配器中使用的事实。VirtualAllocwmemcpynew[]

我修改了原代码,统一使用wmemcpy,多次运行测试,计算每个测试的平均执行时间(不包括第一次运行)。

我还添加了一个HeapAlloc基于 - 的池分配器和一个简单vector<wstring>的基准。

现在结果是:

--- Tests summary ---
VirtualAlloc : 781.671 ms
HeapAlloc    : 806.597 ms
new[]        : 889.792 ms
STL strings  : 1491.36 ms

所以,VirtualAlloc似乎是最快的(如预期的那样)。

可编译代码如下(使用 VS2010 SP1 / VC10 构建):

////////////////////////////////////////////////////////////////////////////
// Testing VirtualAlloc vs. HeapAlloc vs. new[] vs. STL strings.
////////////////////////////////////////////////////////////////////////////


#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;


//--------------------------------------------------------------------------
// String pool allocator using VirtualAlloc, based on this:
// http://blogs.msdn.com/oldnewthing/archive/2005/05/19/420038.aspx
//--------------------------------------------------------------------------
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 &);
};


//--------------------------------------------------------------------------
// String pool allocator using HeapAlloc, 
// based on the VirtualAlloc allocator.
//--------------------------------------------------------------------------
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 &);
};


//--------------------------------------------------------------------------
// String pool allocator that uses standard C++ (no Win32 stuff) and new[].
//--------------------------------------------------------------------------
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&);
};


//--------------------------------------------------------------------------
// This is just a simple vector<wstring>, to compare performance of this 
// simple and easy approach vs. the other pool allocators.
//--------------------------------------------------------------------------
class StringPoolVectorOfString
{
public:

    StringPoolVectorOfString()
    {
    }

    ~StringPoolVectorOfString()
    {
    }

    const wchar_t* DuplicateString(const wstring& source)
    {
        m_strings.push_back(source);
        return m_strings.back().c_str();
    }

private:
    // Simplest case: a STL vector of STL strings
    vector<wstring> m_strings;

    StringPoolVectorOfString(const StringPoolVectorOfString&);
    StringPoolVectorOfString& operator=(const StringPoolVectorOfString&);
};


//------------------------------------------------------------------------
//                          Perf Measurement
//------------------------------------------------------------------------

long long Counter() 
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return li.QuadPart;
}

long long Frequency() 
{
    LARGE_INTEGER li;
    QueryPerformanceFrequency(&li);
    return li.QuadPart;
}


//--------------------------------------------------------------------------
// Tests
//--------------------------------------------------------------------------

// Prints the first N strings in a vector-like container.
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;
}

// Prints the first N strings using the specified allocator.
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);
}

// Tests a given allocator, returning the execution time in ms.
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(); // ms

    wcout << time << " ms\n";
    return time;
}

// Calculates the average in a vector of doubles.
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);
}

// App entry-point ("test driver").
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;

        // First execution tests are discarded.
        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>");

        // Run the tests several times and compute the average for each test.
        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>") );
        }

        // Print average times
        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;
    }
}


////////////////////////////////////////////////////////////////////////////
于 2013-02-28T17:57:09.623 回答