配列データを高速で操作するには?

解決


LUCIA  2004-09-27 05:34:55  No: 116527

初めましてVB初心者です。過去ログなども拝見したのですが見落としているかうまく検索出来ませんでしたので質問させて頂きます。
配列データに処理を施して元の配列に戻す処理をしていますがあまりにも速度が遅く感じられるので何とかならないものかと思いまして・・・
順序はファイルをバイナリで読み込む→データを操作→ファイルに上書き、という感じです。VB歴も浅く動けばよかろうという考えで取り組んでいたのですがそうも行かなくなりまして…よろしくお願いします。
VB6、WinXPで組んでましてコードは下記のような感じです。(データは数メガ〜数十メガのものもあります)

for i=0 to Ubound(bytData)
    bytData(i) = bytData(i) Xor bytKey(i Mod 3)
Next i


Say  2004-09-27 07:36:52  No: 116528

ロジックはこれ以上どうしようもないぐらいシンプルですから、
遅いというならC/C++あたりでDLLを組んではいかがでしょう?

XOR処理しかしないのなら、DLLにしてVBから呼ぶより
C/C++かMASMでコンソールアプリ作ったほうが簡単だったりしますが。
(慣れた人間なら数分で作れるでしょう。)


LUCIA  2004-09-27 10:47:36  No: 116529

Sayさん返信頂きましてありがとうございます。
残念ながらC、C++、MASMの知識がないのですがもう少し勉強して挑戦してみたいと思います。30半ばの私には荷が重いですが頑張ってみます…どうしても実現しなければいけないものですから…また報告に参ります。


Say  2004-09-27 11:02:58  No: 116530

若干の仕様変更が可能なら、
キーデータを3バイトではなく4バイトにすれば、
long配列を使うことでループを1/4に減らせ、
byteよりlongの方が処理が速い上、
剰余演算もせずに済みますから
かなり高速化できるのではないかと。


葉月  2004-09-27 18:02:48  No: 116531

>for i=0 to Ubound(bytData)
の所で最初に

lngData = Ubound(bytData)

for i=0 to lngData
    〜処理〜
next i

としても同じなのかな?


36NET  2004-09-27 18:05:55  No: 116532

経験上変数に数Mのデータを持たせてることが原因のような気がします
現在の
バイナリを全部読み込む→データを操作→ファイルに上書き
って流れを
(データを1000バイト(※1)くらいで読み込み→データを操作→ワークファイルに出力)をファイルEOFまでループ→ファイル書き換え
に修正すれば早くなる気がします

※1  何バイトが一番良いかはやってみないとわかりません


LUCIA  2004-09-27 22:22:37  No: 116533

Sayさん、葉月さん、36NETさん、返信ありがとうございます!
Sayさんの仰っていた他言語での制作orDLLは一朝一夕では私には無理ですので継続して勉強する傍ら少しでも速度向上が望めないかと思い取り組んでおります。

葉月さんに返信頂いたサンプルですが

lngData = Ubound(bytData)
t1 = Timer
for i=0 to lngData
    〜処理〜
next i
t2 = Timer
Debug.Print t2 - t1

として10Mのバイト配列で10回計測しましたところ平均0.7%ほど速度が向上出来ました。ありがとうございます!

Sayさん、36NETさんに返信頂いた分はこれから取り組んでみたいと思います。これが仕事でなければ楽しく学ばせて頂けるのですが…それでも楽しくなって参りました!また夜に参上致します!


ぴろあき  2004-09-28 19:16:56  No: 116534

読み書きの部分が重要なんですが、読み書きの部分のコードを掲載できますか?

36NETさんは、メモリ容量をオーバーしていると予想しているようですが、
どうも1データずつ読み込んで(書き込んで)いるような気がするのですが。。。
どんなマシンか知りませんが、まともな読み書きをしていれば、そんな遅くならないと思いますよ。

36NETさんに補足して、
ハードディスク読み書きの最小単位がクラスタサイズなので、
クラスタサイズの倍数のバイト配列で読み書きすると効率がよくなりますよ。
OSやファイルシステムやドライブサイズによって異なりますが、
512バイトの倍数と覚えておけば間違いないでしょう。
参考URL:
http://support.microsoft.com/default.aspx?scid=kb;ja-JP;Q314878
10M = 10485760ならいいのですが、
10M = 10000000ならば10485760にすると効率がよくなります。
#今回の場合、分母が10Mと大きいので512バイト程度の効率は問題にならないですが、
#覚えておいて損はないでしょう。

#大容量メモリを搭載しているならば、10Mを10回よりも100Mを1回の方が速いです。


LUCIA  2004-09-29 00:34:09  No: 116535

