プログラム全般」カテゴリーアーカイブ

データ圧縮 zlib と gzip と zip (zlib で zip にアクセスする)

Windows を除いてほとんどのプラットフォームには zlib が入っており、
データの圧縮に利用することが出来ます。

zlib

メモリ上のイメージを圧縮するだけなら下記のとおり。

// [1]
// 圧縮データが入るバッファの確保
uLong  buffer_size= compressBound( src_size );
Bytef* buffer= memory_alloc( buffer_size );

// 圧縮
compress( buffer, &buffer_size, src_memory, src_size );

// 圧縮後のサイズ
compressed_size= buffer_size;

圧縮率を指定するなら compress() の代わりに compress2() を使います。
展開も同様。

// [2]
uLong buffer_size= uncompressed_size;
uncompress( buffer, &buffer_size, src_memory, src_size );

解凍時は展開後の元のサイズ (src_size) が必要になるので、
圧縮時に保存しておく必要があります。

●ヘッダと動作モード

圧縮されたデータには、先頭にフォーマットを表す最小限のヘッダ、
最後にチェックサム Adler-32 が付加されます。
このヘッダ形式は zlib 独自のもので、gzip や zip 等とは互換性がありません。
チェックサムも一般的な CRC とは異なっています。

ただし圧縮アルゴリズム自体は広く使われている Deflate で、
ヘッダ&フッタが違うだけでデータ本体は gzip/zip などと同じです。
そのため zlib には、gzip のヘッダを用いて読み書きを行うモードがあります。

[1]/[2] の compress()/uncompress() 相当を低レベルな API で書きなおしたのが
下記の例です。

// [3] Compress (zlib)
size_t Compress_zlib( void* dest_memory, size_t dest_size, const void* src_memory, size_t src_size )
{
   z_stream  stream;
   memset( &stream, 0, sizeof(z_stream) );
   stream.next_in= (Bytef*)src_memory;
   stream.avail_in= src_size;
   stream.next_out= (Bytef*)dest_memory;
   stream.avail_out= dest_size;

   deflateInit2( &stream, COMPRESS_LEVEL, Z_DEFLATED, MAX_WBITS, 5, 8, Z_DEFAULT_STRATEGY );
   int result= deflate( &stream, Z_FINISH )
   if( result != ZSTREAM_END && result != Z_OK ){
      // error
      return  0;
   }
   deflateEnd( &stream );

   return  stream.total_out;  // compressed_size
}
// [4] Uncompress (zlib)
size_t Uncompress_zlib( void* dest_memory, size_t dest_size, const void* src_memory, size_t src_size )
{
   z_stream  stream;
   memset( &stream, 0, sizeof(z_stream) );
   stream.next_in= (Bytef*)src_memory;
   stream.avail_in= src_size;
   stream.next_out= (Bytef*)dest_memory;
   stream.avail_out= dest_size;

   inflateInit2( &stream, MAX_WBITS );
   int result= inflate( &stream, Z_FINISH )
   if( result != ZSTREAM_END && result != Z_OK ){
      // error
      return  0;
   }
   inflateEnd( &stream );

   assert( dest_size == stream.total_out );

   return  stream.total_out;  // uncompressed_size
}

deflateInit2() / inflateInit2() の引数のうち、上の [3]/[4] で
MAX_WBITS が入っている所が windowBits の指定場所になります。
windowBits には下記の特殊な用法があります。

(1) windowBits       =   8~15    zlib format
(2) windowBits + 16  =  24~31    gzip format
(3) windowBits + 32  =  40~47    zlib/gzip format (inflate のみ)
(4) -windowBits      =  -8~-15   raw format

(2) のようにパラメータに 16 を加えると gzip フォーマットになります。
チェックサムも CRC-32 です。
例えば上の例 [3]/[4] だと MAX_WBITS の代わりに (MAX_WBITS+16) を与える
ことになります。

(3) inflateInit2() の場合 windowBits に 32 を加えると zlib/gzip の
自動判別ができるようになります。

