関数を使わない方がプログラムは速くなる?

このQ&Aのポイント
  • 大きなプログラムを書く際、関数を多用することでプログラムの見通しが良くなり、後から修正しやすくなる。
  • しかし、関数を使用すると計算速度が低下する可能性がある。
  • 関数を使用しない場合には、Matlabが最速で、関数を使用する場合にはVBAが最速になることが分かっている。
回答を見る
  • ベストアンサー

プログラムで関数は使わない方が速くなる?

大きなプログラムを書く際、プログラムの見通しが良くなり、後から修正しやすくするため、 同じ計算が複数回出る際には関数を多用したものを作成してきました。 ところが、プログラムの計算速度を上げたいと思い、計算時間を比較してみたところ、 関数を使わない方が圧倒的に速くなることが分かりました。 Matlabを使って、 for j = 1:100000 for i = 1:1000 y = i*i * j *j; end end の計算を行った際の計算時間と、ループ内の計算を関数で置き換えた for j = 1:100000 for i = 1:1000 y = zikan_sokutei_function ( i, j ); end end function [ kei ] = zikan_sokutei_function ( i, j ) kei = i * i * j * j; end の計算を行った時間は、 それぞれ、0.5秒と1分7.3秒、という結果になりました。 つまり関数で置き換えたことで、計算時間が実に135倍も増大したということです。 上記と同じプログラムを使ってVBAで比較したところ、 それぞれ、8.29秒と20.70秒、という結果になりました。 つまり関数で置き換えたことで、計算時間が2.5倍も増大したということです。 また、関数を使わない場合には、Matlabが最速で、関数を使う場合にはVBAが最速であるという結果になっています。 これは関数を全く使わない方が、圧倒的に高速に計算を実行できるという結果になっていますが、 こういうものなのでしょうか? 2倍程度なら許容範囲ですが、100倍以上も遅くなるのであれば、 関数は全く使うべきではない、ということになりますが、なぜこれほどまでに時間がかかるのでしょうか? Matlabの方がVBAよりも数値計算に適しているため、VBAよりも実行速度は速くなるべきだと思いますが 関数を使った場合には、これが逆転してしまうという、かなり腑に落ちない結果となりましたが、 こういうものなのでしょうか?

質問者が選んだベストアンサー

  • ベストアンサー
  • rabbit_cat
  • ベストアンサー率40% (829/2062)
回答No.8

とにかく、matlabでの、唯一絶対の基本原則は、「for文を回しては絶対に駄目」です。 matlabで、ループ回数がものすごく多いfor文を書くと、処理時間が、行列演算の100倍どころか1000倍遅いとか平気でなります。 例えば、for文を使ったプログラムが y=0; for j = 1:100000  for i = 1:1000   y = y + i*i * j *j;  end end だとしたら、 (質問文のプログラムは、結果として何も計算していないの、少し変えました。) matlabでは、 y = sum((1:1000).^2)*sum((1:100000).^2); という行列演算として一行で記述してください。 同じ結果が、おそらく一瞬で出てくるはずです。 matlabの最適化機能はあまり頭が良くないので、かなり単純な構造じゃないと、ループを行列演算に自動変換してくれません。 なんで、基本的には、ユーザーが上記のように、明示的にきちんとループではなくて行列演算の形で記述する必要があります。

その他の回答 (7)

  • rabbit_cat
  • ベストアンサー率40% (829/2062)
回答No.7

#4です。 ちょっと、きちんと言いたいことの本質が伝わらないかもと思ったので補足。 matlabに関して言えば、100倍遅くなった原因は、 ・関数呼び出しにしたから ではなくて、 ・ループ(for文)を使ったから です。 一見、両方とも、ループが存在しているように見えますが、関数版でないほうは、matlabが内部でプログラムを自動的に解析して、内部で、ループではなくて行列計算になるように変更して実行されたため速いのです。 関数版のほうは、最適化が利かなくなってしまったため、実際にループとして実行されたので、劇遅になってしまったわけです。 >2倍程度なら許容範囲ですが、100倍以上も遅くなるのであれば、 >関数は全く使うべきではない、ということになりますが、なぜこれほどまでに時間がかかるのでしょうか? matlabに関して言えば、関数呼び出し自体は当然使用してもよいですが、大規模なループ(for文)は使用すべきではありません。(それこそ100倍遅くなります)