昨夜は机にむかったままダウンしておりました(^^;

ぴろあきさん、返信頂きありがとうございます。
>読み書きの部分が重要なんですが、読み書きの部分のコードを掲載できますか?
こんな感じです。何かお気づきの点がありましたらご教授して頂けますと助かります。
intFileNumber = FreeFile
Open strFileName For Binary As #intFileNumber
    Get #intFileNumber, , bytData
Close #intFileNumber
書き込みはPutでやっております。
>#今回の場合、分母が10Mと大きいので512バイト程度の効率は問題にならないですが、
>#覚えておいて損はないでしょう。
大変勉強になります。ありがとうございます!

>(データを1000バイト(※1)くらいで読み込み→データを操作→ワークファイルに出力)をファイルEOFまでループ→ファイル書き換え
まだこの状態には至っておりませんがデータ操作を50k(色々試したところ私の環境で設定したサイズです)区切で操作したところ15%ほど早くなりました!

>キーデータを3バイトではなく4バイトにすれば、
>long配列を使うことでループを1/4に減らせ、
いまひとつ方法が見えずにおります…お手本を見せて頂けないでしょうか

追伸:同じ環境下で上司が作ったものを実行してみたところ確かに私の作ったものより早かったです…ソースは見せてくれなかったです…後々、役に立つから今のうちに苦労しておけと言われてしまいました(TT
仕事以外では弟のように可愛がってくれるのですが仕事はいつも厳しいです…


ねろ  2004-09-29 01:52:25  No: 116536

基本的にはぴろあきさんの意見に賛成何です。
殆どの時間がファイルの読み書きに使われていると思います。
Binary データーの読み書きはVBでは限界があります。
そこでAPIのReadFileとWriteFileを使用すると断然速くなります。
C言語で作った物とスピードにおいて遜色ありません。
私はビットマップの読み書きや、Waveファイルの読み書き
に使っています。


ぴろあき  2004-09-29 19:07:31  No: 116537

>Open strFileName For Binary As #intFileNumber
>    Get #intFileNumber, , bytData
>Close #intFileNumber

ってことはbytDataは配列で、一回で読込を済ませているという事ですね。
私の予想は外れました。
36NETさんの予想通り、空きメモリ容量が少なくなりすぎている事が原因な気がします。
予想であれこれ言うのも何なので、何秒かかってるか教えてもらえますか?

えと、アドバイス無いのも何なので、一応アドバイスを。。。
プログラムの優先順位を上げれば速くなります。

コマンドプロンプトから「start /REALTIME Test.exe(プログラム名)」
でプログラムの優先順位が最高で実行されます。(副作用あり)
プログラムに組み込むには、SetThreadPriority(API)ですがVB6では使えないかも。
組み込むなら、コマンドプロンプトを起動してスタートさせるだけの別プログラムを用意するといいかも。


ガッ  2004-09-29 20:45:42  No: 116538

ぃゃ、例として、リアルタイムは結構ヤバイのではないかと…


ぴろあき  2004-09-29 22:48:56  No: 116539

>>ガッ様
確かに。
しかもこのケースはCPU100%使用するから、あまり効果が無いかも。
あぁ、最初の予想が外れて何かあせっていました。
などと言い訳してみたり、恥ずかしい限りです。

お詫びして訂正するとともに、指摘してくださったガッ様に感謝します。


GOD  2004-09-30 02:04:20  No: 116540

早くしたいならベタベタに作ってしまうとか。
(i mod 3)だからできること。
3の部分がnになったときは使えないので。

    Dim bytData(10) as Byte
    For i = 0 To UBound(bytData) - 3 Step 3
        bytData(i) = bytData(i) Xor bytKey(0)
        bytData(i + 1) = bytData(i + 1) Xor bytKey(1)
        bytData(i + 2) = bytData(i + 2) Xor bytKey(2)
    Next
    '残りの部分の補正は必要です。
    '(bytData(9), bytData(10)は上記FORでは処理されていない)
    'ここは考えてね。


LUCIA  2004-10-01 00:07:31  No: 116541

中1日あいてしまいました(^^;
ねろさん、ぴろあきさん、ガッさん、GODさん、お返事頂きありがとうございます!
>そこでAPIのReadFileとWriteFileを使用すると断然速くなります。
早速やってみました(使い方を覚えるのに苦労しました…)
早いです!驚くほど!今は一括で読み書きしてますが36NETさんのアドバイスにあるように
>(データを1000バイト(※1)くらいで読み込み→データを操作→ワークファイルに出力)をファイルEOFまでループ→ファイル書き換え
も考慮するともっと効率が良くなるのかも合わせて挑戦してみたいと思います。
>コマンドプロンプトから「start /REALTIME Test.exe(プログラム名)」
>でプログラムの優先順位が最高で実行されます。(副作用あり)
>ぃゃ、例として、リアルタイムは結構ヤバイのではないかと…
こちらに関しましては今回は私のスキルでは危険と判断し試しておりませんが貴重なアドバイスとしてもう少し深く調べてみたいと思います!

GODさんに頂きましたアドバイスには下記コードを追加して使わせて頂いております。サンプルコードも少し改造すれば汎用的に使えていい感じです!速度も向上しました!

Dim lngEOF As Long
lngEOF = UBound(bytData)
lngLoop = lngEOF - lngEOF Mod 10
Do Until lngLoop = lngEOF + 1
    bytData(lngLoop) = bytData(lngLoop) Xor bytKey(lngLoop Mod 10)
    lngLoop = lngLoop + 1
Loop

やっとOKをもらえる事になりました!返信して下さった皆さんありがとうございます!
速度的には満たされたのですがSayさんの仰っていた
>long配列を使うことでループを1/4に減らせ
なんですが4バイトのデータをLONG型にしてデータを操作する方法が思い浮かばないのでもう少し考えてみたいと思います。質問させて頂いた件に関しましては解決なのですが後1日だけお時間下さい(^^;


GOD  2004-10-01 01:48:41  No: 116542

>>long配列を使うことでループを1/4に減らせ
>なんですが4バイトのデータをLONG型にしてデータを操作する方法が思い浮かばな
>いのでもう少し考えてみたいと思います。質問させて頂いた件に関しましては解
>決なのですが後1日だけお時間下さい(^^;
>
じゃヒントとして、
'---------------------
Dim bytKey(3) As Byte
bytKey(0) = &H1
bytKey(1) = &H2
bytKey(2) = &H3
bytKey(3) = &H4
'---------------------
Dim lngKey As Long
lngKey = &H04030201

bytKey, lngKey それぞれ別のファイルに書き出してみて比較してください。


ぴろあき  2004-10-01 02:25:04  No: 116543

もうご存知とは思いますが、ネイティブコードコンパイルで、
コードの実行速度を最適化にしたりとか、最適化の詳細設定をすると速くなりますよ。

あとは、ヘルプの「実行速度の最適化」あたりはご覧になってますか?
もうやっておられるようですが、Long型を使用する事とか勉強になります。

>Do Until lngLoop = lngEOF + 1
葉月さんのアドバイスが全く生かされていない。。。
lngEOF = lngEOF + 1
Do Until lngLoop = lngEOF
にすべきでしょう。

それからループ回数があらかじめわかっている場合、
Do〜LoopよりもFor〜Nextの方が速いですよ。


K.J.K.  2004-10-01 19:33:33  No: 116544

API関数を利用してFileMappingを使って、取得したポインタを
(Long型の)配列にキャスト(Not コピー)したサンプル。
http://www.koalanet.ne.jp/~akiya/vbtaste/vbp/FileMap.lzh
# 今、でっち上げたので、間違っている部分があるかも。


LUCIA  2004-10-02 11:58:10  No: 116545

GODさん、ぴろあきさん、K.J.K.さん返信ありがとうございます!
>'---------------------
>Dim bytKey(3) As Byte
>bytKey(0) = &H1
>bytKey(1) = &H2
>bytKey(2) = &H3
>bytKey(3) = &H4
>'---------------------
>Dim lngKey As Long
>lngKey = &H04030201
お手本を拝見するとなるほどとなるのですが自分では思いつかなかったです…頭固いですね(^^;
どうにかCopyMemoryでBYTE←→LONGの操作が出来るようになりました。貴重なアドバイスありがとうございました!
>あとは、ヘルプの「実行速度の最適化」あたりはご覧になってますか?
>もうやっておられるようですが、Long型を使用する事とか勉強になります。
今まであまり見てなかったのですが今回高速化するにあたりまして何度も読み返しました!残念ながらすぐ生かせるだけの経験と知識が不足しておりますが…(^^;
>>Do Until lngLoop = lngEOF + 1
>葉月さんのアドバイスが全く生かされていない。
>それからループ回数があらかじめわかっている場合、
>Do〜LoopよりもFor〜Nextの方が速いですよ。
お恥ずかしい限りです。コードって性格が現れるなぁと思いつつ貴重なご指導に感謝し頑張りたいと思います!
>API関数を利用してFileMappingを使って、取得したポインタを
>(Long型の)配列にキャスト(Not コピー)したサンプル。
サンプル頂きました!そして…実行して驚きましたです!更にコードを拝見して言葉を失いました(^^;
まだ半分ほどしか理解できておりませんがコッソリ勉強して上司を驚かしてやりたいと思います(笑)
貴重なお手本ありがとうございました!

皆様のおかげで解決することが出来ました、本当にありがとうございました!
もっと食い下がっていたいのですが暫くは頂いたお手本やアドバイスを教材にさせて頂いて勉強したいと思います!
ありがとうございました!


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加