ページ 1 / 1
バイナリファイルのバイトSWAPの高速化方法
Posted: 2006年1月08日(日) 11:08
by shiro
はじめましてshiroと申します。
バイナリファイルをバイトSWAPするプログラムを作りました。
のぞむ機能は得られましたが、512Mbitデータの変換に、
15分程度かかってしまいます。
(ROMライタでSWAPしている時間を短縮したかった)
べたべたな作りで、恥ずかしいのですが以下がリストです。
ファイルアクセスに時間がかかるのかと思い、
フィールド長を大きくして、並べ替えてからPUTを試してみましたが、
あまり変わりません。
高速化する方法をご教示いただけると幸いです。
よろしくお願いします。
コード: 全て選択
OPEN PATH+"\"+FILE AS 1
OPEN PATH+"\"+FILE+"_SW.BIN" AS 2
FIELD #1,1 'ファイルフィールド長 1BYTE
FIELD #2,1 'ファイルフィールド長 1BYTE
PRINT "START";Time$()
/*データ入力&出力====================================*/
*DATA_GET
GET #1,COUNTER,K$1 'データ GET
GET #1,COUNTER+1,K$2
PUT #2,COUNTER,K$2 'SWAPデータ PUT
PUT #2,COUNTER+1,K$1
/*SUM値演算========================================*/
SUM1 = Asc(K$1)
SUM2 = Asc(K$2)
SUM_OR = SUM_OR + (SUM1*256+SUM2)
SUM_SW = SUM_SW + (SUM2*256+SUM1)
COUNTER = COUNTER+2
IF Eof(1) = -1 THEN *END_TASK 'END OF FILE
GOTO *DATA_GET
/*終了処理 ==========================================*/
*END_TASK
CLOSE 1
CLOSE 2
512Mbitデータの変換について
Posted: 2006年1月08日(日) 23:56
by omasu
お世話になります。
処理の高速化については私も悩みまくっております。(~o~)
しかし、標準入出力の1バイトずつのGetとPutでは、
ファイルの入出力アクセスで単純に考えても時間がかかると思われます。
512Mbitデータが64MByteのバイナリファイルとすれば、
全てのバイナリファイルを一回でメモリに読み込み、
編集(それぞれのデータを相互に足し算?)の後
一回でファイル出力をしたほうが効率的であると考えます。
「ROMライタ」という記述で躊躇したのですが、一考察として投稿をいたします。
がんばってください。
Posted: 2006年1月09日(月) 00:12
by konisi
ファイル名の変更では、処理回数が一回だけ(だったと思う。)の為、あまり関係ないと思います。それより、Asc関数を呼び出さなければ相当速くなると思います。
before:
after:
読みやすさを無視して本当に高速化したいのであれば、関数を全てばらすと可也速くなります。(只、その後の改造が面倒になる。)
他に、実際に速くなるかどうかはわかりませんが、goto命令をDo~Loopに変えれば速くなる可能性もあります。
抜ける時はif文+Exit Do命令
追伸
Posted: 2006年1月09日(月) 00:35
by konisi
先ほどの発言の正確性について次のコードをテストしたときの結果をそれぞれ返します。
文字列の変換時にAscを使った時と配列を使った時のスピード比較
コード: 全て選択
Dim I As Long
Dim A$ As String
Dim A As Byte
Dim StartTime As Long
Dim EndTime As Long
Dim UsedTime As Long
Dim StartTime2 As Long
Dim EndTime2 As Long
Dim UsedTime2 As Long
Declare Function timeGetTime Lib "winmm.dll" () As DWord
A$=" "
StartTime=timeGetTime()
For I=0 To 1000000
A=Asc(A$)
Next I
EndTime=timeGetTime()
StartTime2=timeGetTime()
For I=0 To 1000000
A=A$[0]
Next I
EndTime2=timeGetTime()
UsedTime=EndTime-StartTime
UsedTime2=EndTime2-StartTime2
Open "Test.txt" For Append As #1
A$=Str$(UsedTime)
Write #1,A$
A$=Str$(UsedTime2)
Write #1,A$
Close #1
MessageBox(0,"Test was end","EndDialog",0)
End
五回実行した時の結果の平均値
UsedTime=8581.8
UsedTime2=77.6
実際に出力したファイルの記述内容
4929
37
9706
105
9375
89
9511
102
9388
55
測定する順番を逆にした場合の結果
五回実行した時の結果の平均値
UsedTime=9690
UsedTime2=74
実際に出力したファイルの記述内容
9633
95
9603
40
9639
78
9734
75
9841
82
Do~Loopとgotoのスピード比較
コード: 全て選択
Dim I As Long
Dim A$ As String
Dim StartTime As Long
Dim EndTime As Long
Dim UsedTime As Long
Dim StartTime2 As Long
Dim EndTime2 As Long
Dim UsedTime2 As Long
Declare Function timeGetTime Lib "winmm.dll" () As DWord
I=0
StartTime=timeGetTime()
Do
I=I+1
if I>100000000 then Exit Do
Loop
EndTime=timeGetTime()
I=0
StartTime2=timeGetTime()
*A
I=I+1
if I>100000000 then goto *B
goto *A
*B
EndTime2=timeGetTime()
UsedTime=EndTime-StartTime
UsedTime2=EndTime2-StartTime2
Open "Test3.txt" For Append As #1
A$=Str$(UsedTime)
Write #1,A$
A$=Str$(UsedTime2)
Write #1,A$
Close #1
MessageBox(0,"Test was end","EndDialog",0)
End
五回実行した時の結果の平均値
UsedTime=4757
UsedTime2=4905
実際に出力したファイルの記述内容
4659
4884
4887
4949
4715
4931
4651
4879
4873
4882
結論:Asc関数を使わずに文字列の配列を使う価値は十分あるがgotoとDo~Loopでは殆んど差異が見られなかった。
レスありがとうございます
Posted: 2006年1月09日(月) 04:17
by shiro
omasuさん。konishiさん。
レスありがとうございます。
ヒント、助言、テスト結果ありがとうございます。
いろいろ試してみたいと思います。
プログラム書くのが十数年ぶりなのと、BASIC基本7命令ぐらいしか
よくわかっていないので、時間がかかりそうですが.....
自分は、おとといActiveBASICを発見し
使用してみたのですが、便利ですねぇ!!
PC88、98がない状況でN88BASICが使えるとは!!
なにか結果がでれば、報告いたします。
Posted: 2006年1月09日(月) 08:18
by イグトランス
[ここをクリックすると内容が表示されます] [ここをクリックすると非表示にします]コード: 全て選択
PUT #2,COUNTER,K$2 'SWAPデータ PUT
PUT #2,COUNTER+1,K$1
そもそもこの2行は不要な気がします。
こんな風にファイル名を交換してしまえば良いという気がしてなりません。
そうしたら後はSum値の演算に専念できるというわけです。
コード: 全て選択
Dim FilePath1 As String, FilePath2 As String, FilePathTemp As String
FilePath1 = PATH + "\" + FILE
FilePath2 = File1 + "_SW.BIN"
FilePathTemp = File1 + "_temp.$$$" ' とりあえずの適当な名前
MoveFile(FilePath1, FilePathTemp)
MoveFile(FilePath2, FilePath1)
MoveFile(FilePathTemp, FilePath2)
(このコードを挿入するとしたらCloseの後にしてください。)
MoveFileはファイルの移動とファイル名の変更ができる関数です。
最初の引き数で元の名前,2番目の引き数で新しい名前を指定します。
寝ぼけているので頓珍漢なことを書いていたらすみません。
2006/01/09 11:50
すみません。完璧に寝ぼけていましたね。
バイトスワップを見落としていました。
一考察として投稿Ⅱ
Posted: 2006年1月09日(月) 11:45
by omasu
お世話になります。
私もアクティブベーシックと出会ったときは感激しました。
当初のファイル入出力を一回で行うコードを参考として投稿をいたします。(AB4.13.00)
77Mバイトのテストデータでは5秒くらいで終了します。
演算処理はロジックのどこかで使っているのでしょうか?(不明なため反映はしていません)
[ここをクリックすると内容が表示されます] [ここをクリックすると非表示にします]コード: 全て選択
#N88BASIC
Dim Buffer$ As String
Dim RecordSize As Long
Dim i As Long
Dim Work$ As Byte
'
'* 入力ファイル読み込み
'
Open "TestInfile.txt" For Input As #1
RecordSize=Lof(1)
Field #1,RecordSize
Get #1,1,Buffer$
Close #1
'
'* スワップ処理(1⇔2,3⇔4・・・・)
'
For i=1 To RecordSize-1 Step 2
Work$=Buffer$[i-1]
Buffer$[i-1]=Buffer$
Buffer$=Work$
Next i
'
'* 出力ファイル(bin)書き込み
'
Open "TestOutfile.txt" For Output As #2
Field #2,RecordSize
Put #2,1,Buffer$
Close #2
End
追伸:データが奇数の場合、最後のバイトはどうするのでしょうか?
追伸:データが巨大となる場合はメモリに取り込めない可能性があります。
SWAP作成
Posted: 2006年1月09日(月) 12:11
by shiro
お世話になります。
提示リストがわかりにくかったですね。申し訳ないです。
/* データ入力&出力 ====================================*/
*DATA_GET
GET #1,COUNTER,K$1 'データ GET
GET #1,COUNTER+1,K$2
PUT #2,COUNTER,K$2 'SWAPデータ PUT << 単純に1と2を入れ替えてPUTです。
PUT #2,COUNTER+1,K$1
ファイルアクセスの半減させるよう2バイトアクセスで、以下にしてみましたが、
かえって遅くなりました。
文字列操作が遅いのでしょうか?
メモリに展開してからという概念を勘違いしてますか?
/* データ入力&出力 ====================================*/
*DATA_GET
GET #1,COUNTER,K$1 'データ GET
LOW$ = Mid$(K$1,2,1)
HIGH$ = Mid$(K$1,1,1)
K$2 = LOW$ + HIGH$
PUT #2,COUNTER,K$2 'SWAPデータ PUT
>>omasuさん
データ数は偶数前提で作成してます。
過去ログをみて、勉強中です。
ソートロジック大会は読み物としても、おもしろいですね。
メモリに展開してからという概念について
Posted: 2006年1月09日(月) 12:56
by omasu
お世話になります。
omasuの投稿ロジックで機能ははたせると思われますが、
何か不具合があるのでしょうか?
機能1.入力ファイルの全てのデータをメモリ(バッファ)に取り込む
機能2.メモリ(バッファ)内でバイト単位のスワップを行う。
機能3.スワップ済みのメモリ(バッファ)データをファイルに書き込む
※入出力アクセスは入力ファイル取り込みと出力ファイルの書き込みの一回ですみます。
※この結果処理時間は77Mバイトのファイルで5秒くらいとなります。
追伸:ソートロジック大会への参加も期待しております。(~o~)/
Posted: 2006年1月09日(月) 16:13
by イグトランス
さらにその上を行く手段としてメモリマップドファイルと言うものがあります。
ようするにファイルを巨大なByte配列と見なして処理ができると言うことです。(Byte型に限らずなんでも良いですが)
もはやバッファとファイルとの読み書きなんて要りません。(裏でWindowsがよきに計らっているだけですが)
[ここをクリックすると内容が表示されます] [ここをクリックすると非表示にします]InputFileとOutputFileは適当に書き換えてください。
コード: 全て選択
#strict
#prompt
Const InputFile = "c:\temp\test_input.bin"
Const OutputFile = "c:\temp\test_output.bin"
Typedef BOOL = Long
timeBeginPeriod(1)'精度を1msに設定
Dim Time As DWord
Time = timeGetTime()
If BinarySwap(InputFile, OutputFile) <> FALSE Then
Time = timeGetTime() - Time
Print Time ' 所要時間の表示
End If
timeEndPeriod(1)'timeBeginPeriodで指定した値を渡す
Function BinarySwap(pszInputFile As *Char, pszOutputFile As *Char) As BOOL
BinarySwap = FALSE
' 入力ファイルのファイルマッピングオブジェクトを作成。
Dim Input As egtra_FileMapping(pszInputFile, FALSE, OPEN_EXISTING)
If Input.GetFileHandle() = INVALID_HANDLE_VALUE Then
Print "入力ファイルが開けませんでした。"
Exit Function
End If
' 入力ファイルのサイズを取得。
Dim qwFileSize As QWord, pSize As *ULARGE_INTEGER
pSize = VarPtr(qwFileSize)
pSize->LowPart = GetFileSize(Input.GetFileHandle(), VarPtr(pSize->HighPart))
If pSize->LowPart = &hffffffff And GetLastError() <> 0 Then
Print "ファイルサイズが取得できませんでした。"
Exit Function
End If
#ifndef _WIN64
If pSize->HighPart <> 0 Then
Print "このプログラムは,Win32ではこんなに巨大なファイルに対応していません。"
Exit Function
End If
#endif
' 出力ファイルのファイルマッピングオブジェクトを作成。入力ファイルと同じ大きさに設定。
Dim Output As egtra_FileMapping(pszOutputFile, TRUE, CREATE_ALWAYS, pSize->HighPart, pSize->LowPart)
If Input.GetFileHandle() = INVALID_HANDLE_VALUE Then
Print "出力ファイルが開けませんでした。"
Exit Function
End If
' ここで実際にメモリアドレスへ割り当てている。
Dim pInput As *Byte, pOutput As *Byte
pInput = Input.MapView()
pOutput = Output.MapView()
'
'* スワップ処理(1⇔2,3⇔4・・・・)
'
Dim FileSize As ULONG_PTR
FileSize = qwFileSize As ULONG_PTR
Dim i As ULONG_PTR
For i = 0 To FileSize - 1 Step 2
pOutput = pInput[i + 1]
pOutput[i + 1] = pInput
Next
' マッピングの解除
Input.UnmapView(pInput)
Output.UnmapView(pOutput)
BinarySwap = TRUE
End Function
さらにこれも必要です。 [ここをクリックすると内容が表示されます] [ここをクリックすると非表示にします]コード: 全て選択
Declare Function timeGetTime Lib "winmm" () As DWord
Declare Function timeBeginPeriod Lib "winmm" (uPeriod As DWord) As Long
Declare Function timeEndPeriod Lib "winmm" (uPeriod As DWord) As Long
Declare Function CreateFileMapping Lib "kernel32" Alias "CreateFileMappingA" (
hFile As HANDLE,
pAttributes As *SECURITY_ATTRIBUTES,
flProtect As DWord,
dwMaximumSizeHigh As DWord,
dwMaximumSizeLow As DWord,
pName As *Char
) As HANDLE
Declare Function MapViewOfFile Lib "kernel32" (
hFileMappingObject As HANDLE,
dwDesiredAccess As DWord,
dwFileOffsetHigh As DWord,
dwFileOffsetLow As DWord,
NumberOfBytesToMap As SIZE_T
) As VoidPtr
Declare Function UnmapViewOfFile Lib "kernel32" (pBaseAddress As VoidPtr) As Long
/*
Const PAGE_READONLY = &H2
Const PAGE_READWRITE = &H4
*/
Const FILE_MAP_READ = &H4
Const FILE_MAP_WRITE = &H2
'簡易ファイルマッピングクラス
Class egtra_FileMapping
Public
Sub egtra_FileMapping(pszFileName As *Char, canWrite As Long, CreationDisposition As DWord)(FileSizeHigh As DWord, FileSizeLow As DWord)
Dim CreateFileFlag As DWord, CreateFileMappingFlag As DWord
If canWrite <> FALSE Then
CreateFileFlag = GENERIC_READ Or GENERIC_WRITE
CreateFileMappingFlag = PAGE_READWRITE
DesiredAccess = FILE_MAP_WRITE
Else
CreateFileFlag = GENERIC_READ
CreateFileMappingFlag = PAGE_READONLY
DesiredAccess = FILE_MAP_READ
End If
hFile = CreateFile(pszFileName, CreateFileFlag, 0, ByVal 0, CreationDisposition, 0, 0)
If hFile = INVALID_HANDLE_VALUE Then
Exit Sub
End If
hMap = CreateFileMapping(hFile, 0, CreateFileMappingFlag, FileSizeHigh, FileSizeLow, 0)
End Sub
Function MapView() As VoidPtr
MapView = MapViewOfFile(hMap, DesiredAccess, 0, 0, 0)
End Function
/*
Function MapView(dwFileOffsetHigh As DWord, dwFileOffsetLow As DWord, NumberOfBytesToMap As SIZE_T) As VoidPtr
MapView = MapViewOfFile(hMap, DesiredAccess, dwFileOffsetHigh, dwFileOffsetLow, NumberOfBytesToMap)
End Function
*/
Sub UnmapView(pv As VoidPtr) ' 本当は不要だが
UnmapViewOfFile(pv)
End Sub
Function GetFileHandle() As HANDLE
GetFileHandle = hFile
End Function
Sub ~egtra_FileMapping()
CloseHandle(hMap)
CloseHandle(hFile)
End Sub
Private
hFile As HANDLE
hMap As HANDLE
DesiredAccess As DWord
End Class
これで、omasuさんのものに比べおよそ半分の時間で実行できました。
(omasuさんのコードの前後にtimeGetTimeを挟んで計測しました。)
私のPCではおよそ80MiBytesのファイルでomasuさんのがおよそ3.2秒ほど,私のが1.6秒ほどでした。
激速ですね。
Posted: 2006年1月09日(月) 18:24
by shiro
お世話になります。shiroです。
omasuさん。イグトランスさん。ありがとうございます。
>>omasuさん
実用的なスピードになりました。
途中で変なレスしちゃいました。(失礼しました)
>>イグトランスさん
すごいですね。
内容が理解できていないので、まだ実施できていません。
申し訳ありません。
勉強して、組み込んでいきたいと思います。
初回作成版がROMライタと同程度の速度だったので、泣きが入っていました。
実用的なものができたし、学生時代を思い出して楽しくいじれたし、
とても有意義な3連休でした。
本当にありがとうございました。
いえいえ
Posted: 2006年1月09日(月) 21:13
by omasu
お世話になります。
お役に立てて幸いです。
私のコードはN88BASICそのものの記述で、やっとAB4になりつつある未熟者です。m(_w_)m
イグトランスさんのコードは高度すぎるため私としても、能力不足、理解不能を感じます。
今は全体としてよりも、パーツとして、非常に貴重なコード、
技術的な部分での魅力が豊富に存在していると思います。
いつかは理解したいですね
そこから先が、konisiさんのコードになると思います。
ABをカスタマイズしてしまおうという力量がすばらしいと思います。
先にkonisiさんが出てくるとややこしくなりますが?
申し訳ありません。m(_w_)m
実用ロジック参考にしております。
Posted: 2006年1月09日(月) 23:00
by イグトランス
簡単に補足を。(補足になっていませんが)
あのコードの肝はpInputとpOutputです。
ようするにこの2つに対しての読み書きはメモリではなく,
全て入力・出力のファイルに対して行われます。
そこまでの長い行は全てこのための前準備でしかないです。
つまりバイトスワップのロジックだけ見たければ読み飛ばしてもなんら差し支えないと言うことです。
Posted: 2006年1月09日(月) 23:20
by konisi
>>そこから先が、konisiさんのコードになると思います。
ABをカスタマイズしてしまおうという力量がすばらしいと思います。
先にkonisiさんが出てくるとややこしくなりますが?
申し訳ありません。m(_w_)m
実用ロジック参考にしております。
だから、元々そういう性格(「アイコンを手動で挿入」参照。)なのでついやっちまうんですよ。只、僕にもイグトランスsみたいにさまざまな関数を書き分けるだけの技量がないので能力不足は感じていますが。
そしてややこしくなった事に謝罪。
メモリの制限について
Posted: 2006年1月12日(木) 21:10
by omasu
お世話になります。
自分の投稿の中に
> 追伸:データが巨大となる場合はメモリに取り込めない可能性があります。
自分も過去、メモリの少なさに嘆いていた時期があったことを思い出してしまいました。
こんなコードをそのままにしておけないと思い、書き直して見ました。
[ここをクリックすると内容が表示されます] [ここをクリックすると非表示にします]コード: 全て選択
#N88BASIC
'
'グローバル変数領域定義
'
Dim RecordSize As QWord ' 18,446,744,073,709,551,616バイトまで
Const BunkatuSize=1024*1024*64 ' 分割サイズを偶数で指定(K,M,64Mbyte)
'
Dim LoopCount As QWord
Dim i As QWord,j As QWord,k As QWord,q As String
Dim Buffer$ As String
Dim Work$ As Byte
'
'ファイルオープン
'
Open "TestInfile.txt" For Input As #1
Open "TestOutfile.txt" For Output As #2
'
'分割処理回数演算
'
RecordSize=Lof(1)
Field #1,BunkatuSize
LoopCount=(RecordSize\BunkatuSize)+Sgn(RecordSize Mod BunkatuSize)
Print RecordSize;"バイトのデータを";BunkatuSize;"バイトに分割して";LoopCount;"回で処理します。"
'
'スワップ処理
'
For i=1 To LoopCount
Get #1,i,Buffer$
j=1
While (j<BunkatuSize) And (k<RecordSize-1)
Work$=Buffer$[j-1]
Buffer$[j-1]=Buffer$[j]
Buffer$[j]=Work$
j=j+2
k=k+2
Wend
Field #2,Len(Buffer$)
Put #2,i,Buffer$
Print i;"回目"
Next i
'
'ファイルクローズ
'
Close #1
Close #2
'
'終了
'
Input "終了しました。(Hit Enter Key)";q
End