[[20201026122736]] 『For EachとFor Nextの使い方』(よん) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『For EachとFor Nextの使い方』(よん)

こんにちは
For Eachが一番早いよ!って感じの記事を読んだときに、For NextやDo Loopと違って毎ループでセルを直接参照しないから早いんだよって感じの説明が書いてあったのですが、事前にvariant型にセル範囲をぶち込んでからFor Nextをするのと何が違うんや?と思ったので質問させていただきました。
よろしければ回答お願いします。

< 使用 Excel:Excel2007、使用 OS:WindowsVista >


 特に検証していないですが、頭の中で考えたこと。

 「For Eachが一番早いよ!」と言う話自体、ピンと来ないですね。

 For Each ・・In ○○

 ○○がどうなっているかですよね。
  そこがRangeオブジェクトなら、結局、セルを直接参照しているんじゃないですか?(してないの? 自信ないですけど)
  そこがVariant型にぶち込んである配列だったら、どう言う結論になるんですか? (それもFor Eachなんですけど。)

 速い/遅いと言ったって、読み出し処理ですよね。
 目くじら立てるところでもない気がします。

 速度の問題は、書き込み処理で真価が問われるんだと思いますけどねぇ・・

(半平太) 2020/10/26(月) 13:10


 単純にセル位置をわずかな行、列計算しているかしてないかだけだと思いますけど?
 Range で書くより Cell で表現した方が速いとも聞くし。
 添え字使った方がイメージしやすいし。

 おまけ

 Sub aaaa()
 Dim Tb(1 To 5) As Variant
 Dim vv As Variant
 For Each vv In Tb
    i = i + 1
    vv = i 
 Next
 Range("A1:E1").Value = vv
 End Sub

 Sub bbb1()
 Dim vv
 Range("A3:E5").ClearContents
 For Each vv In Range("A3:E5")
    i = i + 1
   vv = i
 Next
 End Sub

 Sub bbb2()
 Dim vv
 Range("A3:E5").ClearContents
 For Each vv In Range("A3:E5")
    i = i + 1
   vv.Value = i
 Next
 End Sub

 Sub bbb3()
 Dim vv As Range
 Range("A3:E5").ClearContents
 For Each vv In Range("A3:E5")
    i = i + 1
   vv = i
 Next
 End Sub

 Sub bbb4()
 Dim Tb As Variant
 Dim vv As Variant
 Range("A3:E5").ClearContents
 Tb = Range("A3:E5").Value
 For Each vv In Tb
    i = i + 1
   vv = i
 Next
 Range("A3:E5").Value = Tb
 End Sub

(Why) 2020/10/26(月) 13:43


 >For Eachが一番早いよ!
 前提が省略されすぎていてなんとも。

 『○○するときには、For Eachが一番早い』の○○部分はなんですか?
 その記事はどうやったら見れますか?