(4) のように負数を入れると余計なデータが出力されません。
ヘッダも付かずにチェックサム計算も省かれます。
zip のように互換性のないファイル構造でも、データ部分の圧縮と解凍に
zlib を利用出来るようになります。( Method 8 )

まとめると下記の通り。zlib には 3種類の動作モードがあります。

               windowBits      header       checksum
-----------------------------------------------------
zlib format    MAX_WBITS       zlib 形式    Adler-32
gzip format   (MAX_WBITS|16)   gzip 形式    CRC-32
raw format    -MAX_WBITS       無し         無し

● Windows

zlib は Windows でも利用できますが、64bit (x64) では少々問題があります。

zlib のサイズ値に uLong (unsigned long) 型が用いられているので、
同じ 64bit OS でも Windows とそれ以外で型のサイズが変わっています。
Windows は LLP64 なので 32bit、Linux x64, OSX, IOS arm64 は LP64 なので
64bit になります。

例えば上の [3]/[4] のコードは size_t を使用しています。
size_t は x64 で 64bit なので、Windows x64 のみ zlib API に渡すときに
32bit への変換が必要になってしまいます。
互換性の意味でも long はできるだけ避けたいのですが、
Windows x64 では諦めてキャストを使っています。

● zip の構造とデータアクセス

zip はデータ圧縮だけでなくアーカイバの機能も含まれています。
tar + gzip の組み合わせはアーカイブした後に一度に圧縮しますが、
zip は個々のファイルを個別に圧縮しています。

また圧縮はファイルの中身だけで、ヘッダや含まれるファイル情報
(Local Header, Central Directory など) は含まれません。
そのため小さいファイルが大量に含まれるケースでは zip の効率は落ちます。

反面、ファイル単位でランダムアクセスが可能で、必要なファイルだけを
取り出すことができます。
アクセスするためには下記の手順が必要になります。

(1) EOR を検索して、Central Directory の位置を特定する
(2) Central Directory を読み込んで必要なファイルを検索する
(3) (2) からファイルの Local Header の位置が求まるので読み込む
(4) Local Header からデータの位置を求めて特定する。

End of Central Directory record (EOR) の位置は不定なので、
ファイルの終端から逆方向に読み込んで SIGNATURE の検索が必要。
EOR が特定できれば Central Directory の位置とサイズがわかるので
まとめて読み込むことが出来ます。

Central Directory 内の個々の情報は不定長なので、一度先頭から
読み進める必要があります。
Local Header の位置、ファイル名、圧縮前後のファイルサイズ、CRC、
圧縮メソッドなどはここに含まれます。
ディレクトリも個別エントリとして含まれており、サイズは 0 で
ファイル名の最後に ‘/’ が付きます。

Local Header はファイル本体の前に挿入されています。
こちらも長さ不定で、読み込んでみるまでデータの位置が厳密に求まりません。
適当なサイズを指定してとりあえず読み込みます。

Local Header からデータの位置が特定できるので、
実際のデータ部分を zip ファイルからロードできます。
Method が 8 なら圧縮されているため zlib の raw format として解凍できます。
解凍後に zlib の crc32() 等を使って CRC チェックを行います。

デフォルトで用いられている圧縮 Method は 0 と 8 です。
Method 8 が Deflate で Method 0 は非圧縮となっています。

下記の資料を参考にしました。

APPNOTE.TXT – .ZIP File Format Specification

簡単にデータ位置を特定できない点は少々やっかいですが、
Central Directory は先キャッシュ出来ますし、Local header も
データと同時に多めにサイズを指定してまとめて読み込んでしまうことも出来ます。
解凍も zlib が使えますし、広く使われているだけあって zip の
データアクセスは比較的容易にできます。

C++11 Lambda function ラムダ関数

C++11 では関数の中で関数オブジェクトの宣言ができるようになっています。
スクリプト系言語などでもよく見る書き方で、下記は関数を値として代入しています。