MASSYY
質問者

お礼

皆さんご回答ありがとうございます。 遅くなる原因は理解することができました。 もう一つ関連した質問をお聞きしたいのですが、 関数に変数のやり取りをしているために遅くなる、というのであれば、 グローバル変数を使ってやれば、引数のやり取りだけはなくなり、その分速くなるのではないかと思い 試してみました。 計算回数を一桁下げて for j = 1:10000 for i = 1:1000 とした上で、 VBAで public i, j をsubの前に書いて実行すると、 iとjをグローバル変数としていない場合で、2.00秒、した場合で1.72秒となり、 若干だけですが、速度が向上しました。 一方で、matlabで global i j; を定義してから同じことをやってみると、 iとjをグローバル変数としていない場合で、6.5秒、した場合で2分12.2秒となり、 なぜかVBAとは反対に、20倍も遅くなってしまうという結果になりました。 この結果は、No.7さんが仰るようにforループを行列としてmatlabが最適化したと考えたとしても、 関数と変数のやり取りをしているためであると、考えたとしても説明がつきません。 なぜmatlabではこのようにグローバル変数を用いた方が20倍も計算速度が遅くなるのでしょうか?

noname#208507
noname#208507
回答No.6

> 2倍程度なら許容範囲ですが、100倍以上も遅くなるのであれば、 > 関数は全く使うべきではない、ということになりますが、... それは極論ですね。速度のボトルネックの多くはプログラムのごく一部です。速度のチューニングは後回しにして、関数を多用する質問者さんの従来のスタイルが正しいと思います。 逆に関数を使わないと、アルゴリズムのボトルネックを探すことが難しくなるでしょう。プロファイラ等で時間計測することが難しくなってしまうので。 しかし最適化すべき部分と期限が決まったなら、どんな手段もためらうべきではないとは思います。

  • kinta03
  • ベストアンサー率41% (7/17)
回答No.5

お疲れ様です。 抽象的な説明ですが参考に為れば幸いです。 例えば次のような関数が有ったとします。 add_abc = abc( a, b, c ) function abc( a, b, c ){   return( a + b + c ) } これを呼び出す一連の処理は (注:値の受け渡しにstackを利用する前提です) 1.呼び出し元で、aの値をstackに格納 2.呼び出し元で、bの値をstackに格納 3.呼び出し元で、cの値をstackに格納 4.呼び出し元で、abcを呼び出す 5.関数側で、aの値を取り出す 6.関数側で、bの値を取り出す 7.関数側で、cの値を取り出す 8.関数側で、演算 a + b + c を行う 9.関数側で、結果(return値)を汎用レジスタなどに置く 10.関数側で、return命令で呼び出し元に帰る 11.呼び出し元で、cの値をstackから削除 12.呼び出し元で、bの値をstackから削除 13.呼び出し元で、aの値をstackから削除 14.呼び出し元で、結果(return値)をadd_abc変数に格納する 、を行っています。 1~7、9~13までが呼び出しに伴うオーバーヘッドとなります。 8と14は関数を使わなければそのまま行っている事ですよね。 簡単な演算なのでオーバーヘッドが物凄く多く感じますが 処理内容によっては無視できる範囲になります。 今回のように呼び出し回数が多い場合にはオーバーヘッドばかり増えますよね。 そこのところを考えて判断してください。

  • rabbit_cat
  • ベストアンサー率40% (829/2062)
回答No.4

