Pebble Coding

ソフトウェアエンジニアによるIT技術、数学の備忘録

std::shared_ptr利用法

std::shared_ptrの利用法です。Infoインスタンスは内側のスコープ内で生成されて外側のスコープ内で破棄されていることが分かります。

struct Info
{
    Info(){
        printf("Info() %p\n", this);
    }
    Info(const Info& obj) = delete;
    Info(Info&& obj) = delete;
    Info& operator=(const Info& obj) = delete;
    Info& operator=(Info& obj) = delete;
    ~Info(){
        printf("~Info() %p\n", this);
    }
    void dance(void){
        printf("dance! %p\n", this);
    }
};

int main(int argc, const char * argv[]) {
    
    std::shared_ptr<Info> a;
    {
        std::shared_ptr<Info> b;
        b = std::shared_ptr<Info>(new Info());
        b.get()->dance();
        a = b;
    }
    a.get()->dance();
    
    return 0;
}
Info() 0x100500000
dance! 0x100500000
dance! 0x100500000
~Info() 0x100500000

循環参照があるケースではstd::weak_ptrを使いますが、それはまた別の機会に。

std::vectorメモリ管理

std::vectorにはreserveというメソッドがあるがどのように動作するのか調べてみた。

struct Info {
    uint8_t* dex = nullptr;
    Info(){
        dex = new uint8_t;
        *dex = 7;
        printf("%p Info() dex %p %d\n", this, dex, *dex);
    }
    Info(const Info& obj){
        dex = new uint8_t;
        *dex = *obj.dex;
        printf("%p Info(const Info&) dex %p %d\n", this, dex, *dex);
    }
#if 0
    Info(Info&& obj){
        dex = std::move(obj.dex);
        obj.dex = nullptr;
        printf("%p Info(Info&&)\n", this);
    }
#endif
    ~Info(){
        if (dex != nullptr){
            printf("%p ~Info() dex %p %d\n", this, dex, *dex);
        } else {
            printf("%p ~Info() dex %p\n", this, dex);
        }
        delete dex;
    }
};

int main(int argc, const char * argv[]) {
    std::vector<Info> v;
    v.reserve(2);
    
    printf("v.data() %p\n", v.data());
    
    v.push_back(Info());
    
    printf("v[0] %p dex %p %d\n", &v.at(0), v.at(0).dex, *(v.at(0).dex));
    
    while(true){
    }
    
    return 0;
}
v.data() 0x100200170
0x7fff5fbff6b0 Info() dex 0x100103b50 7
0x100200170 Info(const Info&) dex 0x100103b60 7
0x7fff5fbff6b0 ~Info() dex 0x100103b50 7
v[0] 0x100200170 dex 0x100103b60 7

reserveによってアロケートされるのは構造体のメンバ変数のみつまり、uint8_t* dex のポインタサイズ分だけである。

1回目のコンストラクタはpush_backの中のInfo()によるものである。 2回目のコンストラクタはpush_backの中で生成されたInfo()をコピーコンストラクタで 要素の0番目のアドレスの位置にコピーしているところである。 ここでのコンストラクタのアドレスはv_data()のアドレスと同じであることから、メモリのアロケートは 行われずにコンストラクタだけが実行されている。

new expression - cppreference.com

これはここにあるように、placement newと呼ばれるものである。 以下のように既にアロケートされたメモリ領域にTを埋め込み new(ptr) T と呼び出すことで、コンストラクタを実行し、 T*のポインタを得ている。

char* ptr = new char[sizeof(T)]; // allocate memory
T* tptr = new(ptr) T;            // construct in allocated storage ("place")
tptr->~T();                      // destruct
delete[] ptr;                    // deallocate memory

さて、ここでついでに#if 0の部分をコメントアウトし、ムーブコンストラクタを定義して再度実行してみよう。

v.data() 0x100103b30
0x7fff5fbff6b0 Info() dex 0x100103b40 7
0x100103b30 Info(Info&&)
0x7fff5fbff6b0 ~Info() dex 0x0
v[0] 0x100103b30 dex 0x100103b40 7

push_backの引数部分は右辺値であるからムーブコンストラクタが呼び出されているのが分かる。 ムーブコンストラクタが未定義の場合、コピーコンストラクタが呼び出されるようだ。

C/C++ 構造体アライメント最適化

C/C++の構造体の要素はある程度の境界位置にアライメントされる。そのため、要素の定義順序によって、構造体全体のサイズが変わったりする。たかが数バイトと侮るなかれ。要素数が多ければ、それだけメモリ領域が小さくなり、CPUキャッシュミスが起きる確率が下がり、高速化が望めるのだ。

struct Info1 {
    uint8_t dex;      // 1byte
    int64_t strength; // 8byte 
    float vital;      // 4byte
};

struct Info2 {
    uint8_t dex;      // 1byte
    float vital;      // 4byte
    int64_t strength; // 8byte 
};

printf("sizeof(Info1) %d dex %d:%d strength %d:%d vital %d:%d\n",
           sizeof(Info1), 
           offsetof(Info1, dex), sizeof(Info1::dex),
           offsetof(Info1, strength), sizeof(Info1::strength),
           offsetof(Info1, vital), sizeof(Info1::vital));
           