void func_test()
{
  auto func_obj= []( int a ){
    return a + 100;
  };

  func_obj( 23 );
}

関数ポインタとの違いは、関数や式の中に実体をインラインで無名宣言できることと、
実行コードだけでなく、オブジェクトの中にデータ領域も併せ持つことです。
要するに関数オブジェクトで、以前だと下記のような書き方をしていました。

void func()
{
  struct LocalFuncT {
     int operator()( int a ){
       return a + 100;
     }
  };
  LocalFuncT func_obj= LocalFuncT();

  func_obj( 23 );
}

ただし関数内で宣言された class ↑は template に渡すことができないなど
制限があります。(VisualC++ ではできました)

C++11 ではこの制限が撤廃されると同時に、Lambda 関数としてより簡単に
宣言して使えるようになっています。

template
struct CallbackObject {
  FT Func;
  CallbackObject( FT func ) : Func( func )
  {
  }
  int operator()( int a )
  {
    return  Func( a );
  }
};
template
CallbackObject CreateCallback( FT func )
{
  return  CallbackObject( func );
}

int s_func( int a )
{
  return a + 200;
}

struct FuncObject {
  int operator()( int a )
  {
     returtn a + 300;
  }
};

void func_test()
{
  CreateCallback( s_func )( 12 );  // pointer
  CreateCallback( FuncObject() )( 12 ); // object
  CreateCallback( []( int a ){ return a + 100; } )( 12 ); // lambda
}

今まで関数オブジェクトを扱っていた部分を Lambda 関数で置き換えられます。
オブジェクトなので引数の型を取り出そうとしてもそのままでは関数の型には
マッチしませんが、operator() は取れるようです。

template
struct CallbackObject {
  TT* This;
  FT  Func;
  CallbackObject( TT* t, FT func ) : This( t ), Func( func ) {}
  int operator()( int a )
  {
    return  (This->*Func)( a );
  }
};
template
CallbackObject CreateCallback( TT* t, FT func )
{
  return  CallbackObject( t, func );
}

struct func_class {
  void func_test()
  {
    auto func_obj2= [=]( int a ){ dump(this); return a + 400; };

    func_obj2( 12 );

    func_obj2.operator()( 12 );

    typedef decltype( func_obj2 )  FUNC_T2;
    CreateCallback( &func_obj2, &FUNC_T2::operator() )( 12 );
  }
};

特徴的なのはクロジャーとして変数を自動的に取り込めることで、
関数オブジェクトのメンバとして格納されています。
実際に vc/gcc でキャプチャなしの場合 struct {} 同様に sizeof() が 1 を返しており、
キャプチャ変数が存在する場合はキャプチャ分のサイズになります。

this はキャプチャ変数とみなされるため、自分自身へアクセスする方法は無いようです。
つまりキャプチャ変数に対する暗黙の “this->” が存在しており、
this も this->this 相当となっています。
キャプチャはコピー ([=]) または参照 ([&]) となり、
move ([&&]?) にはならないようです。

関連エントリ
C++11 Variadic Templates 可変長引数のテンプレート
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照

C++11 Variadic Templates 可変長引数のテンプレート

C++11 では Template で不定個の引数をまとめて扱えるようになっています。

void func00( int a0, float a1, int a2 )
{
}

template
void Exec00( A0... a0 )
{
   func00( a0... );
}

void VT_Test()
{
   Exec00( 1, 2.0f, 3 );
}

例えば上の例は VT_Test() からそのまま Exec00 経由で func00() を
呼び出しています。
func00() の引数の数が変わっても Tempalte の宣言を変える必要がなくなります。

今までこのようなケースでは、Template にマッチさせるために
引数の数だけオーバーロードか特殊化が必要でした。

template
void Exec01( A0 a0 )
{
   func00( a0 );
}
template
void Exec01( A0 a0, A1 a1 )
{
   func00( a0, a1 );
}
template
void Exec01( A0 a0, A1 a1, A2 a2 )
{
   func00( a0, a1, a2 );
}
~

