Boost.Optional Must Go - 野良C++erの雑記帳
Boost.Optional Must Go (2) - 野良C++erの雑記帳
最近、何回かに分けて、 Boost.Optional について、(細かい)不満を日記に書いてきました。
これは別に Boost.Optional が嫌いだから不満を書いた訳ではなく、むしろ その逆、
Boost.Optional が便利な分、逆に不満点が目立つので書いていたわけですが、
よく考えたら、 Boost.Optional の便利さを、纏まった記事で書いていないなー、
と、そう思い至ったので、この辺で Boost.Optional を徹底的に推奨する記事でも書いてみようかな、
というわけで、さんざん既出なネタばかりですが、折角なので書いてみることにしましたよ。
冒頭に挙げた以外で参考になるような記事は、この辺でしょうか:
C++ で Maybe モナド - 野良C++erの雑記帳
Boost.Optional と Boost.InPlaceFactory で初期化遅延 - 野良C++erの雑記帳
とりあえずメモリを確保→後からオブジェクトを構築するテク - 野良C++erの雑記帳
それでは、長文になるかもしれませんが、お付き合いいただけると幸い。
この記事は、新たに Boost.Optional の魅力がわかり次第、追記していく予定は未定です。
0. Boost.Optional 事始め
まず Boost.Optional を知らない人に、手短に解説。
公式: http://www.boost.org/doc/libs/1_43_0/libs/optional/index.html
boost::optional
使い方は全体的にポインタを模したものになっており、
boost::optional<int> x = 23; // 構築はポインタと違い、 T から暗黙変換できる // 使い方はポインタと同じで、 if( x ) // if で有効/無効をチェックして、 { std::cout << *x << std::endl; // 有効なら * で実際の値を取り出せる } x = boost::none; // x に対して「無効状態」を設定 assert( !x ); boost::optional<int> y = 42; // int i = y; // ダメ。値を取り出す時には * を使う。 x = y; // optional 同士なら普通に代入出来る(当然) // y が有効と分かってるなら、 x = *y; と書いてもいい(が、冗長)。
こんな感じで使います。
ポインタと違う点は、値の論理で動く点です:
boost::optional<int> x = 23, y; y = x; // y に x を代入して *x = 42; // x の中身を変えても assert( *y == 23 ); // y の中身は変わらない
このように、全ての boost::optional
これらを踏まえて、 Boost.Optional の使い道を見ていきましょう。
1. 「失敗するかもしれない」関数の戻り値として使う Boost.Optional
まず Boost.Optional の役割としての基本、誰でも知ってるのがこれ。
boost::optional
関数に失敗した結果を表す値として使おう、という考え方で、
これは所謂 Haskell の Maybe モナドと同じ役割であり、
Haskell の Maybe モナドと同様、極めて強力な考え方です。
無論、C++では他にも「関数の失敗」を表現する方法はあるわけですが、
Boost.Optional を使うメリットは、とにかく汎用性に富んでいる点。
C++で「関数の失敗」を表す方法としては、例外機構などがあるわけですが、
C++における例外処理というのは、基本的にコストが掛かるので、
関数の失敗が
「ごく稀にしか起きない」あるいは「事前のチェックで取り除ける」
場合にのみ使うべきであり、
頻繁に失敗するならば、別の手段を模索した方が良い場合が殆どです。
要するに、例外は あくまで「例外的な事態」に対処する為のものなので、気軽には使えないのです。
例外処理を使わない場合は、戻り値によって成否を判別することになりますが、
その場合、
- 「戻り値は(スマート)ポインタにして、 NULL だったら失敗という意味にする」
- 「正常な結果の持つ制約(例えば非負の値になる)を利用し、正常でない結果(例えば負の値)を返した場合には失敗という意味にする」
- 「関数の結果は参照(or ポインタ)引数経由で返し、戻り値で成否を示す」
などの方法を、状況に応じて使い分けることになります。
しかし、これらの方法には、どれも一長一短あって、
- 戻り値を(スマート)ポインタにした場合は、(どこか1パスであっても)一時オブジェクトを作って返したい場合に、(全てのパスで)動的メモリ確保が必要になる。生ポインタの場合には、その参照先を解放するべきか否かを、ドキュメントを読んで確認しなければいけない。
- 正常な結果の持つ制約を使った場合には、戻り値に制約条件が無い場合に困る。また、正しく使うにはドキュメントをしっかり読まなければいけないし、また制約条件を変えるような仕様変更を行えない。
- 参照引数経由で結果を返し、戻り値は成否を示す場合、関数を呼び出す前に、予め 結果を格納する変数を用意しなければいけない。結果を受け取る変数が組み込み型でない場合には 初期化する分が余計なコストになるし、一般に全ての型が default constructible であるとは限らないのでテンプレートにしにくいし、結果を格納する変数を const にすることも出来無い。テンプレートや const を使わない場合*1であっても、戻り値をチェックしないという怠慢が生じる。
と、使い分けるのが正直面倒です。ポリシーにして使い分けたい、とかなったら、尚更。
そこで、 Boost.Optional の出番です。
Boost.Optional は、戻り値を(スマート)ポインタとして返す、という戦略の発展形であり、
最初に紹介した通り、ポインタと同様の構文で使えます。
(スマート)ポインタと違う点は、動的メモリ確保を行わない、という点です。
これにより、単純に「行う処理の量が減る」というだけでなく、
動的確保を行う場合に比べ、コンパイラによる最適化を妨げる要因が少なくなります。
御託はこれくらいにして、実際に使ってみましょう。
例として、std::getline や boost::lexical_cast を、Boost.Optionalで書き換えてみることにします。
#include <boost/optional.hpp> // 関数例1: std::getline を optional 文字列で返すようにする // 結果を受け取るための一時変数を作る必要がなくなる #include <iostream> inline boost::optional<std::string> get_line( std::istream & is ) { std::string temp; if( std::getline( is, temp ) ) { return temp; } else { return boost::none; } } // 関数例2: boost::lexical_cast を optional にする // 失敗する可能性が高い場合に、例外ではなく if で扱えるのは便利。 #include <boost/lexical_cast.hpp> template<typename Target, typename Source> inline boost::optional<Target> lexical_cast_opt( Source const& src ) { try { return boost::lexical_cast<Target>( src ); } catch( boost::bad_lexical_cast& ) { return boost::none; } } #include <boost/format.hpp> #include <boost/algorithm/string.hpp> // for trim int main() { // 使用例 // 入力を二倍にして出力するプログラム // 入力を取ってきて、変数に束縛 if( auto input_ = get_line( std::cin ) ) { // いちいち * で参照するのは面倒なので、予め参照を外しておく std::string& input = *input_; // lexical_cast は空白を無視しないので、予め trim しておく boost::algorithm::trim( input ); // さっきは非 const で受けたが、普通は const で受けた方が「自然」 if( auto const value = lexical_cast_opt<double>( input ) ) { // 処理内容が短いなら * を使って書く方が簡潔。 std::cout << *value * 2 << std::endl; } else { std::cout << boost::format("'%1%' is not a number.\n") % input; } } else { std::cout << "no input.\n"; } }
どうでしょうか。
一般に boost::optional
「 if の条件部で変数を宣言して結果を受け取り、それが有効なら」
という処理を書きます。
これは慣れない人には奇妙に感じるかもしれませんが、れっきとしたC++のコードであり、
慣れてしまえば非常に強力な表現力を持ちます。 *2
この書き方は、一般に、ポインタで戻り値を得る場合にも使える、ということに お気づきでしょうか。
そうです。戻り値を auto で受けて if でチェックし、 if 内部では * でアクセス、という構文は、
実際の戻り値が optional なのかポインタなのかによって、書き方を変える必要がないのです。
これは極めて強力です。
なぜなら、書き方が同じなら、テンプレートを使うことが出来るからです。
とすると、仮に「戻り値の条件を利用して関数の成否を示す」という方針を採った場合でも、
戻り値の型をラップしたクラスを作り、適切な operator bool と operator * を定義することで、
optional やポインタと同様の書き方を提供すれば、
これもまたテンプレートを使って書き表すことが出来る、ということになります。
つまり、この「 if でチェックし、 * で取り出す」という書き方は、
失敗する可能性の有る処理を行う場合の「統一的な書き方」であり、
Boost.Optional がポインタと同じ構文になっていることによる最大のメリットであり、
Boost.Optional の素晴らしさを最も端的に示していると、僕は考えます。
この「統一的な書き方」は、単に「テンプレートで統一的に書ける」というだけでなく、
やがて、これらの処理を楽に書けるようなマクロ*3が生み出される、ということも十分に考えられますし、
将来的には、拡張されて C++ 上でパターンマッチングを実現するための土台となる、可能性もあります。
こうして夢は広がっていくのです。
なにより、単純に、統一的に書けた方がカッコいい、というのもありますしね。
とはいえ実のところ、 Boost.Optional のインターフェイスは、 Haskell に比べて冗長です。
Haskell なら、モナドを使うことで、失敗するかもしれない処理を綺麗に並べて書けるのですが、
Boost.Optional にはその辺りのサポートが少なく、その辺りが少し不満だったりします。
これは、今後の発展に期待するということで。
2. 任意のタイミングでオブジェクトを構築/破棄できる、「メモリ管理クラス」としての Boost.Optional
さて、ここからが本題です。
Boost.Optional は、論理的には「 T に無効値を追加する」という、それだけの物です。
それでも十分に強力なのは前の章で挙げた通りですが、 C++ 的には、それに劣らず強力な、もうひとつの側面があります。
それが「汎用的なメモリ管理クラス」としての Boost.Optional で、
Boost.Optional を使うことで、オブジェクトの寿命管理を、ずっと柔軟に、かつ低コストで行うことができるのです。
と言っても、すぐにはパッとしないかもしれません。
そこで、具体的な例を挙げてみましょう。
値の「再束縛」が出来るクラスとしての Boost.Optional
Boost.Optional は、任意の T に対して、値の「再束縛」を可能にします。
「再束縛」というと少し仰々しい感じですが、要は「再代入」です。
しかし、「再代入」と言っても、実際に「代入」しているわけではありません。
具体的に言うなら、もし operator= が定義されてたとしても、それは呼ばれません。*4
boost::optional
T 型のオブジェクトを「作り直す」ことが出来るのです。
こう言うと「じゃあ、どうやるの」と思うかもしれません。
ので、ここで Boost.Optional の実装について軽く触れておきます。興味の無い方はスルーして下さい。
boost::optional
- T 型を格納出来る大きさ/アライメントが確保されたメモリ領域
- 「そのメモリ領域に実際にオブジェクトが構築されているか」を示すフラグ変数
この2つからなります。
フラグが立っている場合、メモリ領域にはオブジェクトが実際に構築されているので、
無効状態に遷移する時や、デストラクタの呼び出し時は、きっちりと中身を破棄してやる必要があります。
一方、フラグが立っていない場合、メモリ領域はただの未初期化メモリです。
これを大前提として、状況に応じて、
「メモリ領域にオブジェクトを構築し、フラグを立てる」「構築されたオブジェクトを破棄し、フラグを倒す」
という処理を行っていくのが、 Boost.Optional の実装になります。
これを踏まえた上で、 Boost.Optional に対して値を代入した場合の処理を考えると
- フラグが立っているなら、既にオブジェクトが構築されているので、オブジェクトを破棄してフラグを倒す
- 無効状態のメモリ領域に対してオブジェクトを構築し、成功したらフラグを立てる
という処理を行えば、直接的な代入操作を行なうことなく、再代入を実装することが出来るのです。
さて、実装の話はここまでにして、再代入、ということについて、すこし考えてみましょう。、
「変数を再代入できる」と聞くと、なんとなくスゴそうに思えるかもしれませんが、
再代入すると言うことは、それだけバグの原因が増える、ということでもあります。
特に、再代入されることが想定されてない型に対して、擬似的に再代入を実現した場合、
コードレベルでは明確に構文が区別されるので問題はないといえばないのですが、
コードを書く人によっては、ちょっと混乱してしまうようなことが、あるかもしれません。
ですが、現実として、 C++ という言語は、変数への再代入が可能な言語です。
それによって失われた物は大きいですが、得られる物も、また大きい。
本来ならば代入操作が出来無い型に対して、代入操作を可能にする、
そのことで得られる利益は、決して少なくない筈です。
なので、 Boost.Optional を使うなら、 Boost.Optional が「再束縛可能である」という点は、
知っていて損はない事実だと思うので、ここで紹介することにしました。
と、話が少し脇道にそれました。
では、この「再束縛可能である」という点を活かして、具体的に、どのようなコードが書けるか。
ここで具体的に「再代入できない」型を考えた場合、代表的なものは「const変数」と「参照」です。
それらをメンバにもつクラスも「再代入できない」型となりますが、それらはこれらの簡単な応用です。
よって、代表的な例として、「const」と「参照」に対する optional を見ていくことにしましょう。
boost::optional
まず「再代入できない」型の最初の例、 const な値に対する optional から見ていきましょう。
boost::optional
// const への optional について boost::optional<int const> x = 1; std::cout << *x << std::endl; // OK. 非破壊的な操作は任意に行える ++(*x); // NG. 破壊的な操作は行えない x = 2; // OK. 再代入できる *x = 3; // これはダメ。あくまで optional に対する再代入のみを受け付ける // 参考までに const な optional も紹介 boost::optional<int> const y = 1; std::cout << *y << std::endl; // OK. 非破壊的な操作は任意に行える ++(*y); // NG. 中身に対しても破壊的な操作は行えない(推移的) y = 2; // NG. 再代入も出来無い *y = 3; // 当然、これもダメ。
*x = 3;
というコードがコンパイルエラーになる点に注目して下さい。
Boost.Optional は、 optional 自身に対する操作と、中身に対する操作を、明確に区別します。
ただ、区別はするものの、 Boost.Optional は参照(ポインタ)ではなく値の論理で働くので、
通常は optional に対する操作は、中身に対しても同様に働きます。
が、代入操作は違います。
さっきも触れた通り、 Boost.Optional の代入操作は、
- まず、それまで入っていたものを破棄して、空状態にする
- それから、右辺の値を元にコンストラクタを呼び出し、中身を再構築する
という二段階で処理されるので、問題なく再代入できるのです。
もっとも、単に再代入をしたいだけなら、別に optional でなくてもいいのでは、と思うかもしれません。
つまり「無効状態」という余計な状態を持たない、純粋に「再代入可能」なクラスを作れるのでは、と。
結論から言うと、一般の型 T に対して、そうすることは不可能です。
理由は、「再代入」する時のコンストラクタ呼び出しで、例外が投げられる可能性が有るため。
その場合、例外安全の強い保証を満たすのは、不可能ではないですが、非常に面倒であり、
それならば「空の状態も受け入れられる」とした上で、例外安全に関しては基本的な保証のみを行なうのが、理にかなっているのです。*5
…と、少々脱線してしまいました。普通に使う分には、これらの実装を気にする必要はありません。
要するに「安全性のために const にしたいけど、でも再代入はしたい」っていう状況において、
それを実現する有効な方法のうちの一つが boost::optional
パフォーマンスを考えると他の選択肢が良い場合も多いですが、何より手軽で例外安全なので、
最初の試験的な実装などに用いる分には、十二分な強さを発揮します。是非とも使ってみましょう。
訂正: boost::optional
http://d.hatena.ne.jp/gintenlabo/20100905/1283706950
boost::optional
次に、「再代入できない」型のもう一つの代表例、参照に対する optional を見てみます。
これは、メモリ管理という点からは少し外れますが、
Boost.Optional の中でも、特に異質で、特に重要な概念なので、しっかり触れておきたいです。
繰り返しになりますが、 boost::optional
さらに、 T に対して代入操作を行うことなく、値の再束縛を可能にするのでした。
つまり boost::optional
実際に使ってみましょう。
int i = 23, j = 42; boost::optional<int&> x = i; std::cout << *x << std::endl; // x の参照先の値を表示 ++(*x); // x の参照先の値をインクリメント std::cout << i << std::endl; // 24 x = j; // 参照先を変える ++(*x); // 変更された参照先 j の値をインクリメント std::cout << i << std::endl; // 24 std::cout << j << std::endl; // 43 x = boost::none; // x を「何も指し示していない」状態にする assert( !x ); // x は無効参照 ++(*x); // 未定義動作(通常は assert に引っかかる)
…お気づきでしょうか。
これは要するに、生ポインタそのものです。
実際、使用例を見ると、
変数の設定の部分は、生ポインタとは違う構文を使っていますが、
それ以外の場面では、基本的に生ポインタと同じように書けていることが分かります。
と言うと、
「なんだ、生ポインタと同じなら、生ポインタを使えばいいじゃないか」
と思うかもしれません。
しかし、僕はそうは思いません。
なぜならば、生ポインタは、うっかりミスの温床だからです。
int* p; // 初期化してないポインタは NULL とは限らない。 if( p ){ /* ... */ } // if チェックをくぐり抜けてしまう、潜在的な危険がある。 boost::optional<int&> x; // 一方、こちらは常に none である。 if( x ){ /* ... */ } // ここは明確に実行されない。 struct hoge { hoge* operator&() { return (hoge*)666; } // operator& が多重定義されてる! } h; hoge* p2 = &h; // NG. boost::addressof を使うべし。 boost::optional<hoge&> x2 = h; // OK. delete p2; // うっかり delete してしまう!?(コンパイルは通る。動作は未定義。) delete x2; // コンパイルエラー。
どうでしょう。
確かに、これらのうっかりミスは、する方が悪い、という初歩的なものばかり。
operator& の多重定義とか、まさに「こんなクラスを書く方が悪い」です。
でも、人間なので、うっかりミスは付き物。
ちょっとした工夫で うっかりミスを抑止できるなら、それに越したことはないですし、
何より、 boost::optional
「これはあくまで、無効値を取れて再代入可能な、『参照』です。」
という意図を、明確にできるのです。
実のところ、標準と準標準を合わせても、
この手の「再束縛できる参照変数」っていうのは、実は boost::optional
生ポインタ T* に、この boost::optional
これらのうち、生ポインタは「参照以外にも使われる、安全性が低い」という弱点があり、
reference_wrapper は「無効参照を取ることが出来ない」という弱点があるため、
boost::optional
にも関わらず、現状、 boost::optional
これは、単純に「有名でない」という他に、
Boost.Optional Must Go (2) - 野良C++erの雑記帳 で触れたように、
現状の実装では、何故かポインタよりも大きなオブジェクトサイズを要求してしまうためであり、
これについては本気で爆発した方がいいと思っていますが、
しかし、メモリ効率以外は非常に優秀なので、知らなくていい理由にはならない筈です。
本当に効率が必要なとき以外は、積極的に使うといいんじゃないでしょうか。
std::unique_ptr ( auto_ptr, scoped_ptr ) の代替としての Boost.Optional
さて、最後に、 Boost.Optional を最もフリーダムに使う例を挙げましょう。
寿命管理です。任意タイミングでの生成/破棄です。早速コードを見てみましょう。
// ... class resource : boost::noncopyable { /* ... */ }; int main() { boost::optional<resource> rs; // boost::optional はコピーを行うことなくスコープを超越する if( /* ... */ ) { /* 処理とか */ if( /* ... */ ) { // リソースを構築! rs = boost::in_place( /* 引数 */ ); } } if( rs ) { // リソースが構築されていたら // なにか処理をする /* ... */ // リソースを此処で破棄! rs = boost::none; } // 更に処理を続行する /* ... */ }
resource は、特に具体例が思い浮かばなかったので こういう名前ですが、
動的メモリ確保とか RAII なんかを駆使した、比較的「重い」何か、としてください。*8
見れば分かる通り、スコープなんてまるで存在しないかのように、自由自在に扱えてるのが分かると思います。
無論、 C++ のスコープは大事な概念なので、無視されると基本的には困るわけですが、
実際問題として、無視したい場合だって少なくないのです。
そのような場合に便利そうだな、ってのが、コードを見てなんとなく察せたでしょうか?
また、これにはもう一つ、大事なことがあって、
最初から Boost.Optional を使うことを決めてしまえば、
resource クラスに「無効状態」を用意する必要がなくなります。
通常、クラスに対して特殊な状態を追加することは、バグの温床となりえます。
一般に、 T 型のオブジェクトというのは、もし効率を失わずに無効状態を表現できたとしても、
「常に有効な状態にある」という制約を課した方が、設計のコストもデバッグのコストも小さいのです。
言い換えると、全てのオブジェクトは、コンストラクタの呼び出しをもって有効状態になり、
デストラクタが呼ばれるまで有効な状態を維持するべきである、と言えます。*9
そして、特別な理由(初期化を遅延したいとか)があって、無効状態を扱いたい場合は、 Boost.Optional に任せる。
これが美しい役割分担、美しい設計、というものである。そうは思いませんか?
もし Boost.Optional を使わずに、これらの、寿命や有効/無効の管理を行いたい場合、
boost::scoped_ptr
その場合では動的メモリ確保を行うので、少しばかり無駄な処理を行なうことになります。
無論、動的メモリ確保のコストなんて無視出来る程度に微々たるものであり、
それによって得られる効果は大きいので、 scoped_ptr や unique_ptr を使わない手はないのですが、
単純に「初期化を遅延したい」「破棄を早めたい」「スコープを超越したい」という程度の用途なら、
Boost.Optional を使うことにより、動的確保を行わない分だけ高速に動作させられるのです。
ここで大事なのが、前に少し触れた、 boost::in_place を使ったオブジェクトの直接構築であり、
これを使うことによって、 boost::optional
例え T が「そもそもコピーできない型」であっても格納することが出来るようになるし、
コピーできる型に対しても、直接構築することで、構築してからコピーするよりも処理量を抑えられます。*10
そうです。 Boost.Optional は、実のところ、「単に無効状態を追加する」だけの代物ではないのです。
Boost.Optional は「実行効率を殆ど下げずに、無効状態を持ち込むクラス」です。
つまり、一般のクラスを設計する際に、有効/無効は Boost.Optional に任せて、
クラス自身は要求される機能の実装のみに専念する、という設計パターンを可能にします。
無論、 Boost.Optional を使わなくても、このあたりは動的メモリ確保によって実現出来ることですが、
Boost.Optional のメリットは、とにかく実行効率を殆ど下げないという点であり、
速度こそ正義である C++ では極めて有用なものである、と言えるのです。
これを支えているのが、 boost::optional
その条件とは、
- boost::optional
を単に構築したり再束縛したりするだけなら、 T がスタック上に置ける型であり、デストラクタが例外を投げなければ良い。 - boost::optional
を戻り値として使いたい場合(コピーしたい場合)は、それに加えて T がコピーコンストラクタを持ちさえすれば良い。
たったこれだけです。
これらの条件さえ満たせば、どのようなクラスであっても、 Boost.Optional の恩恵を享受できます。
また、パフォーマンス上の要請から、有効/無効をクラス自身に持たせたい状況であっても、
そのインターフェイスを Boost.Optional に揃えておけば、いろいろと便利になる筈です。
ex. 免責事項
最後に、一応、お約束というか。
Boost.Optional は強力ですが、強力なものほど、使い道を誤ると酷い目に遭います。
特に「スコープの概念」とかは、 C++ において非常に大事な概念であり、
Boost.Optional を用いてスコープを超えたい場合は、果たしてそれが最も良い選択肢なのか、
もっと自然な書き方はないのかどうか、常に考えながら行う必要があります。
下手をすると、参照先のオブジェクトが不意に破棄されて、取りにくいバグの原因になったりします。
とにかく、このあたりの悲劇は、 C++ を使う場合には本当に多い。
安全性を考慮すると、 optional ではなく shared_ptr 等を使うべき局面は、かなり多いのです。
また、それとは別に、 Boost.Optional は参照ではなく値の論理で働きます。
つまり、巨大なオブジェクトを扱わせると、スタック消費量やコピーコストが馬鹿にならない、ということです。
それらを常に考えた上で、動的メモリ確保と optional 、どちらが良いか、常に考えるようにしましょう。
C++ を使う以上、思考停止は可能な限り避けるようにしたいですね。
.
*1: 勿体無い。
*2: 擬似的なパターンマッチングとして使えますし、スコープが if 文中のみに限定されるので安心です。
*3: Boost.Range に対する BOOST_FOREACH とかが代表例でしょう。便利です。
*4: 勿論、 *x = 〜〜 として明示的に呼ぶことは、いつでも可能です。
*5: 実際問題として、コンストラクタを使わず、代入演算子を使って再代入を設計すれば、それでいい問題でもあるわけで、実際にそういうクラスは有効です。あくまで、一般的に「代入演算子が無い場合でも」実現しようと思ったら、ということです。
*6: もっとも boost::shared_ptr
*7: boost にもありますし、C++0xでめでたく標準入りしました。なので名前空間は書いてません。
*8: スレッド辺りとかを想定してくれれば結構です。
*9: もっと言うと、バグを減らすためには、極力デストラクタはコンパイラによって自動生成されるべきです。
*10: 実際には戻り値最適化によって処理量は変わらない場合が殆どでしょうが。