C++などC言語系では、情報がどのように渡されているか見えないことが多いです。コンピュータにある程度のメモリがあり、取り扱う情報も少なければそれほど気にはなりません。しかし、組み込みマイコンのようにメモリや演算速度が遅い場合や画像や動画、ビッグデータなどの情報が巨大な場合は処理速度に影響が出てきます。
このため、情報がどのようにやり取りされているかを意識する必要があります。繰り返しますが、これから示す効率の良いプログラミングは見た目が悪いです。つまり、可読性が悪いということです。 昔ながらのパフォーマンスが悪い記述方法は計算アルゴリズムの記述としてはシンプルなわけです。
だから、数学やアルゴリズムの研究としてプログラミングをする場合は、エラー処理や例外処理、無駄なコピーなどを気にせずにシンプルに記述すればよいわけです。全てに完璧と汎用性を求めすぎることは愚行です。
一方で業務で使われるプログラムの場合はそれ自体のシンプルさや可読性より処理速度やメモリー消費のほうが重要になります。今回はこのための考察をします。
普通な記述
int test(int arg){…};
こんな関数は普通に記述します。そして、余計なものが書かれていないのでわかりやすいです。昔から書かれている方法ですが、これは、関数を呼び出すときと、戻るときに二回コピーが発生します。ある意味無駄なわけです。でも繰り返しますがわかりやすいです。
ちなみにこの方法を現実に例えますと、「相手にファックスを送り、それに記入後、ファックスで返信」という方法です。書類なら素早いですが、大きなものでは実現不可です。
参照渡し
void test(int &arg) {…}
この方法は場所だけを関数に知らせます。そして、関数はその場所にあるデータを処理した後に、作業終了だけを伝えます。これは最も効率が良い方法だと言われますが、戻り値が無い不思議な関数でもあります。そもそも戻り値がない関数とは何なのか?と数学者に突っ込まれそうでもあります。だから、可読性が悪いわけです。
あと、この方法は実は現実的であったりします。家のトイレを修理してもらうために技術者を呼ぶという方法です。相手にはトイレの場所を言うだけです。そして、修理が終わった後はその報告だけ受けます。
移動
int test(int &&arg) {…}
この方法はデータを移動させます。これはC++の途中から生まれた考えです。例えると、壊れた電化製品を修理するためにメーカーに送り、修理後に戻してもらうというものです。これも現実世界では自然な考えです。
ただし、プログラミングではややわかりにくい moveという命令を使う必要があります。見られないコードが増えるので可読性が悪いです。
まとめ
元々のプログラミングは少ないデータを複雑なアルゴリズムで解析して、少ない計算結果を返すというものでした。 データは少ないが、計算が複雑というものです。しかし、今ではビッグデータに代表される、データが極端に大きいものが増えてきています。この場合にデータを渡すたびにコピーをしていたらそれだけで時間が食われてしまいます。だから、参照や移動を使って大きなデータをできるだけ動かさずに処理をする必要が出てきます。
数秒程度の処理であれば、コードの見やすさを重視することは重要ですが、参照や移動を使えば一週間の処理が一晩になるのであれば検討の価値はあると考えます。
ある程度プログラミングに慣れてきたら、実際のデータはどのように移動しているのか?を考える必要もあるのではないでしょうか?
- #include <iostream>
- #include <string>
- #include <vector>
- //原紙のコピーを作業者に渡す、作業者はそれに加筆する。そして作業者はコピーを取り戻す。(二回コピーされる、ファックスに近い)
- std::vector<std::string> fx(std::vector<std::string> arg){
-
- arg[0] = "xxxxx";
-
- return arg;
- }
- //原紙の場所を作業者に伝え、作業者はそれに加筆する。そして、作業者は作業を終わったことを通知する。(一度もコピーされないし、移動もない)
- void fy(std::vector<std::string> &arg){
-
- arg[0] = "yyyyy";
-
- }
- //依頼主は原紙を渡し、作業者はそれに加筆する、そして、作業者はその原紙を返却する。
- std::vector<std::string> fz(std::vector <std::string> &&arg) {
-
- arg[0] = "zzzzzz";
-
- return std::move(arg);
-
- }
- int main(){
- std::vector <std::string> test;
-
- test.push_back("aaaa");
-
- test = fx(test); //移動するたびにコピーが作られる(ファックスで書類を送り、記入後、ファックスで受ける)
- std::cout <<test[0]<< std::endl;
-
- fy(test); //移動もコピーも作られない(相手を自宅に呼び、書類に記入してもらう)
- std::cout <<test[0]<< std::endl;
-
- test=fz(std::move(test)); //書類を郵送し、記入してもらい、また戻す
- std::cout << test[0]<< std::endl;
- //依頼主に渡したのにそれを受け取らずに処理した場合(やってはいけない)
- fz(std::move(test)); //この時点でtestは無効な値である。
- std::cout << test[0]<< std::endl;
-
- return 0;
-
-
- }