printf("sizeof(Info2) %d dex %d:%d vital %d:%d strength %d:%d\n",
           sizeof(Info2), 
           offsetof(Info2, dex), sizeof(Info2::dex),
           offsetof(Info2, vital), sizeof(Info2::vital),
           offsetof(Info2, strength), sizeof(Info2::strength));
sizeof(Info1) 24 dex 0:1 strength 8:8 vital 16:4
sizeof(Info2) 16 dex 0:1 vital 4:4 strength 8:8

上記サンプルでは構造体Info1,Info2の違いはvitalとstrengthの定義位置を逆にしただけなのだが、なんと構造体サイズが8バイトも節約できてしまった。このように、大量にインスタンスを生成する構造体においてはアライメントを考慮した上で要素数の定義順を変えるとよい。

iOS/OSXにおけるスレッド優先順位

iOS/OSXではスレッドスケジューラーの出来が良く、細かい設定が可能である。 例として、CoreAudioでのコールバックスレッドのスレッドポリシーがどうなっているかみてみる。

standard policy_count 0 get_default 1
importance 32 count 1 get_default 0
timeshare 1 policy_count 1 get_default 1
period 10666666 computation 100000 constraint 10666666 preemptible 1 policy_count 4 get_default 0
inNumberFrames 471

471/44100 = 10.6ms
period 10.6ms
computation 0.1ms
constraint 10.6ms

  • iOS 9.3 MacMini2

standard policy_count 0 get_default 1
importance 0 count 1 get_default 0
timeshare 1 policy_count 1 get_default 1
period 2229115 computation 12000 constraint 2229115 preemptible 1 policy_count 4 get_default 0
inNumberFrames 1024

1024/44100 = 0.023ms
period 22ms
computation 0.12ms
constraint 22ms

OSX/iOSともに、THREAD_TIME_CONSTRAINT_POLICYによってバッファサイズの数値に合わせた値が設定されている。 さらに、OSXではimportanceに32が設定されており、少し上げてあるようである。 一方、iOSのimportanceは0となっている。

詳しくはAppleのドキュメントに解説がある。 Mach Scheduling and Thread Interfaces

さらに詳しく知りたい方は、この書籍が参考になる。

https://www.amazon.com/Mac-OS-Internals-Systems-Approach/dp/0321278542

macOS環境でrustを学習する

以下の日本語ドキュメントで学習します。

プログラミング言語Rust

インストール

~$ curl -sSf https://static.rust-lang.org/rustup.sh | sh

rustコンパイラのバージョン確認

~$ rustc --version
rustc 1.11.0 (9b21dcd6a 2016-08-15)

vimシンタックスハイライト設定

Bundle 'rust-lang/rust.vim'

3つのサンプルの解説を読む

「3.Rustを学ぶ」の3つの短いサンプルソースの解説を読んでみると、大体の感じがつかめます。

現時点(2016-08-21)では
「3.1. 数当てゲーム」
「3.2. 食事する哲学者」
「3.3. 他言語と共存する」
の最初の2つだけが日本語化されています。 3つ目は英語ですが、最初の2つをやった後なら苦労せず分かります。

rustの雑感

3つのサンプルをやってみた感想はこんな感じです。

  • rustは「安全性」、「速度」、「並行性」にフォーカスしたシステムプログラミング言語である。
  • テストコードも含めると、Win+mingw(32bit/64bit),Win+MSVC(64bit),macOS(32bit/64bit),Linux(32bit/64bit)環境に対応。 テストコードを含めないと、iOS(arm)やAndroidなども対応している。
  • 基本的にコマンドラインベース。
  • パッケージ管理はcargoコマンドを使うのが標準。rubyで言うRakeのようなもの。
  • 文の終わりがセミコロン。シンタックスがいろいろな言語の寄せ集めのように感じる。メソッド名の命名規約がC/C++っぽくものすごく省略してある。例えば比較関数がcmpとか。
  • 言語仕様によりスコープを抜けるとロックが解除されたりする。atomicなリファレンスカウンタをプログラマがUP/DOWNすることにより、オブジェクトの寿命を決められる。ガーベージコレクタ方式ではない。
  • rustからC言語C言語からrustを呼び出したりできる。ruby,python,javascriptから呼び出すことも可能。
  • rustは数値計算とマルチスレッド処理に優れている。
  • map,collectがあったり、最後に評価した値は戻り値になるのはrubyっぽい。
  • rustでライブラリを作った場合はC言語で作ったのと同じ形式であり、ダイナミックライブラリになる。 拡張子はLinuxなら.so,Windowsなら.dll,macOSなら.dylibとなるので、どの言語からも呼び出し可能な形式となる。
  • 「3.3. 他言語と共存する」のサンプルはmacOS環境で試す場合と.soファイルではなく.dylibファイルを指定すると動作する。
  • rustではメモリアロケーションをほとんどスタックメモリで行うことで高速性を確保している。クロージャーもスタック上に取る。
  • C/C++でマルチスレッドプログラミングの知識がある人なら、rustを使うモチベーションがどの程度あるかについては疑問が残る。モダンな言語らしく、パッケージマネージャーがついているのが一つの強みではある。
  • C/C++と比較してrustを使うメリットは、C/C++のように動作未定義やバッファオーバーフローのような脆弱性が組み込まれる可能性が低いことが挙げられる。 OpenSSLのような頻繁に脆弱性が見つかるライブラリよりrust製のライブラリを使いたいという気になる。