web-dev-qa-db-ja.com

JuliaのPush!()メソッドとappend!()メソッドはどれくらい効率的ですか?

this ページでは、メソッドPush!()append!()が非常に効率的であると書かれています。

私の質問は、それらがどれほど正確に効率的かということです。

つまり、

最終的な配列のサイズを知っている場合、配列を事前に割り当てるか、append!()/Push!()も同じくらい効率的でしょうか?

ここで、1つが最終的な配列のサイズを知らない場合を考えてみましょう。たとえば、複数の配列を1つの大きな配列にマージします(Aと呼びます)。

これを実現する2つの方法:

  1. append!()-各配列をAに変換します。このサイズは、事前に割り当てられていません。
  2. マージされた配列Aの最終的なサイズを見つけるために、各配列の最初の合計次元。次に、Aを事前に割り当て、各配列の内容をコピーします。

この場合、どちらがより効率的でしょうか?

14
aberdysh

このような質問に対する答えは通常、「状況によって異なります」です。たとえば、どのサイズの配列を作成しようとしていますか?配列の要素タイプは何ですか?

しかし、ヒューリスティックの直後であれば、簡単な速度テストを実行してみませんか?たとえば、次のスニペット:

_function f1(N::Int)
    x = Array(Int, N)
    for n = 1:N
        x[n] = n
    end
    return(x)
end

function f2(N::Int)
    x = Array(Int, 0)
    for n = 1:N
        Push!(x, n)
    end
    return(x)
end

f1(2)
f2(2)

N = 5000000000
@time f1(N)
@time f2(N)
_

_Push!_の使用は、事前割り当てよりも約6倍遅いことを示唆しています。 _append!_を使用して、より少ないステップでより大きなブロックを追加した場合、乗数はほぼ確実に少なくなります。

これらの数値を解釈するときは、「何!?6倍遅い!?」というひざまずく反応に抵抗してください。この数は、プログラム/関数/サブルーチン全体にとって配列構築がどれほど重要であるかという文脈で配置する必要があります。たとえば、配列の構築がルーチンの実行時間の1%のみで構成されている場合(最も一般的なルーチンの場合、配列の構築はmuch 1%未満で構成されます)、ルーチンが100秒間実行される場合、1秒はアレイの構築に費やされます。これに6を掛けると、6秒になります。 99秒+6秒= 105秒。したがって、事前に割り当てる代わりに_Push!_を使用すると、プログラム全体の実行時間が5%増加します。あなたが高頻度取引で働いていない限り、あなたはおそらくそれを気にするつもりはないでしょう。

私自身の通常のルールは次のとおりです。簡単に事前に割り当てることができる場合は、事前に割り当てます。しかし、_Push!_によってルーチンのコーディングがはるかに簡単になり、バグが発生する可能性が低くなり、適切な配列サイズを事前に決定しようとする混乱が少なくなる場合は、何も考えずに_Push!_を使用します。

最後の注意:_Push!_がどのように機能するかを実際に確認したい場合は、 Julia sourceccall

UPDATE:OPは、コメントで_Push!_とMATLABのarray(end+1) = nのような操作の違いについて質問しました。最近MATLABでコーディングしていませんが、古い論文のコードはすべてMATLABにあるため、自分のマシンにコピーを保持しています。私の現在のバージョンはR2014aです。私の理解では、このバージョンのMATLABでは、配列の最後に追加すると、entire配列が再割り当てされます。対照的に、Juliaの_Push!_は、私の知る限り、_.NET_のリストとよく似ています。ベクトルに割り当てられたメモリは、ベクトルのサイズが大きくなるにつれて動的にブロック単位で追加されます。これにより、実行する必要のある再割り当ての量が大幅に削減されますが、再割り当てがまだ必要であると理解しています(この点で修正できてうれしいです)。したがって、_Push!_はMatlabで配列に追加するよりも速く動作するはずですはるかに。したがって、次のMATLABコードを実行できます。

_N = 10000000;
tic
x = ones(N, 1);
for n = 1:N
    x(n) = n;
end
toc


N = 10000000;
tic
x = [];
for n = 1:N
    x(end+1) = n;
end
toc
_

私は得る:

_Elapsed time is 0.407288 seconds.
Elapsed time is 1.802845 seconds.
_

したがって、約5倍の速度低下です。タイミング方法論に適用される極端な非厳密さを考えると、これはジュリアの場合と同等であると言いたくなるかもしれません。ただし、ジュリアで_N = 10000000_を使用して演習を再実行すると、タイミングは0.01秒と0.07秒になります。これらの数値の大きさがMATLABの数値と大きく異なるため、実際に内部で何が起こっているのか、MATLABの5倍の速度低下とMATLABの6倍の速度低下を比較することが正当であるかどうかについて主張することに非常に神経質になります。ジュリア。基本的に、私は今、自分の深みから外れています。たぶん、MATLABが実際に内部で何をしているのかをもっと知っている人は、より多くの洞察を提供することができます。 Juliaに関しては、私はCコーダーではないので、ソース(MATLABとは異なり公開されています)を調べることで多くの洞察を得ることができるとは思えません。

14
Colin T Bowers