インデックスを使用する for ループとコレクションを使用する for ループのパフォーマンス比較

質問:

以下を使用する場合と

for i = 1 to theArray.count do( ... )

および

for i in theArray do( ... )

どちらも同じでしょうか。どちらかが速いということはありますか。何かほかに注意する点はありますか。

回答:

どちらも同じように使用できますが、利点やパフォーマンスの特徴に違いがあります。

1 つ目のバージョンでは、インデックスを使用できます。このため、進行状況バーの表示を動かしたい場合や、要素数が同じである別の配列にアクセスするのにインデックスを使用したい場合は、通常この方法を使用します。i 番目のオブジェクトを取得できるのは同じですが、加えて、これまでに処理したオブジェクトの数も取得することができます。

例:

    theArray = for i = 1 to 10  collect i
    (
    for i = 1 to theArray.count do
    (
      --assuming prg_bar is a progressbar UI control:
      --prg_bar.value = 100.0*i/theArray.count
      --some more code here...
    )
    )

次の 2 つ目の例では、現在のインデックスを追跡するため、独自のカウンターとしてローカル変数を追加する必要があります。

例:

    (
    cnt = 0
    for i in theArray do
    (
      cnt+=1
      --prg_bar.value = 100.0*cnt/theArray.count
      --some more code...
    )
    )

また、1 つ目の形式では、逆方向にループを実行できます。これは、配列を処理しながら要素を削除する場合に必要です。以下の例は、1000 のランダムな整数を持つ配列を作成し、2 つ目の for ループで値が 50 よりも小さい要素をすべて削除しています。

例:

    (
    theArray =for i = 1 to 1000 collect random 1 100
    for i = theArray.count to 1 by -1 where theArray[i] < 50 do
      deleteItem theArray i
    )

ただし、速度の点では、配列要素を直接ループしたほうが速くなります。 10000 個の要素を持つ配列を作成し、これを 1000 回ループで回して、重要な時間値を取得して評価しましょう。内側のループ自体は、特に何の処理も行っていません。単に配列全体をループで回し、i 番目の要素にアクセスしています。

インデックスによるループ:

    (
    theArray = for i = 1 to 10000 collect random 1 100
    st = timestamp()
    for j = 1 to 1000 do
      for i = 1 to theArray.count do theArray[i]
    format "% ms\n" (timestamp()-st)
    )

このコードは、ある特定のマシンで 5.6 秒 (+/- 0.1 秒) で実行されます。

たとえば、1 から配列カウントまでループし、配列要素を何の目的にも使用しないという場合には、インデックス (theArray[i]) による配列要素への実際のアクセスを削除することで、この時間は 4.6 秒に減少します。しかし実際にはこのような状況はありえないでしょう。

比較: コレクションのループ

    (
    theArray =for i = 1 to 10000 collect random 1 100
    st = timestamp()
    for j = 1 to 1000 do
      for i in theArray do i
        format "% ms\n" (timestamp()-st)
    )

上記のコードは、同じマシンで 4.3 秒 (+/- 0.1 秒) で実行されます。これは先ほどの 1.3 倍の速度です。変数 i には、すでに n 番目の要素が含まれているため、配列要素の値を実際に何らかの処理に使用するかどうかは問題になりません。

進行状況の更新など別の処理にカウンターが必要な場合、1 つ目の方法のほうが 2 つ目よりも約 4 倍も高速になります。

低速

    (
    theArray =for i = 1 to 10000 collect random 1 100
    st = timestamp()
    for j = 1 to 1000 do
    (
      cnt = 0
      for i in theArray do cnt +=1
    )
    format "% ms\n" (timestamp()-st)
    )

上記のコードは、同じマシンで 14.3 秒で実行されます。これは、変数 cnt の値を 1000 万回上書きするために生じる、メモリのオーバーヘッドが原因です。

つまり、カウンタ変数がコードにとって重要である場合は、for i = 1 to theArray.count do() の形式を使用すべきです。それ以外の場合には、一般に少し高速であるコレクションの直接ループをなるべく使用するようにします。

前のヒント

別の方法がある場合は実行関数を使用しない