5

私はいくつかの文字列プール アロケーターのパフォーマンスをテストしていました。ここで示した、サブ割り当てを呼び出しVirtual­Allocて分割するものと、標準 C++ を使用した同様の実装 (Win32 API を直接呼び出さない) とnew[].

Virtual­AllocC++ よりもオーバーヘッドが少ないはずだと思っていたので、バージョンが高速になることを期待していました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 以降では、Windows API を呼び出して CRT ヒープからストレージを割り当てるoperator new[]だけです。Visual C++ 2012 より前のバージョンでは、各 CRT には独自のヒープがあり、. Visual C++ 2012 では、CRT は 経由で取得したプロセス ヒープを使用します。パフォーマンスの観点からは、どのヒープが使用されても問題ありません。mallocHeapAllocHeapCreateGetProcessHeap

VirtualAllocメモリのページをプロセスの仮想アドレス空間にマップするために使用されます。この関数は、ページ全体を制御する必要がある場合に使用されます。たとえば、実行可能コードを保持するためにストレージを割り当てたい場合は、VirtualAllocそのストレージのアクセス許可を変更して実行できるようにするために、 を使用する必要があります。 VirtualAlloc汎用メモリ割り当て用に最適化されていません。

そのためには、アドレス空間の大きな領域を一度にマップし、そのマップされたアドレス空間から割り当て要求を処理するヒープが必要です。ヒープは、割り当てが要求されるたびに仮想ページをマップおよびマップ解除する必要はありません (また、重要なことに、ヒープは割り当てが実行されるたびにメモリをゼロにする必要はありません)。

元のベンチマークを実行すると、次の結果が得られます。

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

の使用法を に置き換えましVirtualAllocHeapAlloc。これを行うために、 を使用してアロケータのプライベート ヒープを作成し、このプライベート ヒープへの呼び出しと呼び出しHeapCreate(0, 0, 0)を置き換えました。(上記で説明したように、プロセス ヒープを使用していないことに注意してください。そのため、ここでもそのヒープを使用すると、アロケーターのパフォーマンスが変化する可能性があります。) 変更したアロケーターの結果は次のとおりです。VirtualAllocVirtualFreeHeapAllocHeapFreenew[]new[]

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

うーん、それは非常に残念です!カスタム アロケーターのパフォーマンスは 21% 向上しましたが、それでもnew[]. どうしたの?

プロファイラーは問題の原因を指摘してくれました。ベンチマークはリンゴとオレンジを比較しています。new[]ベースのアロケータは文字列のコピーに使用しwmemcpyますが、VirtualAllocベースのアロケータは を使用しますlstrcpynwmemcpyは、組み込み形式を持つ を呼び出すだけmemcpyなので、非常に高速な組み込み形式で完全にインライン化できます。 lstrcpynインライン化できない Windows API 関数です。あなたのVirtualAllocベースのアロケーターにはチャンスがありません!

lstrcpynの使用をに置き換えましたwmemcpy。結果は次のとおりです。

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

そして、これらは私たちが期待する結果です:おそらく と を介して呼び出すオーバーヘッドが小さいため、少しだけ遅くなりますが、ほぼ同じように動作しnew[]ます。operator newmalloc

于 2013-02-23T04:04:19.013 に答える
7

は一度にかなりの量のメモリに対して(または、より可能性が高い)へのnew1 つの呼び出しを行い、それを へのいくつかの呼び出しに使用します。同様に、より多くのメモリが一度に解放されるため、を使用してメモリを解放する場合は よりも高速です。VirtualAllocHeapAllocnewVirtualAllocdeleteVirtualFree

を使用するのとまったく同じです。確かに、一度に 1 ギガバイトを読み取ると、何億回も呼び出すよりfgetcもかなり高速になる可能性がありますが、一度に 1 バイトずつ読み取ると、を使用すると、一度に複数 (おそらく 4KB) のデータが読み取られ、そのバッファーが空になるまで一度に 1 文字ずつ読み込まれます。ReadFileReadFilefgetcReadFilefgetc

于 2013-02-01T00:58:27.690 に答える
2

そのため、@ JamesMcNellisは主な問題、つまり、ベースのプールアロケータでlstrcpynW使用されていたという事実をVirtualAlloc、代わりwmemcpyにベースのプールアロケータで使用されていることを発見しましたnew[]

元のコードを変更し、を均一に使用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 に答える