(´・ω・`) 2020/10/26(月) 14:03

 書き込み方法だけの話だと思いますけど・・・
 試してみたところ、
 書き込み速度の差 > 参照速度 の影響が強そう
 For Eachでtbl回すのって見たことないですね・・・

    Sub mktestdate()
        Dim i As Long
        Dim tbl(1 To 10000, 1 To 1)
        Randomize
        For i = 1 To 10000
            tbl(i, 1) = Int(Rnd() * 1000 + 1000)
        Next i
        Range("A1:A10000").Value = tbl
    End Sub
    Sub aa()
        Dim t As Double
        Dim r As Range
        Dim tt As Variant
        Dim r1(1 To 10000, 1 To 1)
        Dim tbl As Variant
        Dim i As Long
        Dim msg As String
        t = Timer
        For Each r In Range("A1:A10000")
            r.Value = r.Value + r.Value
        Next r
        msg = "for each 都度書込: " & Timer - t & "ミリ秒" & vbCrLf

        i = 1
        t = Timer
        For Each r In Range("A1:A10000")
            r1(i, 1) = r.Value + r.Value
            i = i + 1
        Next r
        Range("A1:A10000").Value = r1
        msg = msg & "for each 一括書込: " & Timer - t & "ミリ秒" & vbCrLf

        i = 1
        t = Timer
        tbl = Range("A1:A10000").Value
        For Each tt In tbl
            tbl(i, 1) = tt + tt
            i = i + 1
        Next tt
        Range("A1:A10000").Value = tbl
        msg = msg & "for each tbl都度書込: " & Timer - t & "ミリ秒" & vbCrLf

        t = Timer
        tbl = Range("A1:A10000").Value
        For i = 1 To 10000
            Cells(i, "A").Value = Cells(i, "A").Value + Cells(i, "A").Value
        Next i
        msg = msg & "for 都度書込: " & Timer - t & "ミリ秒" & vbCrLf

        t = Timer
        tbl = Range("A1:A10000").Value
        For i = 1 To 10000
            tbl(i, 1) = tbl(i, 1) + tbl(i, 1)
        Next i
        Range("A1:A10000").Value = tbl
        msg = msg & "for 一括書込: " & Timer - t & "ミリ秒" & vbCrLf

        MsgBox msg
    End Sub

(稲葉) 2020/10/26(月) 14:16


 >For Eachが一番早いよ!って感じの記事を読んだ

あぁ、その噂は聞いたことがありますが、
結局その後、その値なりオブジェクトなりをどう使うかが重要で、
ループの書き方で差はないようです。

 >事前にvariant型にセル範囲をぶち込んでからFor Nextをするのと何が違うんや?
セルの値を参照するだけなら、変数に代入してからループした方が速いですし、
セルそのものを代入するのなら、2度手間ですね。まぁ、時間は変わりませんが。

噂は噂ですし、バージョン等でも違うかも知れません。
結局は自分で実験してみて、確認する方が確実です。
たぶんこういう比較をしてFor Each〜nextの方が速いと言われてると思います。

Option Explicit

Sub test1()

    Dim c As Range
    Dim v
    Dim t

    t = Timer

    For Each c In Columns(1).Cells
        v = c.Value
    Next

    MsgBox Timer - t
End Sub

Sub test2()

    Dim i As Long
    Dim v
    Dim t

    t = Timer

    For i = 1 To Rows.Count
        v = Cells(i, 1).Value
    Next

    MsgBox Timer - t
End Sub

Sub test3()

    Dim i As Long
    Dim vv
    Dim v
    Dim t

    t = Timer

    vv = Columns(1).Value
    For i = 1 To UBound(vv, 1)
        v = vv(i, 1)
    Next

    MsgBox Timer - t
End Sub

 >Range で書くより Cell で表現した方が速いとも聞くし。

僕はCellsよりRangeの方が速いって聞きましたけど、
どうなんでしょうね。

Valueプロパティも省略した方が速いって聞きますし、

(まっつわん) 2020/10/26(月) 15:13


 ここら辺の記事が元でしょうか?
http://officetanaka.net/excel/vba/speed/s5.htm

 セルへのアクセスの回数を減らすのが、ループの方法より影響が大きいので同列に扱わない方が
 よい気はしますが。
(QS) 2020/10/26(月) 16:27

皆さんがRangeの話をしているところに水を指すようですが、Collectionオブジェクトが対象の場合はForEachの方が高速になる時があります。

http://blog.starbug1.com/archives/568

書き方が悪いだけの場合もあるので、速度の話をするときは「前提」が大事です。
(通りすがり) 2020/10/26(月) 16:57


 ええと、私のコードは、列神話がはびこっているみたいなので、
 その教訓として、For Each vv in 配列 で配列に書き込めない?
 と、言うのを知ってもらうだけの物。
 なんか、VBでは出来るみたいなんですが。
 二十年ぐらい前?大学の教授さんとかが、For Each は、速い、
 For Each で、配列の初期化と言うのを雑誌で見て、それを真似たら、VBAでは出来なかったので。

 >僕はCellsよりRangeの方が速いって聞きましたけど

 あれ?Rangeだったっけ?覚えてないと。
 後で書き込もうとしていたので、そうかもしれないですね。

(Why) 2020/10/26(月) 21:29


 >その教訓として、For Each vv in 配列 で配列に書き込めない?
 これは便利かもですね
 tbl = [a1:d5].value
 for i = 1 to 10
     for each c in index(tbl,i,0)
         c = c + 1
     next c
 next i
 [a1:d5].value = tbl

 ためしてないですが、こんな感じの使い方もできます?
 配列にindexの場合は、参照渡しじゃなくて、値渡しになってしまいますかね?

 余談になりますが
[[20200811090110]] 『「Dictionary」からの取出し』(T20)
 こっちでも半平太さんとγさんに教えてもらって実験してみたので
 質問者さんの参考になれば。
(稲葉) 2020/10/26(月) 22:54

 VBSでINDEXなかった・・・
 vbsからxlApp作ってIndexから1次配列にする?
 そこまでしてForEach使う必要もないですよね。

(稲葉) 2020/10/27(火) 09:07


 本題については、議論されているので、周辺話題を。

 officetanakaさんのサイト「VBA高速化テクニック」
 http://officetanaka.net/excel/vba/speed/
 に一度目を通されると有益かと思います。

 RangeとCellsとでは、Cellsのほうが早いらしいが、
 田中さんは、正鵠を射たことを書かれている。
 | どちらを使うかは速度差で決めることじゃなく、必然性がすべてです。
 | Rangeを使うべき場面だったら、たとえ多少遅くたってRangeを使うべきだし、
 | Cellsを使うべきケースだったら迷わずにCellsを使うべきです。
 ま、そのとおりですな。

 Collectionについて言及がありましたので、少し補足を。
 indexを経由してitemを取りに行っているのに対して、
 For each は、itemを直接走査するので、その分早くなります。

 なお、Collectionより色々な面で拡張されているのがDictionaryで、
 私はもっぱらこちらを使いますが、これには、For Each しかありません。
 Collectionとの違いは、CollectionがItemを走査するのに対して、
 DictionaryはKeyを走査することです。
 (もっともKey配列に対してindexでアクセスすることはできますが、
   たぶん若干なりとも遅くなるのではないかと思います。)

 For Eachというiteratorが定義されている場合は、概ねこちらを使ったほうが早いです。
 そのための機能ですから。
(γ) 2020/10/27(火) 09:53

皆さんたくさんの回答ありがとうございます。
皆さんのお話の40パーセントくらいは理解できたかと思います。
ForEach下だと早くできる処理を無理やりForNextでやったりすると明確な差が出てくるよって感じなんですかね?

自分が見つけた記事はこちらで、以下の文言に違和感を感じたので質問させてもらいました。

https://www.exvba.com/2251/

For Each構文は、ループに入るタイミングで、あらかじめ作業対象のセルの参照の集合を、メモリに読み込みます。
そして、その読み込まれた集合に対して処理をしていきます。
一方、For Next構文、Do Loop構文では、「Range(“C” & c)」といった構文に来る都度、改めて、そこで言われているセルがどこなのか?ということを調べにいきます。

その分のオーバーヘッド(IT用語: 間接的・付加的に必要となる処理とそれにより発生する負荷の大きさ)のせいで、時間がかかってしまうんですね。

この部分を読んで、
Sub test()
Dim r() As Long, c As Long, n As Long
ReDim Preserve r(9, 0)
For c = 2 To 11
n = c - 1
r(c - 2, 0) = n
Next c
Range("A2:A11").Value = r
End Sub

こんな感じだとForNextでも速度変わらないんでは?
と思った次第です。
初めの質問で前提が抜けていて申し訳ないです。

これも結局演算自体の処理というよりも書き込み速度が大きく影響してると思ったらいいのかなぁと何となく理解はできたと思います。

結局どっちを使ったらいいのってとこは、実用レベルだと誤差の範囲だけれど最効率を目指すならば走査しやすいほうだと考えればいいって感じなんですかね?
(よん) 2020/10/27(火) 11:14


引用サイト拝見。
速度を問題にしながら
逐次書き込みしている段階で論外です。

(γ) 2020/10/27(火) 11:43


 記事読ませていただきました。
 すこし古い記事なので、今のバージョンとは違うのかもしれませんが、
 Valueプロパティを読み書きする場合の速度については、
 少しだけ For Eachが早いのは本当だと思います。
 余計な処理がいらないので。

 >実用レベルだと誤差の範囲だけれど最効率を目指すならば

 最効率を目指すならば、全体の最適化が重要なので、
 これだけでは決まりませんね。
(´・ω・`) 2020/10/27(火) 13:08

私もできる限り最速を目指してコードを書いていた時期がありましたが、
最近は可読性を重視しています。(セル毎書きこみとかは論外ですが)

ルールが無かったので、自身でコーディングルールを作りました。
1カ月もそのコードに触れていないと忘れますから。

For Each 対象が全ての場合
For Next 繰り返す数が決まっている場合
Do Loop フラグが立たない限り繰り返す場合
見たいに決めておくと、コードを見たとき大枠が読めるので便利ですよ。
(tkit) 2020/10/27(火) 13:47


お返事ありがとうございます。
tkitさんの言うように可読性を上げるために使い分けるようにしてみようと思います。
たくさんの回答ありがとうございました。
(よん) 2020/10/28(水) 12:33

  >事前にvariant型にセル範囲をぶち込んでからFor Nextをするのと何が違うんや?と思った

 ・・で、何が違うのか分かったんですか?

 紹介されたサイトのコードは、「論外」とも言われた程のしょうもない代物なんですけど。

(半平太) 2020/10/28(水) 13:02


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.