他にも参照で問題が生じます。
↑ Exec01 はどんな引数でもコンパイルが通りますが、↓ func01() のような
参照を受け取る関数に対して意図しないコピーが渡ってしまいます。

void func01( int& a0 )
{
}

↓ Template 側を参照にすれば解決しますが、今度は定数値(右辺値)を
渡すことができず、Exec02( 100 ) などの呼び出しがエラーになります。

template
void Exec02( A0& a0 )
{
~

定数など右辺値に対しては const の参照も必要となるので、結局
引数の数に加えて参照の組みわせの分だけ宣言が必要となります。

template
void Exec03( A0& a0 )
{
   func00( a0 );
}
template
void Exec03( const A0& a0 )
{
   func00( a0 );
}
template
void Exec03( A0& a0, A1& a1 )
{
   func00( a0, a1 );
}
template
void Exec03( const A0& a0, A1& a1 )
{
   func00( a0, a1 );
}
template
void Exec03( A0& a0, const A1& a1 )
{
   func00( a0, a1 );
}
~

C++ の Rvalue Reference は Move operation だけでなく、
↓参照の転送の問題も解決してくれます。

template
void Exec04( A0&& a0 )
{
   func00( std::forward( a0 ) );
}
~

const 無しに右辺値も左辺値も参照を渡すことができるようになりました。
さらに forward をつけると右辺値参照もそのまま転送されます。

可変引数と合わせると非常に簡単で、これひとつで多くの組み合わせに
対応できることになります。

template
void Exec05( A0&&... a0 )
{
   func00( std::forward( a0 )... );
}

様々なプラットフォームをターゲットにしていると、
コンパイラの対応状況が問題になってきます。

iOS/OSX の Xcode や Android NDK は頻繁に更新されており、
比較的新しい clang/gcc が使えるので問題はないようです。

特に Android NDK の r8e に至っては、 toolchain として
gcc 4.4.3/4.6/4.7, clang 3.1/3.2 の 5種類ものコンパイラが含まれており
選択可能となっています。

VisualStudio 2012 は未対応ですが、November 2012 CTP として
Variadic Templates 対応のコンパイラが公開されています。
正式対応もそう遠くないと考えられます。

NaCl pepper_26 の x86 は gcc 4.4.3 なのでまだコンパイルが通りません。
arm は 4.7.3 が使われているようです。

関連エントリ
スレッド同期命令の比較 C++11 とコンパイラ
C++11 Rvalue Reference 右辺値参照

スレッド同期命令の比較 C++11 とコンパイラ

スレッド間の同期を取る方法は複数あります。
OS のカーネルオブジェクト、ライブラリ関数、OS 依存の API など。
他にも CAS のような最低限の atomic 操作を使って Spinlock を作ることができます。
もちろんハードウエアスレッド環境に限ります。

C++11 対応に伴いコンパイラも新しい atomic 命令に移行しています。
例えば gcc の builtin 命令は __sync_ から __atomic_ となり、
clang にも同様に __c11_atomic_ があります。

Windows の場合は OS に Interlocked~ API がありますが、
VC++ が生成する同名の Intrinsic バージョンもあります。
こちらも C++11 に従いメモリバリアを指定できる命令が追加されているようです。

// CAS
gcc      __atomic_compare_exchange_*()
clang    __c11_atomic_compare_exchange_*()
Windows  _InterlockedCompareExchange_*()

gcc 4.7.2 : Built-in functions for memory model aware atomic operations
Clang 3.3 : Clang Language Extensions
msdn : x86 Intrinsics List

↓各種 API による比較です。

(1) Windows             Win8 x86   Win8 x64
-------------------------------------------------------
SpinLock(intrinsic)         5852       6353
CriticalSection            20894      22610
Win32 Mutex              2537901    3790403


(2) Linux              Linux x86   Linux x64   Linux ARM
-------------------------------------------------------
SpinLock(gcc atomic)      12712       10641       27977
pthread mutex             13693       11796       23807


(3) MacOS X         MacOS X x86  MacOS X x64
-------------------------------------------------------
SpinLock(OSAtomic)         9505        9070
pthread mutex            563013      467785

// 実行時間 単位=us

3つのスレッドで同じカウンタをそれぞれ 50000回カウントしています。
100% 競合する状態で、ロック手段による違いを測定しています。

走らせた PC のスペックが異なることと、数値の変動が大きいため
単純な比較ができないのでご注意ください。
同じ OS 内で API による違いだけ見てください。

(1) Windows (Windows 8 + VS2012 Express)

カーネルオブジェクトである Mutex は非常に遅くなっています。
CriticalSection の方が2桁高速ですが、_Interlocked 命令を使った
SpinLock の方が更に速い結果となっています。

(2) Linux (Ubuntu 12.10 / 13.04)

x86/x64 は VMware Player によるテストなのでご了承ください。
特筆すべき点は pthread_mutex が SpinLock と全く変わらないか、
むしろ速いということです。

SpinLock を作る場合に atomic API の使い方を間違えると、
pthread_mutex よりも遅くなったり、ロックに失敗したりするようです。

X86/x64 の場合 gcc (4.7.2) builtin の __atomic_ でメモリアクセス方法に
__ATOMIC_SEQ_CST を指定すると pthread_mutex よりも低速でした。
__ATOMIC_RELAXED で pthread と同等になります。
また Legacy API ですが __sync_ を使った場合も pthread と同等の
速度となりました。

ARM (Cortex-A9) では RELAXED の場合メモリアクセスが競合し
正しくブロックされません。
上記テストは ARM の場合のみ SEQ_CST を指定しています。
また __sync_ も RELAXED 相当なためかロックされませんでした。

(3) MacOS X (10.8)

OSAtomic と phtread_mutex で大きな隔たりがあります。
同じ pthread の mutex でも、Linux と違い Windows のように
カーネルオブジェクトとして実装されている可能性があります。

API が同一でも OS と CPU によって挙動が違うことがわかりました。
Linux では pthread_mutex が最もよい選択ですが、
MacOS X では逆に pthread_mutex を避けた方が良いようです。

テストプログラムは下記のとおり。

template
class LockTest {
    LockT  lock;
    static int Counter;
    enum {
        MAX_LOOP= 50000
    };
public:
    void thread_func()
    {
        for( int i= 0 ; i< MAX_LOOP ; i++ ){
            lock.Lock();
            Counter++;
            lock.Unlock();
        }
    }
    void Run()
    {
        ClockTimer time;

        Counter= 0;
        Thread t0, t1, t2;

        t0.Run( this, &LockTest::thread_func );
        t1.Run( this, &LockTest::thread_func );
        t2.Run( this, &LockTest::thread_func );

        t0.Join();
        t1.Join();
        t2.Join();

        time.Output();
        assert( Counter == MAX_LOOP * 3 );
    }
};

関連?エントリ
C++11 Rvalue Reference 右辺値参照

C++11 Rvalue Reference 右辺値参照

C++11 の新機能を使うとオブジェクトの無駄なコピーを減らすことができます。

記述が簡単になるなど、C++11 にはいろいろ便利に使える追加機能があります。
Rvalue Reference の場合はむしろコードが増えるのですが、
うまく使うことでプログラムの動作を効率化することができます。
とりあえず適当に文字列型を作ってみます。

class MyString {
    char*   Name;
private:
    void Clear()
    {
        delete[] Name;
        Name= NULL;
    }
    void Copy( const char* str )
    {
        Clear();
        size_t  length= strlen(str)+1;
        Name= new char[length];
        memcpy( Name, str, sizeof(char)*length );
    }
    void DeepCopy( const MyString& src )
    {
        Copy( src.Name );
    }
public:
    MyString() : Name( NULL ) {}
    MyString( const MyString& src ) : Name( NULL )
    {
        DeepCopy( src );
    }
    MyString( const char* str ) : Name( NULL )
    {
        Copy( str );
    }
    ~MyString()
    {
        Clear();
    }
    MyString& operator=( const MyString& src )
    {
        DeepCopy( src );
        return  *this;
    }
};

copy 時に文字列バッファの複製が発生します。
例えばこんな感じ。

MyString  str1;
MyString  str2;
str1= "abcdefg";              // -- (1)
str2= MyString( "ABCDEF" );   // -- (2)
str1= str2;                   // -- (3)

C++11 の Rvalue Reference を使うと特定のケースで copy を move に変換出来ます。
対応コードを追加したのが下記の形。

#include    
#include    

#ifndef IS_CPP11
# define    IS_CPP11    (__cplusplus > 199711L)
#endif

int  AllocCount;
int  FreeCount;

class MyString {
    char*   Name;
private:
    void Clear()
    {
        if( Name ){
            FreeCount++;
        }
        delete[] Name;
        Name= NULL;
    }
    void Copy( const char* str )
    {
        Clear();
        size_t  length= strlen(str)+1;
        Name= new char[length];
        memcpy( Name, str, sizeof(char)*length );
        AllocCount++;
    }
    void DeepCopy( const MyString& src )
    {
        Copy( src.Name );
    }
public:
    MyString() : Name( NULL ) {}
    MyString( const MyString& src ) : Name( NULL )
    {
        DeepCopy( src );
    }
    MyString( const char* str ) : Name( NULL )
    {
        Copy( str );
    }
    ~MyString()
    {
        Clear();
    }
    MyString& operator=( const MyString& src )
    {
        DeepCopy( src );
        return  *this;
    }
    //-- ↓ここから追加分
#if IS_CPP11
    MyString( MyString&& src )
    {
        Name= src.Name;
        src.Name= NULL;
    }
    MyString& operator=( MyString&& src )
    {
        char*   tmp= Name;
        Name= src.Name;
        src.Name= tmp;
        return  *this;
    }
#endif
};

これで転送元を捨てても構わない場合に copy ではなく破壊転送 (move) が行われます。
上の場合はメモリを確保し直す必要がないので効率が上がります。

転送元を捨てても構わないケースの代表が、
一時確保されたオブジェクトである rvalue 右辺値です。
また明示的に std::move() をつければ任意の値を破壊転送することができます。

これで C++11 の場合は (1),(2) において copy が消えます。
どちらも一時的な object が作られているからです。

さらに適当なベクターを作ってみます。

template
class MyVector {
    T*      Buffer;
    T*      Ptr;
    size_t  BufferSize;
private:
    void Clear()
    {
        delete[] Buffer;
        Buffer= Ptr= NULL;
    }
public:
    MyVector() : Buffer( NULL ), Ptr( NULL ), BufferSize( 0 ) {};
    MyVector( size_t size ) : BufferSize( size )
    {
        Ptr= Buffer= new T[size];
    }
    ~MyVector()
    {
        Clear();
    }
    size_t Size() const
    {
        return  Ptr - Buffer;
    }
    T& operator[]( size_t index )
    {
        return  Buffer[index];
    }
    const T& operator[]( size_t index ) const
    {
        return  Buffer[index];
    }
    void PushBack( const T& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= src;
    }
#if IS_CPP11
    void PushBack( T&& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= std::forward(src);
    }
#endif
};

ループさせてどの程度高速化されるか測ってみます。

// main.cpp
int main()
{
    AllocCount= 0;
    FreeCount= 0;
    {
        const int   LOOP_MAX= 100000;
        MyVector  string_list( LOOP_MAX );

        for( int i= 0 ; i< LOOP_MAX ; i++ ){
            string_list.PushBack( "12345" );
        }
    }
    printf( "alloc=%d  free=%d\n", AllocCount, FreeCount );
    return  0;
}

差が出るように作ったので C++11 でコンパイルした方が速いのは当然なのですが、
実際にメモリ確保の回数が半減していることがわかります。

Prog  time         output
c03   0m0.015s     alloc=200000  free=200000
c11   0m0.009s     alloc=100000  free=100000
# Makefile
mac:
	clang++ -std=c++11 -stdlib=libc++  -O4 main.cpp  -o c11
	clang++ -std=c++03 -stdlib=libc++  -O4 main.cpp  -o c03

linux:
	g++ -std=c++11   -O4 main.cpp  -o g11
	g++ -std=c++03   -O4 main.cpp  -o g03
	clang++ -std=c++11 -O3 main.cpp  -o c11
	clang++ -std=c++03 -O3 main.cpp  -o c03

win:
	cl /O2 main.cpp /Fev11.exe -DIS_CPP11=1
	cl /O2 main.cpp /Fev03.exe -DIS_CPP11=0

特定のケースで明らかな無駄を省いているだけなので、通常はここまで差がでません。
例えば (3) の場合何もせずに copy を減らすことはできません。

move の場合は結局 move 用に別の API を設けていることになります。
またオブジェクトだけでなく、copy が発生する API を経由する場合も
copy の他に move 用の別のルートを追加する必要があります。
途中で move() や forward() を付け忘れると、オブジェクトまで到達しないで
途切れてしまいます。

上の MyVector 自体も複製できるようにしてみます。

template
class MyVector {
    T*      Buffer;
    T*      Ptr;
    size_t  BufferSize;
private:
    void Clear()
    {
        delete[] Buffer;
        Buffer= Ptr= NULL;
    }
#if IS_CPP11
    void Move( MyVector&& src )
    {
        Clear();
        BufferSize= src.BufferSize;
        Ptr= Buffer= new T[BufferSize];
        for( int i= 0 ; i< BufferSize ; i++ ){
            *Ptr++= std::move(src[i]);  //-- (4)
        }
    }
#endif
    void DeepCopy( const MyVector& src )
    {
        Clear();
        BufferSize= src.BufferSize;
        Ptr= Buffer= new T[BufferSize];
        for( int i= 0 ; i< BufferSize ; i++ ){
            *Ptr++= src[i];
        }
    }
public:
    MyVector() : Buffer( NULL ), Ptr( NULL ), BufferSize( 0 ) {};
    MyVector( size_t size ) : BufferSize( size )
    {
        Ptr= Buffer= new T[size];
    }
    ~MyVector()
    {
        Clear();
    }
    size_t Size() const
    {
        return  Ptr - Buffer;
    }
    void PushBack( const T& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= src;
    }
#if IS_CPP11
    void PushBack( T&& src )
    {
        assert( Ptr < Buffer + BufferSize );
        *Ptr++= std::forward(src);
    }
#endif
    T& operator[]( size_t index )
    {
        return  Buffer[index];
    }
    const T& operator[]( size_t index ) const
    {
        return  Buffer[index];
    }


    MyVector& operator=( const MyVector& src )
    {
        DeepCopy( src );
        return  *this;
    }
#if IS_CPP11
    MyVector& operator=( MyVector&& src )
    {
        Move( std::move(src) );
        return  *this;
    }
#endif
};

下記のように MyVector を代入できるようになりました。
この (5) のケースでは完全な move となり string のメモリ確保が発生しません。

int main()
{
    AllocCount= 0;
    FreeCount= 0;
    {
        const int   LOOP_MAX= 100000;
        MyVector  string_list( LOOP_MAX );

        for( int i= 0 ; i< LOOP_MAX ; i++ ){
            string_list.PushBack( "12345" );
        }

        MyVector  string_list2( LOOP_MAX );
        string_list2= std::move(string_list);    // -- (5)
    }
    printf( "alloc=%d  free=%d\n", AllocCount, FreeCount );
    return  0;
}

ですが std::move() を付け忘れると move でなく MyString の copy になります。
下記のように alloc/free が増えます。(5) でも同じです。

c03    alloc=300000  free=300000
c11    alloc=200000  free=200000   ( (5) or (6) の move 無し )
c11    alloc=100000  free=100000