どう書くのが速いか、言語と、コンパイラやインタプリタの最適化機能によるので一般論では言えません。 通常は、まったく同じ内容を関数化すれば、最適化が効き難くなる分だけ遅くなることが多いとは思いますが、場合によっては、関数化することで、コンパイラの中での最適化用で考えないと行けないスコープが狭まることで、逆に最適化が効きやすくなって高速化する場合もあります。 その上で、Matlabに限定した話ですが。 まず、Matlabでは、基本的に、for文などのループは劇遅なので使ってはいけません。 ループで行列の要素毎に計算するのではなく、必ず行列全体をまとめて計算するようにします。こうすると、Matlabはかなり速いです。(それこそ下手にCやfortranで自分で書くよりずっと速い) で、今回の場合、非関数版のプログラムのほうでは、おそらくMatlabのJITコンパイラが、ループを行列計算に最適化して実行したのでしょう。 それに対して関数版では、最適化機能が効かずに実際にループとして実行されたのでしょう。 Matlabのループは本当に劇遅なので、VBAより遅いということもありえる話だと思います。 とくに、Matlab でループの中で、行列のサイズを毎回変更するとかすると、信じられないくらいに遅いですよ。

  • nag0720
  • ベストアンサー率58% (1093/1860)
回答No.3

関数を使わないよりは使ったほうが時間が掛かるのは当然ですが、単純に倍率で判断しないほうがいいでしょう。 示された例の場合は、関数が100000×1000回呼び出されたことによる増大ですから、 比率ではなく、差で比較したほうが適切です。 Matlabなら、1分6.8秒 BVAなら、12.41秒 増えたということです。 今回は、kei = i * i * j * j; という単純な計算だから何倍にもなっただけで、 これがもっと時間の掛かる処理、例えば、100000×1000回やって10分掛かるような処理でも、その差は同じですから、 Matlabは、10分と11分6.8秒 BVAなら、10分と10分12.41秒 ということになり、倍率としてはそれほど違いはないでしょう。 1時間掛かるような処理なら、そのくらいの差はほとんど気にならなくなります。

  • zwi
  • ベストアンサー率56% (730/1282)
回答No.2

MATLABは使ったことがないですがインタプリタとしては遅いようです。MATLABを純粋に演算機能だけ比べればVBAよりも速いのも情報通りです。 関数呼び出しは、処理後に続きに戻る情報を記録するため基本的に遅いのですが、MATLABは特別遅いのかもしれません。 関数呼び出しを高速に実行するにはコンパイラ系の言語を使ったほうが良いので、FORTANとかであれば問題がほぼありません(若干は速度低下します)。 MATLAB Compilerってもの有る見たいですから使えるのなら使ってみてはどうでしょうか? http://www.mathworks.co.jp/products/compiler/

  • Wr5
  • ベストアンサー率53% (2177/4070)
回答No.1

一般的に……関数(というかサブルーチン)を使用した場合は、 「サブルーチンから戻る時の戻り先」 「サブルーチン内で使用するローカル変数の領域の確保」 などを記憶して、 「制御をそのサブルーチンの開始位置に飛ばす」 という処理が入ります。 単純なループだけでしたらサブルーチン化した場合に上記のオーバーヘッドが重くなります。 # 言語仕様によるところもあるかと思われますが。

