|
|
||
個人的なソースコードはsubversionに全部突っ込んでまして、それなりに便利に使ってたのですが、時代の流れと共にいろいろ不都合が生じてきました。具体的には1.7系列にしたらsvkが動かなくなったのでオフラインコミットができなくなったり(将来的に本家で実装する予定だそうですがとりあえずsvkが動かないのは「今」なのです)、githubを使うようになって二重管理めんどかったり(公開用gitリポジトリの方はgit svn fetchしてmerge --squashしてcommitしてpushしてるだけなので手間はたいしたことないのですが、HDD上に同じソースコードが2つあるってのは何かと面倒を生じるのです)、あとまあいつの間にかbitbucketがプライベートリポジトリ作り放題になってたり最近私はWindowsを全く使ってない等の諸々の事情が合わさってsubversionを捨ててgitに完全移行する障壁が無くなってきた感じです。
という日記はともかく、今回はsvn:externalsの真似の話です。
各種分散型vcsも外部リポジトリを取り込む機能は提供してますが、subversionが便利だったのは、svn:externalsプロパティひとつで各種ゴタゴタを良きに計らってくれる点でした。
具体的には↓な運用。
lib1/
source/
lib.ads
app1/
extlib/ svn:externalsに../../lib/source lib1を設定
この状態で全体をチェックアウトすると↓になります。
lib1/
source/
lib.ads
app1/
extlib/
lib1/
lib.ads
lib1/source/lib.adsを更新してupdateするとapp1/extlib/lib1/lib.adsに反映されますし、逆も可。svkを使ってますと全部オフラインでできます(できました)。push先の指定なんかも不要で操作ミスを誘発するような要素もなくて、単純明快です。で、lib1以下だけ、app1以下だけをチェックアウトしても上手く動きます。
これをgitで再現したいわけです。
操作が煩雑になるのはまあ仕方ない。リポジトリを細かく分けないといけないのも仕方ない。app1にlib1を取り込むのはsubmoduleでできます。後はpartial checkout。ここまで前置き。
やりたいこととしては、lib1のmasterはlib1に必要なもの全部入りで、それとは別に外部からlib1を利用するため用に、lib1のsourceのみブランチを用意しよう、と。
http://progit.org/book/ja/ch6-7.html で説明されている例では、ブランチをmasterのサブディレクトリとして取り込み、ブランチ側の更新をmasterでマージしていますが、この逆ができたらいいなと。
とりあえずmaster作ります。
$ mkdir subtree && cd subtree && git init # 実験用リポジトリ Initialized empty Git repository in ~/subtree/.git/ $ mkdir source $ edit source/lib.ads # ソースを追加(editはTextWranglerを起動するコマンド) $ edit manual.txt # ソースコード以外のものも追加しておく $ git add source/lib.ads $ git add manual.txt $ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: manual.txt # new file: source/lib.ads # $ git commit -m "master initial commit" [master (root-commit) 52b8a83] master initial commit 2 files changed, 2 insertions(+), 0 deletions(-) create mode 100644 manual.txt create mode 100644 source/lib.ads
空ブランチを作ります。作り方はgithub:pagesから。
$ git symbolic-ref HEAD refs/heads/sourceonly # 新しいブランチの名前 $ rm .git/index $ git clean -fdx # (.gitディレクトリ以外の)ファイルを全部手動で消してもいい Removing manual.txt Removing source/ $ git status # On branch sourceonly # # Initial commit # nothing to commit (create/copy files and use "git add" to track) $ git branch master # sourceonlyブランチは出てこない。initial commitがまだ無いから?
この新しいブランチに、masterのsourceディレクトリをそのままコミットします。
$ git cat-file -p master # masterブランチが指しているコミットオブジェクトを調べる tree b9231259976ca289eba4c430902a912d9bb12af7 # このtreeが本体だな author yt <...> 1327636245 +0900 committer yt <...> 1327636245 +0900 master initial commit $ git cat-file -p b9231259976ca289eba4c430902a912d9bb12af7 # treeの中を調べる 100644 blob ba0c3d41594a6bf8c9c45b8ea750426abc4789c2 manual.txt 040000 tree 493e1a42635421242b367f4793bc1cbd92cbcbef source # 目的のディレクトリ発見 $ git read-tree 493e1a42635421242b367f4793bc1cbd92cbcbef # sourceディレクトリをそのままindexにする $ ls # ワーキングコピーには反映されてない $ git status # On branch sourceonly # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: lib.ads # # Changes not staged for commit: # (use "git add/rm <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # deleted: lib.ads # ワーキングコピーがないためなんか言われてますが無視します # $ git commit -m "initial source only" [sourceonly (root-commit) 84cb94a] initial source only 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 lib.ads $ git branch master * sourceonly
めでたくsourceonlyブランチができました。
続けてマージのテスト。
$ git checkout master # masterに戻って Switched to branch 'master' $ edit source/lib.ads # 何がしかの変更を加えます $ git status # On branch master # Changes not staged for commit: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: source/lib.ads # no changes added to commit (use "git add" and/or "git commit -a") $ git add source/lib.ads $ git commit -m "change in master" [master fbdd687] change in master 1 files changed, 3 insertions(+), 1 deletions(-) $ git checkout sourceonly # sourceonlyブランチに切り替え Switched to branch 'sourceonly' $ ls lib.ads $ git merge -s subtree master # サブツリーマージ Auto-merging lib.ads CONFLICT (add/add): Merge conflict in lib.ads Automatic merge failed; fix conflicts and then commit the result. $ git status # On branch sourceonly # Unmerged paths: # (use "git add/rm <file>..." as appropriate to mark resolution) # # both added: lib.ads # no changes added to commit (use "git add" and/or "git commit -a")
あれ、なんかコンフリクトしました……?なんででしょう?
$ git reset --hard # というわけでやり直し
HEAD is now at 84cb94a initial source only
$ git merge -s subtree -Xtheirs master # とにかくmasterを使え
Auto-merging lib.ads
Merge made by the 'subtree' strategy.
lib.ads | 4 +++-
1 files changed, 3 insertions(+), 1 deletions(-)
$ git diff master # git diffには-sオプションが無いぞ……
# 大量のログ省略
$ edit lib.ads # しょうがないので目視確認、ちゃんとmasterの内容になってそう
$ git log
commit 5f079753cadb727a813556de9b29f2a2851b0552
Merge: 84cb94a fbdd687
Author: yt <...>
Date: Fri Jan 27 13:30:36 2012 +0900
Merge branch 'master' into sourceonly
commit fbdd68745883a47099ad386a1a40d2514ad3dca2
Author: yt <...>
Date: Fri Jan 27 13:20:02 2012 +0900
change in master
commit 84cb94a7ea53233bc89374413980181694fd0b6c
Author: yt <...>
Date: Fri Jan 27 12:53:00 2012 +0900
initial source only
commit 52b8a833a00339de319d8dbd2037fd41e521e34f
Author: yt <...>
Date: Fri Jan 27 12:50:45 2012 +0900
master initial commit
後はこのsouceonlyブランチを使う側でサブモジュールとして取り込めばおしまいです。
めでたしめでたし……煩雑ですよねー。
subversionのローカルコミット実装を熱烈希望します!!
参考URL: http://brion.inria.fr/gallium/index.php/Using_an_external_library
酷評されているocamlbuildを使ってみましたので忘れないうちにメモ。
その1
$ ocamlbuild main.native
_buildという作業ディレクトリが作られて、なんやかんややった末に_build/main.nativeができて、カレントディレクトリにmain.nativeのシンボリックリンクができます。
簡単ですね!
その2
実行ファイルを複数作りたいのでディレクトリを分けました。
exe1/ main.ml exe2/ main.ml source/ shared.ml
exe1、exe2それぞれのディレクトリでビルドする算段。
ただ、このままではエラーになります。
$ ocamlbuild -I ../source main.native Failure: Included or excluded directories must be implicit (not "../source"). Compilation unsuccessful after building 0 targets (0 cached) in 00:00:00.
フルパスにしても同じで、どうやら全てのソースファイルがカレントディレクトリ以下に必要な模様。
なのでリンク。
exe1/ main.ml source -> ../source exe2/ main.ml source -> ../source source/ shared.ml
$ ocamlbuild -I source main.native
その3
外部ライブラリを使いたいと思います。
ocamlが初めから持っているBigarrayやUnixやなんかは-tag use_bigarrayで充分なのですが、野良ライブラリの場合は位置を教えてあげる必要があります。なのでmyocamlbuild.mlを書きます。
open Ocamlbuild_plugin;; dispatch begin function | After_rules -> ocaml_lib ~extern:true ~dir:("../lib") "gmp"; ocaml_lib ~extern:true ~dir:("../lib") "mpfr"; ocaml_lib ~extern:true ~dir:("../lib") "unicode"; tag_file "main.ml" [ "use_gmp"; "use_mpfr"; "use_unicode"]; tag_file "main.native" [ "use_bigarray"; "use_gmp"; "use_mpfr"; "use_unicode"; "use_unix"] | _ -> () end;;
配置としては、main.mlと同じところにlibというディレクトリを作って、野良ライブラリ自身のmakefileでもってlibにライブラリを配置させます。ocamlbuildからは配置済みのライブラリを使おうという戦略です。私はOCamlのライブラリ付属のmakefileは全く信用していないのですが、ocamlmklibやなんかはもっと信用していません。(この例で使っているGMPラッパーなんかは私自身が作ったライブラリですので、そのmakefileは私が書いたものです。なので、私の環境限定で上手く動作することは確実なのでそれを使います。これを読んでいる人は、ライブラリのmakefile(私のものも含めて)も信用しないようにお願いします。必ず自分でビルドしましょう。)
ocaml_libのオプションですが、~extern:trueは、ocamlbuildにビルドさせないために必要です。このパラメータを省略しますと、親切に全部ビルドしようとしてくれやがりますがソースが見つからずにエラーになります。~dirが"../lib"になってますが、これは_buildからの相対パスなのでこうなります。
tag_fileは、_tagsというファイルを作りたくないのでここに書いてるだけです。ひとつのビルドツールのために複数ファイル書きたくないです。
後は、このlibディレクトリ自身が依存関係解析に巻き込まれないように、無視するようオプションをつければOKです。
$ ocamlbuild -X lib -I source main.native
ocamlbuildは標準で入っている素晴らしいツールです。少なくともocamldepを駆使したmakefileを書くよりは良いので、俺ビルドツールを作るよりも先に試してみましょう。俺ビルドツールが散乱してもいいことは何もありませんからね!*1
最近、クロスリファレンスツール(gnatfindとかocamlspotなど)はビルドツール(gnatmakeとかocamlbuildなど)に内蔵されているべきと思うようになってきました。
だってビルドツールはソースの位置とか中間ファイル(大抵クロスリファレンスに必要な情報も持ってる)の位置とか依存関係も含めて全部知ってるじゃないですか、ねえ。
*1:ちなみにYTの俺ビルドツールはhttps://github.com/ytomino/ocamlmakeにあります。バカですね!
何度も書いてますが、言語組み込みの文字列とライブラリ提供の文字列では効率に天と地の差があります。C++みたいになるべく自然に使えるように努力している言語ですら、その差は到底埋められていません。ましてやAdaのUnbounded_Stringなんて(略)ですよ。
Delphiの文字列のスゴイところと、どうすればおんなじ風にできるかなー、ってのをダラダラやってた話です。あ、ここでいうDelphiの文字列というのは2007以前のAnsiStringのことです。2009以降については正直静的言語なのにコードページを実行時に持つ必要は無いと……どうでもいい。
実はD言語の文字列のほうがもっとスゴイのですが、あのスゴさはGC前提なので省略。
Delphi2〜Delphi2007のAnsiStringの構造
「参照カウンタ、長さ、文字列本体、(C互換用のゼロ終端←以後省略)」というメモリブロックへの「ポインタ」です。
まあこの構造自体はstd::stringでも同じでしょう。0xでは参照カウンタでの共有は許されなくなったんでしたっけ?
で、早速Adaには難点が。Adaでは(C++で言う)デストラクタを実現するためには、Ada.Finalization.Controlledを継承しなければいけないのですが、そうすると当然VMTが必要になるわけです。つまりポインタ2つの組になってしまうわけです。若干メモリの無駄。
| Delphi | C++ | Ada |
| ○ | ○ | × |
レジスタに入る
AnsiStringの実体はポインタ1つだけです。ということはレジスタに入ります。
std::string(C++03以前)も、実体はポインタ1つだけにできますが、レジスタに入りません。メンバ関数にthis(ポインタを1個だけ持つクラスへの更にポインタ)を渡さないといけないからですね。*1
もちろん上記のようにAdaでは最低でもポインタ2つ必要になりますので、レジスタには入りません。
| Delphi | C++ | Ada |
| ○ | × | × |
文字列定数
AnsiStringの定数は、静的に確保されてます。参照カウンタには-1が入ってます。コンパイラが最初っから文字列の構造を把握してるからこそですね。
一方、std::stringもUnbounded_Stringも、コンストラクタ/To_Unbounded_Stringに定数文字列を渡されても、動的にメモリを確保して中身をコピーする必要があります。C/C++もAdaも文字列定数は単なる文字型の配列であり、参照カウンタも長さデータも持ってないからです。
そのため、ちょっと構造を変えましょう。
「参照カウンタ、長さ、文字列本体」→「参照カウンタ、長さ、文字列本体へのポインタ、(文字列本体)」
動的に確保する時は、「文字列本体」まで一緒に確保して、「文字列本体へのポインタ」メンバには「文字列本体」(要するにすぐ次のアドレス)を入れます。
静的に確保する時は、「文字列本体」は確保せずに、「文字列本体へのポインタ」メンバには実定数へのポインタをそのまま持たせます。
……実はこれだけだとダメです。
仮に引数の型をchar const *やaccess constant Stringにしたとしても、ローカル変数(定数)へのポインタを渡されたりすると、そのまま使うわけにいかないですよね。呼び出し元の関数を抜けると消えちゃいますから。
というわけで安全に文字列定数を作るためには、引数で渡された定数が本当に定数であることの保証が必要になります。D言語ならimmutableって便利なものがあるのですが、C++やAdaにはありません。
ただAdaのaccess型(ポインタ)はスコープに紐付けられた型ですので、ライブラリレベル(一番外)のaccess型を使えば、関数を抜けるなどで消えたりはしないことだけは保証できます。それでも、Adaのaccess constantはC++のconst *と同じで、実際に指しているものは変数かもしれません。中身が書き換わる恐れだけは無くせません。
| Delphi | C++ | Ada |
| ○ | × | × |
引数渡し
AnsiStringは参照カウンタで管理されていますが、引数渡しの時は参照カウンタが増減されません。
実引数は関数呼び出しよりも寿命が長いことが確実だからです。
C++やAdaでこれを避けるには参照渡しにするしかなくて、そうするとポインタのポインタになってしまって、無意味な逆参照が1段入ってしまいます。引数として渡ってくるわけですからどんなにコンパイラが賢くてもひとつの関数内だけの最適化では取り除けません。LTOなら話は別。
まあAdaは特に、継承を使っている型は明示しなくても問答無用で参照渡しになってしまうわけですが。
| Delphi | C++ | Ada |
| ○ | × | × |
世の中にはStringPieceなんかもありますが、折角の参照カウンタが活用されないため、あんまり意味無いかなあ。(リンク先の例だと中でCopyToStringするのも呼び出し元で一時的なstd::stringを作って中では参照カウンタを増やすだけってのも大して変わらないはず?out->append(dir);をout->assign(dir);に変えたら……と思って試してるのですが……続きは↓↓)
返値も今までの話と似たようなものです。
AnsiStringはレジスタで返せて、その際参照カウントも増減しません。
std::stringはレジスタでは返せずに(暗黙の引数が必要になる)、コピーコンストラクタと一時変数のデストラクタも走ります。(0xならムーブコンストラクタと一時変数のデストラクタ)
Unbounded_Stringもレジスタでは返せずに(暗黙の略)、やっぱりAssignと一時変数のFinalizeが走ります。
もちろん定数を返すとわかっているなら参照返しにすればよいのですが、std::stringやUnbounded_Stringは上記のように定数であっても作成時点で中身全部コピーしたりしますので参照返しにして参照カウンタの操作を省略できたやったー、なんてやっても虚しいだけです。最終的に単なるポインタのレジスタ間コピーだけになるAnsiStringだからこそ活きる最適化ですよね。
| Delphi | C++ | Ada |
| ○ | × | × |
連結
文字列を一気に3つ連結したりする時も、AnsiStringならコンパイラが知ってますからまとめて連結してくれたりします。(ランタイムには_LStrCat3とかある)
std::stringやUnbounded_Stringの連結は、演算子オーバーロードで実現されいるわけですが、「A & B & Cを一括で処理するオーバーロード」は定義できませんので、ちまちま連結していくしかないです。
ただ少しだけあがくことは出来て、「A & B」の時点で領域を多めにとっておいて、「& C」がそこに入るなら再アロケート無し、みたいな頑張り方はできます。もちろん「& C」されなれば無駄な領域ですのでふつーの発想だとやらないと思います。後はRopeにするぐらいかなあ。
| Delphi | C++ | Ada |
| ○ | × | × |
というわけで結論
Unbounded_Stringをいろいろ弄ってたんですがあまり改善できそうにないです。
要は失敗談でした。
http://d.hatena.ne.jp/shinichiro_h/20100823#1282563465の例は参照カウンタを活用すればstd::stringはもっと輝けるはずだ、clear();append(dir);ではなくてassign(dir);すればもっと速いんじゃないか、という実験。
パターン1
void JoinFilePathStr(string const& dir, string const& base, string* out)
out->assign(dir);
}
void JoinFilePathSp(StringPiece const& dir, StringPiece const& base, string* out) {
dir.CopyToString(out);
}
Str(const char*, const char*) 0.665250 Str(string, const char*) 0.236847 Str(const char*, string) 0.308445 Str(string, string) 0.011704 Sp(const char*, const char*) 0.174883 Sp(string, const char*) 0.136190 Sp(const char*, string) 0.139241 Sp(string, string) 0.109931
Str(string, string)が他とケタ違いに速い。これはOK。逆に、このパターンでstd::stringのほうが速いことで、実装が参照カウンタを使っていることも確認できます。(basic_stringのソース見たら参照カウンタを使わない条件とかあって複雑でしたので)
パターン2
void JoinFilePathStr(string const& dir, string const& base, string* out)
{
out->assign(dir);
out->push_back('/');
}
void JoinFilePathSp(StringPiece const& dir, StringPiece const& base, string* out) {
dir.CopyToString(out);
out->push_back('/');
}
Str(const char*, const char*) 0.837721 Str(string, const char*) 0.512278 Str(const char*, string) 0.499792 Str(string, string) 0.279397 Sp(const char*, const char*) 0.202806 Sp(string, const char*) 0.156834 Sp(const char*, string) 0.161091 Sp(string, string) 0.132171
1文字push_backしてるだけ。再アロケーションが必要になるのでStr(string, string)が遅くなるのはわかります。問題はSp(string, string)で、あまり変わってません。ここで逆転が発生するのはおかしい。この速度差は純粋にstd::stringのせいであってStringPieceあまり関係ないんじゃないか……。
どうやら(gcc 4.0の)basic_stringはC文字列からのassign時に余分な領域を取っているらしいです。JoinFilePathSpのほうはそれを利用しているから再アロケーションが発生していない。逆に実データが共有状態ですと、領域が仮に余っていても、常に共有解除するから遅いのではないかと。で、Str(string, string)がSp(string, string)の2倍遅いのは、共有解除とpush_backで2回コピーが起きてるのではないか?と予想できます。共有解除と連結を一気に行うようにしてしまえば、同じぐらいの速度になるはず。要するに、この例でStringPieceが速いのはStringPieceの手柄ではなくて、単にbasic_stringの実装がタコなせいではないでしょうか……。
パターン3
void JoinFilePathStr(string const& dir, string const& base, string* out)
{
out->assign(dir);
out->reserve(dir.size());
}
void JoinFilePathSp(StringPiece const& dir, StringPiece const& base, string* out) {
dir.CopyToString(out);
out->reserve(dir.size());
}
Str(const char*, const char*) 0.820524 Str(string, const char*) 0.498222 Str(const char*, string) 0.498238 Str(string, string) 0.273923 Sp(const char*, const char*) 0.185341 Sp(string, const char*) 0.156204 Sp(const char*, string) 0.147269 Sp(string, string) 0.121723
ソースを見るかぎりpush_backはreserve(size() + 1);してるだけでしたので、reserveによる共有解除だけにしてみました。
先の予想はハズレです。同じ長さへのreserveだけでも遅い。なんだこれは。長さが変わらないので純粋に共有解除だけ、つまりアロケーションとコピーが各1回必要で、CopyToStringと同じ=Sp(string, string)と同じになるはず、と信じたかったのですが……。
一方、Sp(string, string)に変化はありません。最初から共有していない状態で、長さも変わらないからreserveは何もしません。当然ですね。
というわけで予想2。reserveの実装が極端に酷い。
……ソース見てるんですが、reserve→_M_cloneと呼ばれてアロケート(_S_create)もコピー(_M_copy)も1回ずつしかやってませんね……なんでしょう??
パターン4
void JoinFilePathStr(string const& dir, string const& base, string* out)
{
out->clear();
out->append(dir);
}
void JoinFilePathSp(StringPiece const& dir, StringPiece const& base, string* out) {
dir.CopyToString(out);
}
Str(const char*, const char*) 0.624683 Str(string, const char*) 0.298058 Str(const char*, string) 0.299669 Str(string, string) 0.055530 Sp(const char*, const char*) 0.178162 Sp(string, const char*) 0.135845 Sp(const char*, string) 0.138761 Sp(string, string) 0.106734
clear();append(dir);に戻してみました。これもアロケーションとコピーが1回ずつというのは同じですので、変わらないはず……と思いきやappendが速い!CopyToStringよりも速い!
謎すぎます。
ここから考えられるのは……ということで予想3。clear();は領域を開放しない。このベンチマークはjoined変数を使い回しているため、clear();append(dir);では再アロケーションが発生していない。つまりこのベンチマークは元々意味がないのでは?shinichiro.hさんごめんなさい。
パターン5
define BENCH(msg, expr) do { \
time_t start = clock(); \
for (int i = 0; i < 1000000; i++) { \
string joined; \
expr; \
} \
int elapsed = clock() - start; \
/*assert(!strcmp(joined.c_str(), "/tmp/hoge.c"));*/ \
printf("%s %f\n", msg, (double)elapsed / CLOCKS_PER_SEC); \
} while (0)
joinedをforループ内に移動して、毎回デストラクトされるように。
5-1 assign/CopyToStringのみ
Str(const char*, const char*) 0.678821 Str(string, const char*) 0.358226 Str(const char*, string) 0.359924 Str(string, string) 0.126315 Sp(const char*, const char*) 0.368007 Sp(string, const char*) 0.328823 Sp(const char*, string) 0.323669 Sp(string, string) 0.298406
5-2 push_back
Str(const char*, const char*) 1.079381 Str(string, const char*) 0.684841 Str(const char*, string) 0.679538 Str(string, string) 0.330640 Sp(const char*, const char*) 0.687556 Sp(string, const char*) 0.653732 Sp(const char*, string) 0.646298 Sp(string, string) 0.619009
5-3 reserve
Str(const char*, const char*) 1.063986 Str(string, const char*) 0.674232 Str(const char*, string) 0.663562 Str(string, string) 0.315969 Sp(const char*, const char*) 0.374639 Sp(string, const char*) 0.339220 Sp(const char*, string) 0.342732 Sp(string, string) 0.299357
5-4 clear();append(dir);
Str(const char*, const char*) 1.030788 Str(string, const char*) 0.639979 Str(const char*, string) 0.636695 Str(string, string) 0.286971 Sp(const char*, const char*) 0.367424 Sp(string, const char*) 0.329775 Sp(const char*, string) 0.324559 Sp(string, string) 0.297469
ちゃんとパスを連結するように戻すと
void JoinFilePathStr(string const& dir, string const& base, string* out)
{
out->assign(dir);
out->push_back('/');
out->append(base);
}
void JoinFilePathSp(StringPiece const& dir, StringPiece const& base, string* out) {
dir.CopyToString(out);
out->push_back('/');
base.AppendToString(out);
}
Str(const char*, const char*) 1.452504 Str(string, const char*) 0.998036 Str(const char*, string) 0.999712 Str(string, string) 0.642984 Sp(const char*, const char*) 0.898612 Sp(string, const char*) 0.857780 Sp(const char*, string) 0.849120 Sp(string, string) 0.825539
おお!予想通りの計測時間が出てきました!
見ての通り、いい勝負してます。
2つの引数で変換が発生するとstd::string不利ですが、これも予想通りですしね。元のshinichiro.hさんのベンチマーク程は大差がついていません。結論:暗黙の変換が発生するとしても、(仮に一時的にでも)共有状態を作り出せるならstd::stringを引数にしてOK。
ただしC++0xでは……。
http://panathenaia.halfmoon.jp/birdeyes/
やっぱり何番煎じかわからないのですが……。
愛用させていただいていたI knowが終了してしまって困ったことは、「複数のRSSをまとめた結果をRSSとして再出力できるWeb上のRSSリーダーがない」ということでした。(ダッシュボード上のRSSリーダーで更新チェックしてます)
で、Yahoo Pipesの存在を教えていただいて、しばらくそれを使っていました。こいつはGUIプログラミングでRSSを加工できるかなり楽しいツールなのですが、ふつーのRSSリーダーのようにサイトをひとつ追加しようと思えば、Fetch Feedを配置してUnionにつないで……と、まあ、めんどいわけです。そのせいですっかり購読しているサイトが少なくなってしまいました。
次に、無いなら自分で設置しよう、ということでCGIアンテナを探したわけです。その中ではRNAが私の要件を満たしてるっぽいです。
一応I knowでできてRNAでできないことはありまして、RNAはサイトごとに、最新記事だけを表示するか、各記事をばらして表示するかの設定ができません。各サイトの最新記事だけをまとめたRSS、各サイトの各記事を全部含めたRSSは作れます。その設定を混ぜることができません。I knowではできたんですよね。
例えば個人のブログサイトなんかは、更新されていることがわかれば充分ですので最新記事だけでよくて、むしろ過去の記事は混ざってほしくないわけです。
逆に、ニュースサイトなんかは、タイトルを見て開くかどうかを決めますので、全タイトル出てきて欲しくて、ブログとは逆に出元のニュースサイトがどこかは意識しないわけで、バラバラに時系列順に並んで欲しいわけです。
というわけでその機能付きの俺アンテナです。
あとI knowでできたことと言えば、RSSを公開していないサイトもdiff取ってRSSに載せてくれたのですが、はてなアンテナのRSSを取り込めば実現できますので実装していません。負荷ははてなに負わせる作戦。
あとインポート/エクスポート形式は、ふつーOPMLと思うのですが、たまたまI knowのデータをLIRSでしか保存してなかったのでLIRSのみ対応してます。
引きずって申し訳ありませんが、先のranhaさんの発表を聞いて以来、Exceptional C++を読み返しています。
で、116ページでとんでもない文を見つけてしまいました。
とんでもない!この一文だけでこれは禁書です、燃やしましょう!
……ええ、ええ、説明しますとも。
私がCのヘッダファイルのトランスレータを作っていることは前に書いたとおりです。変換できるライブラリも割と順調に増えて、そろそろ標準Cライブラリはカバーできるのではないかってあたりは視野に入ってきました。最終目標のwindows.hはまだまだ遠いですが。
で、今libxml2対応しようとして「どうしようか、これ……」ってなってるところです。具体的には次のようなパターン。
one.h
struct T; typedef struct T *t_ptr;
two.h
#include "one.h" struct T { t_ptr mem; };
one.hでは、struct Tをopaque typeとして宣言して、そのポインタだけを使ってます。全く問題ありません。
two.hでは、struct Tに実体を与えて、そこで自身のポインタをメンバに持ってます。全く問題ありません。
問題は、two.hがone.hを#includeしているため、必ずone.hがtwo.hより先に来ることです。
今まで変換に成功した他のライブラリにもこの手のパターンはあったのですが、幸いにもtwo.hがone.hを#includeしてはいなかったため、#includeの順番を調整することで回避できました。これは直接#includeが書かれてしまっていて順番を入れ替えることができません。
……ええ、ええ、意味不明ですね。
C言語では、opaque typeとその実体は依存関係なく別々に宣言可能です。これははっきり言って珍しい機能であり、C言語特有の超便利な機能です。もっと言えばC言語がモジュールを持たないからこそ実現できている機能です。
まともなモジュールがある言語でこれを表現するには……循環参照が必須ですよね、ですよね。
んでもってまともなモジュールがある言語はフツー公開部分の循環参照が禁止されてますよね、ますよね。
どうしろと!?
D言語は確か循環参照できたよなー(うろ覚え)……とかそういう問題ではなくて。
まともなモジュールがある言語では、struct Tの宣言箇所を1箇所に絞らないといけないわけですよ。
つまりopaque typeの宣言と実体の宣言が別ヘッダにあることを検出して、内部的に循環参照に置換しないといけないわけです。
(実体の宣言をtwo.hからone.hに移動するというのはダメです。実体はtwo.hで宣言されている他の何かを使ってるかもですし、opaque typeを宣言してるのはone.hだけじゃなくもっとあるかもしれませんし)
one.d
import two; alias struct_T* t_ptr;
two.d
import one;
struct struct_T { t_ptr mem; }
Adaの場合はもっと面倒です。普通には循環参照できないので、どっちかにlimitedを付けないといけません。
limitedを付けた参照先はaccess型(ポインタ)経由でしか扱えなくなりますので、one→twoとtwo→oneのどっちに付けるか判定が必要です。
更にAdaはCのようなやわらか型言語とは異なりvery very strong-typingな言語ですので、複数ヶ所でaccess型(ポインタ)を宣言するとそれぞれ別の型になってしまいます。
これを避けるために私のトランスレータではポインタ型の宣言は元の型と同じ場所に固めることにしています。
つまり本当であればstruct T *やstruct T **に相当する宣言はtwoに入ります。で、ポインタのtypedefみたいなのは、「元の型と同じ場所(two)で宣言されてるポインタ型」の別名として定義するようにしてます。
limited withしますとこれが使えなくなります。ポインタと言えどもlimited with先の型を値として使おうとしていることには変わらないからです。
つまり元の型に対するポインタ型を2箇所で宣言する羽目になり、strong-typingな言語ではこれが結構痛いわけです。
one.ads
limited with two; package one is type t_ptr is access all two.struct_T; -- subtype t_ptr is two.struct_T_ptr; ← エラーになる end one;
two.ads
with one; package two is type struct_T is record mem : one.t_ptr; end record; type struct_T_ptr is access all struct_T; ← struct T*が使われているので自動生成 end two;
↑のone.t_ptrとtwo.struct_T_ptrに互換性はありません!
追記: twoからはoneは全部見えてるんだから全部one.t_ptrを使うようにしてしまえば宜しいのでした。あほか私は……。
この時点でもう実装をあきらめたいレベルなわけですが、これにインライン関数でもくっついてて、struct T::memに触られてたりすると……。
このトランスレータは今のところAdaしかサポートしてませんが、最終的は汎用にしたいわけです。循環参照ができない言語ですと(Dなんかが例外であってほとんどの言語がそうでしょう)……one.h内で使われている分はvoid *扱いにでもするしかないですよねもう。
というわけで、先行宣言を別のヘッダファイルに泣き別れにするのはやめてください、
先行宣言は同じファイル内だけで完結させてください、お願いします!
(理由: 他の言語に移植できなくなるからC言語を滅ぼす日が遠くなる)
っていうかostreamの宣言がclass直からtemplateになったために<iofwd>ができたってすぐ前のページで書いてるんですから、自前の型も後でtemplateにするかもしれないわけですから、先行宣言をあちこちに書き散らかすなんて推奨するのはやめましょうよHerb大先生!これがありなら関数のプロトタイプだって使う場所で別個に書けば#include要らないよねやったーって話になるじゃないですかー。
の懇親会?みたいなのが無かったので(まあ私は聴講者その1に過ぎなかったのですが)、発表者のranhaさんと、nihaさんとeldeshさんを呼び出して(なんという不遜、なんという豪華メンバー)秋葉原で適当に話してきました。ありがとうございます。
覚え書き。C++0xにはパターンマッチ可能な多相バリアントが追加される。
型。
std::exception_ptr var;
値をboxing。
try{ throw 任意の型の任意の値; }catch(...){ var = std::current_exception(); }
追記。専用の関数が用意されているそうです。
var = std::make_exception_ptr(任意の型の任意の値);
値をパターンマッチで取り出す。
try{ std::rethrow_exception(var); }catch(int i){ ... }catch(std::string s){ ... }catch(...){ ... }
メモ。http://www.nminoru.jp/~nminoru/programming/stackoverflow_handling.htmlを穴が開くまで読む。
あとどうでもいいじまん。多相バリアントによる先頭要素を保証した異種リストはhttps://github.com/ytomino/headmaster/blob/master/source/c_parser.ml で実際書いたことがありまして、冗長なことに目をつむれば便利なものでした。コンパイラを書くのに最適な言語は間違いなくO'Caml、私の中では。
wraith13
値を boxing するところのコードですが、それ用の関数がちゃんと標準ライブラリで提供されていまして std::make_exception_ptr() を使えば一発ですよ! (^ρ^)
Flast
それ自体はそれほど難しくなくて、普通にC++03の範囲でも実装することができます。
ex) http://ideone.com/f2t27
要はboxingするときに完全に型が分かっていれば、必要な関数をtype erasure等で用意することができるわけです
h_sakurai
また欠席してしまった。うぁぁあぁあ。まぁいいや
ytqwerty
>wraith13さん
もうバリアントとしての用途が想定されているとしか思えない\(^o^)/
>Flastさん
ああいえ、Boost.Anyもありますし、できるのは知ってたんですよ。
例外に使われるのが面白いなーと。
>h_sakuraiさん
会場でh_sakuraiさん探しましたよ!残念です。
こういう「すべての例外を受け取る」機能って、本当にすべての例外を受け取れるとは限りませんよね……。もちろん実装次第ではあるのですが。
具体的には「他の言語ランタイムから投げられた例外」。g++とgcjとGNATで関数ポインタ交換しあって例外投げまくったらどうなるかとかそういう話です。pthread_cancelとかその手のも。あとGNATですとAbort_Signalという特殊な例外は明示的にwhen Standard'Abort_Signalって書くと受け取れるのですがwhen othersには引っかかりません。違うコンパイラですとBorland C++のlongjmpは抜ける途中の関数にあるオブジェクトのデストラクタは動かしてくれますが、当然catch(...)にはかかりません。
で、そういうことを考えると、よくある↓のイディオムが使えなくなります。
nanika_t allocate_and_initialize()
{
nanika_t *p = new nanika_t;
try{
initialize(p);
return p;
}catch(...){
delete p; /* initializeで例外発生 */
throw;
}
}
これだけですとauto_ptrやunique_ptrを使えと言われそうですが、メモリ管理に限らなければ、全部の例外を一度受けて巻き戻し処理を行って再度投げる形は、結構使われているはず。
try { /* なんかする */ } catch(...) { /* 実は全ての例外を取れないかもしれない */ /* 巻き戻し */ throw; }
なんで同ランタイムの例外しか受け取れないかといいますと、知らない例外を受け取っても例外オブジェクトの解体方法なんかがわかりませんし、バックトレース情報なんかも更新できませんし。
finallyやデストラクタは、例外オブジェクトの方に触れる必要がありませんので、gccでしたらdwarf、WindowsでしたらSEHみたいな共通の枠組みに則っていれば例外を投げたランタイムを問わずハンドリングできます。
というわけでfinallyが使えれば↓でいいのですが、
bool completed = false; try { /* なんかする */ completed = true; }finally{ if(!completed){ /* 巻き戻し */ } }
このcompletedって変数が嫌な感じです。break禁止な糞コーディング規約の元で無理やりフラグ立ててループを抜けているのと同じ感じがします。
というわけで↓みたいに書きたいなあ、という話でした。
try { /* なんかする */ } catch_all_and_rethrow { /* 巻き戻し */ }
常に再度投げるとわかっていれば、内部的にもcompletedみたいな変数は不要にできないかな……具体的には例外の種類を判別したりせずに一旦全部受けて、例外オブジェクトには一切手を付けないでそのまま_Unwind_Resume_or_Rethrowに任せたりでいけそうな。あ、でも/* 巻き戻し */の処理中に別の例外が発生したら、結局前の例外オブジェクトは解体せざるを得ませんので、お手上げですね……。
Egtra
SEHならEXCEPTION_CONTINUE_SEARCH、D言語のscope(failure)、C++ならunique_ptrで削除子指定 + 成功時releaseあたりなんかどうでしょうか。
SaitoAtsushi
広告が入るようになってのははてなダイアリの側で変更があったようです。
http://d.hatena.ne.jp/hatenadiary/20110819/1313721142
設定ページで従来の挙動に戻すことは出来ます。
ytqwerty
>Egtraさん
ええ、scope(failure)は求めてるものそのものですね(try文みたいに書きたいというのは置いておいて)。unique_ptrは、finally効果のある機能がデストラクタしかないなら仕方ないのかな……unique_ptrでスコープガードを作る話は意外と先人が多くいてびっくりしました。
>SaitoAtsushiさん
ありがとうございます。消してみました。消せてるかな。
http://www.ada-auth.org/standards/12aarm/html/AA-A-3-2.html
Is_Line_Terminator, Is_Mark, Is_Other_Format, Is_Punctuation_Connector, Is_Spaceが追加されます。
Unicodeカテゴリともちょっと違うようですし(ていうか素直にIs_XXXをUnicodeカテゴリの全種類分並べるだけではなぜダメだったのか)、下の方の説明を読んでもいまいちピンと来ないのですが、{AI05-0185-1}のリンクをたどるとこの正体が明らかになります。
The current version of the GNAT compiler has defined the following implementation-defined packages;
http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ai05s/ai05-0185-1.txt?rev=1.7
まじか……。
文字コードに対するAdaCoreのナンセンスさ加減はcomp.lang.adaでもある程度共有された認識だと信じていたのですが。
ということで、a-zchuni.adsを読むとこれらのサブプログラムの正体がわかります。
| サブプログラム | 対応するUnicodeカテゴリ |
| Is_Line_Terminator | Zp(Paragraph_Separator), Zs(Space_Separator), 0a-0d(LF, VT, FF, CR) |
| Is_Mark | Mn(Nonspacing_Mark), Mc(Spacing_Mark) *1 |
| Is_Other(_Format) | Cf(Format) |
| Is_Punctuation(_Connector) | Pc(Connector_Punctuation) *2 |
| Is_Space | Zs(Space_Separator) |
名前ぐらいUnicodeカテゴリを正確に反映しろよ……っていうか私Unicode詳しくないんですが、MnとMcだけを判定したくてMeを除きたいような用途ってなんかあるんでしょうか?詳しい人がいましたら教えて下さい、お願いします。
流し読みですが、Is_Punctuation_ConnectorのほうはRandy大先生が_Connectorを追加させた様子。a-zchuni.adsのIs_Punctuationのままですと、P系カテゴリ全部含むと誤解しちゃいますもんね。とりあえずその程度には元のa-zchuni.adsは酷い。
で、Handlingのサブプログラムは、Maps.Constantsのほうに対応する集合が定義されてるはずなんですが、まだ追加されてないです。
http://www.ada-auth.org/standards/12aarm/html/AA-A-4-6.html
要するに他のベンダーはこんなの実装せずに無視しろってことですよねわかります。
ocamlspot のデータファイルは ocamlc が呼ばれたときのコマンドラインを記憶しているので、ビルドシステムからの情報をそれなりに引き継いでることになります。知りたいファイルのファイルシステムでの位置を求めることが可能です。
エディタはともかく、最低限ビルドツールとコンパイラは一体になっているべきと思います。依存関係を割り出すためにヘッダをパースして、各ファイル毎に別プロセスが立ち上がって同じヘッダをパースして、って無駄ですよ……。