関連するQ&A

  • べき乗の計算が遅い理由

    VBAを使っていてふと気づいたことなのですが、 x ^ 2とx * xは計算結果は全く同じなのですが、x * xの方が6倍程度速く計算することができます。 Sub test() start_time = Timer For j = 1 To 10000 For i = 1 To 1000 y = i ^ 2 * j ^ 2 Next Next zikan = Format(Timer - start_time, "0.00") End Sub というプログラムの計算時間が3.80秒なのに対し、 y = i ^ 2 * j ^ 2をy = i * i * j * jに書き換えて実行すると 0.66秒になります。 恐らく、x ^ 2とx * xで計算アルゴリズムが異なるからだと思うのですが、 一般的によく知られたことなのでしょうか? また、どのようにアルゴリズムが異なるのでしょうか? また、VB以外の他のプログラミング言語にも見られるのでしょうか? ちなみに、 Function bekijou(x, y) bekijou = 1 For q = 1 To y bekijou = bekijou * x Next End Function という関数を作って、 y = bekijou(i, 2) * bekijou(j, 2)で実行してみたところ、計算時間は更に伸びて、 9.47秒になってしまいました。 これ以外に意外と知られていない計算速度を上げるためのコツなどがありましたら 教えてください。

  • リアルタイムでForループを表示

    Excel2010でVBAを使っています。 一つのループの計算のために10秒くらいかかるForループを10000回繰り返すプログラムを作った場合、 計算が終了するまでに30分程度時間がかかります。 計算途中にどこまで計算が進んだか分かるようにするため ダイアログにリアルタイムで変数iを表示できるようなプログラムを書きたいのですが どのようにすれば良いですか? Matlabだと「;」をつけないといった方法があるのですが VBAでやり方を教えてください。

  • MATLABでの行列の全要素の和

    MATLABでの行列の全要素の和を計算するのに下の例のようにfor文を使うのとsum関数を使うのとで結果が違います。何故ですか? 計算結果の差をとってみると極々微小な差が生じてしまいます(e-10位)。 シミュレーションの中でこのような処理を行っているのでより正確な方が知りたいです。 例 A=rand(10); plus=0; sum=0; for i=1:10  for j=1:10   plus=plus+A(i,j);  end end sum=sum(A(:));

  • clock関数は正確じゃないの?

    clock関数で時間を計測するとどうも狂ってしまいます。 おかしいので、このようなプログラムでclock関数の精度を調べてみました。 #include <stdio.h> #include <time.h> int main(){     int i,j;     clock_t start;     for(i=0;i<50;i++){         start = clock();         for(j=0;j<3000000000;j++);         printf("%4.3f秒\n",(double)(clock()-start)/CLOCKS_PER_SEC);         }     return 0; } このプログラムの実行結果はこちらです。 1.672秒 1.672秒 1.672秒 1.672秒 1.687秒←● 1.672秒 1.672秒 1.672秒 1.672秒 1.672秒 1.671秒 1.672秒 1.672秒 1.672秒 1.688秒←● ・・(略) 若干1.672で落ち着いているかと思いきや、時々狂っています。 この狂いは何が原因なんでしょうか? しかも狂うときは0.01位、結構ガツンと狂います。 しかもその狂い←●が等間隔で現れるのかと思いきや、そうでもありません。 この一見そろっているようで、時々ガツンと狂うのは何故なんでしょうか?

  • プログラムの実行速度は何で決まりますか?

    Excel2010附属のVBAで数値計算プログラムを作成し、 5年前に7万円(モニター抜無し)ほどで自作したデスクトップPC Intel Core 2 Duo E8400 3GHz 6MB LGA775 メモリ 4GByte 半年前に12万円で購入したノートPC i5-2540M Processor 2.6GHz メモリ 4GByte OSはともにwindows 7 64bit。 この二つで計算を行い計算時間の比較を行いました。 結果は、 デスクトップPCだと24秒 ノートPCだと29秒 という結果が得られました。 おおよそ24 : 29 = 2.6 : 30 の比率になっていると思います。 これってVBAによる計算はプロセッサの数を増やしても変わらず 一つのプロセッサのクロック周波数のみによって決まるということですよね? VBAを使って、マルチプロセッサを効率的に活用して計算速度を上げることは可能でしょうか? mathematicaやmatlabといった他のプログラム言語だとこういったことに対応しているのでしょうか?

  • matlabで複数条件のif文を行列計算で行うには?

    matlabで複数条件のif文を行列計算で行うには? matlabではfor文などは行列計算で回したほうが高速化できると聞きました。 for文でのやりかたはわかったのですが、複数条件でのif文ではどのように書けばよいのでしょうか? 自分のプログラムは以下のようなものです for i=1:100 for j=1:100 theta_kari=atan(abs(B(j,i))/abs(A(j,i))); if A(j,i)>=0&&B(j,i)>=0 theta(j,i)=theta_kari; elseif A(j,i)<0&&B(j,i)>=0 theta(j,i)=theta_kari+(pi/2); elseif A(j,i)<0&&B(j,i)<0 theta(j,i)=theta_kari+pi; elseif A(j,i)>=0&&B(j,i)<0 theta(j,i)=theta_kari+(pi/2*3); end end end よろしくお願いします

  • Excel VBAにて2の100乗を計算するには

    プログラミングの勉強でVBAを学んでいるものです 以下の様な問題を出されました 2の100乗の値を計算する。この値はLong型で表せる最大の値をはるかに超すので、十分な大きさのInteger型の配列を用意し、その各要素で各けたの値を表す。値を2倍するサブプロシージャ「二倍」を書いてプログラムを完成させ、値を計算せよ。 Option Explicit Sub 二の百乗() Const n As Integer = 200 Dim s(n) As Integer Dim i As Integer, j As Integer s(1) = 1 For i = 2 To UBound(s) 'UBoundは配列の最大の添え字を返す関数 s(i) = 0 Next i For i = 1 To 100 二倍 s Next i For i = UBound(s) To 1 Step -1 If s(i) <> 0 Then Exit For Next i For j = 1 To i Cells(1, j).Value = s(i - j + 1) Next j End Sub セル一つに計算結果を表示させられないことはよく分かるのですが、そのための2の掛け算を全く思いつきません 二倍のサブプロシージャをどのようにすればいいのでしょうか

  • MATLABの計算精度

    いま,  A=[0.803, -0.803, 1.2124, 0.803, -0.803, -0.803, 1.2124, 0.803]; とします. このとき,次の2通りの演算をします. (i) a=0; for I=1:8  if( A(I)>=0 )   a=a+A(I)*0.25;  else   a=a+A(I)*0.75;  end end (ii) Bi=[0.25, 0.75, 0.25, 0.25, 0.75, 0.75, 0.25, 0.25]; b=sum(Bi.*A); (i)と(ii)によって得られる和の値をMATLABのコマンド上で表示すると, 同じ値として表示されるのですが,a-b と計算すると,0 ではなく, 8.04e-15のようになります.(現環境ではMATLABが使えないので,確認できませんが,もっと大きいデータでやったとき場合には,上記のように誤差が生じました). 困っているのは,現在組んでいるプログラムでは計算した値の正負によって 異なった計算をする必要があり,計算精度が非常に重要になってきます. そのため計算結果が例えば +1.4e-15となるか -6.4e-16 となるかによってその後の計算結果が大きく変わってしまいます. MATLABでは,上記(i)(ii)のような誤差が生じるためか,+1.4e-15となってほしいところが,なぜか -6.4e-16となってしまいうまくプログラムが動きません. MATLABでは,for文によって和をとるのではなく,一回でまとめて計算しなければ誤差が生じるのでしょうか? また,MATLABで計算精度を変更できないのでしょうか? (formatコマンドによって,「format short g」「format double g」としましたが有効な小数点が増減するだけで結果は同じでした.) よりよい精度で計算するにはどうしたらよいのでしょうか? 回答よろしくお願いします.

  • ガウス関数を少しずつずらして足し上げるプログラム

    ある値をある検出器に入力したときに出力結果がその入力したを中心にガウス関数のように広がってしまう場合を考え,ある関数の値を小刻みに入力したとき、その関数が出力されたときそれぞれ値のガウス関数の広がりにより重ね合わされてどのように見えるか数値計算してみようと思い、以下のソースコードを書いて見ました。行列で考えてそれぞれの値の寄与を足しあげて見ようとしたのしたのですが、実行結果をみると明らかにおかしい10^19などの数値が見られます。これはなぜでしょうか。私のソースコードのどこに問題があるのでしょうか。お手数をお掛けしますが回答よろしくおねがいします。 インデントが反映されていませんでしたらすみません。 ---------------------------ソースコード------------------------------------------ #include<stdio.h> #include <stdlib.h> #include<math.h> #define N 100 #define f(x) pow(x,-1.7) //この関数が検出器に入力したときどのように見えるか知りたい// void mat_product(double ec[N],double r[N][N],double e[N]) { int i,j; for(i=0; i<N; i++){ for(j=0; j<N;j++){ ec[i]+=r[i][j]*e[j]; } } } int main(void) { int i,j; double ec[N],r[N][N],e[N]; double max,min, ei,E,sigma,min1,min2,m,pi; sigma=0.08; //keV// max=10.5; min=0.5; min1=0.5; min2=0.5; m=0.5; ei=(max-min)/N; pi=3.141592; for(i=0; i<N ;i++){ for(j=0; j<N ;j++){ r[j][i]=(1/(pow(2*pi,0.5)*sigma))*exp(-1*pow(m-min1,2)/(2*pow(sigma,2))); m+=ei; } e[i]=f(min2); m=0.5; min1+=ei; min2+=ei; } mat_product(ec,r,e); for(i=0; i<N; i++){ printf("%g\t%g\n",min,ec[i]); min+=ei; } return 0; } ---------------------------出力結果--------------------------------- 0.5 22.0512 0.6 23.8139 0.7 18.9 0.8 14.9642 0.9 12.1864 1 10.1512 1.1 8.60991 1.2 7.4112 1.3 6.45833 1.4 5.68688 1.5 5.05253 1.6 4.52387 1.7 4.07814 1.8 3.69846 1.9 3.37209 2 3.08926 2.1 2.84239 2.2 2.62547 2.3 2.43375 2.4 2.26337 2.5 2.1112 2.6 1.66592 2.7 1.85166 2.8 1.7404 2.9 1.63941 3 1.54742 3.1 1.46337 3.2 1.38635 3.3 1.31558 3.4 4.47979e+30 3.5 1.19017 3.6 1.13444 3.7 1.08275 3.8 1.0347 3.9 0.989949 4 0.9482 4.1 0.909181 4.2 0.872652 4.3 0.838401 4.4 0.806238 4.5 0.775991 4.6 0.747509 4.7 0.720652 4.8 0.695296 4.9 0.671329 5 0.648648 5.1 0.627161 5.2 0.606783 5.3 0.587437 5.4 0.569053 5.5 0.551566 5.6 0.534917 5.7 0.519053 5.8 0.503924 5.9 0.489483 6 0.475689 6.1 0.462502 6.2 0.449887 6.3 0.437809 6.4 0.426239 6.5 0.415146 6.6 0.404506 6.7 0.394292 6.8 0.384482 6.9 0.375054 7 0.365988 7.1 0.357265 7.2 0.348868 7.3 0.34078 7.4 0.332986 7.5 0.325471 7.6 0.318222 7.7 0.311226 7.8 0.304472 7.9 0.297947 8 0.291642 8.1 0.285546 8.2 0.27965 8.3 0.273945 8.4 -0.0404521 8.5 0.263075 8.6 0.257894 8.7 0.252874 8.8 0.248007 8.9 0.243288 9 0.238709 9.1 7.46629e+19 9.2 0.229953 9.3 0.225764 9.4 0.221696 9.5 0.217743 9.6 0.2139 9.7 0.210164 9.8 0.206531 9.9 0.202996 10 0.199557 10.1 0.196209 10.2 0.192868 10.3 0.185672 10.4 0.140717

  • Fortranの素数のプログラム

    5000万までの素数を求めるプログラムなのですが、私の作ったプログラムは実行時間26秒くらいかかります。 先生が言うには10秒台が出るとのことですが、私は頑張っても時間を短くすることができません。 下に私の作ったプログラムを載せますので短くする方法を教えて下さい。 integer table(2:50000000),pno(50000000),cnt,m,i,j m=sqrt(50000000.) do 10 i=2,m do 10 j=i*2,50000000,i table(j)=1 10 continue do 20 i=2,50000000 if(table(i).eq.0)then cnt=cnt+1 end if 20 continue write(6,610)cnt 610 format('sosu no goukei =',i8) end