ab.com コミュニティ

ActiveBasicを通したコミュニケーション
現在時刻 - 2024年3月29日(金) 05:09

全ての表示時間は UTC+09:00 です




新しいトピックを投稿する  トピックへ返信する  [ 5 件の記事 ] 
作成者 メッセージ
投稿記事Posted: 2009年8月04日(火) 16:30 
関数ポインタに関する質問です。

処理速度向上を図るために、ループを使用せず以下のようなコードを書きました。
(ループを使用すれば、数行で済むのに非常に長いオバカなコードなのです)
コード:
Select Case PtnNo  '変数PtnNoには0~63の数値が代入される
	Case  0:Ptn00()
	Case  1:Ptn01()
	Case  2:Ptn02()
	Case  3:Ptn03()
	Case  4:Ptn04()
	Case  5:Ptn05()
	Case  6:Ptn06()
	Case  7:Ptn07()
	Case  8:Ptn08()
	Case  9:Ptn09()
     ・
     ・
     ・
     ・
     ・
	Case 62:Ptn62()
	Case 63:Ptn63()
End Select

Sub Ptn00()
	処理00
Endsub

Sub Ptn01()
	処理01
Endsub
     ・
     ・
     ・
Sub Ptn63()
	処理63
Endsub
PtnNoには0~63の数値が計算結果として代入され、数値によって関数を64個用意し
Select Caseにて処理をさせています。

もしかすると「関数ポインタ」というモノ(←よくわかっていません)
を利用すると、もっと速度向上になるのではないだろうか?・・・と思い投稿いたしました。

具体的な関数ポインタの使用方法をご教授いただけないでしょうか?

「関数ポインタ」なるものを勘違いしているかもしれませんが、よろしくお願いいたします。


通報する
ページトップ
   
投稿記事Posted: 2009年8月05日(水) 03:19 
オフライン

登録日時: 2005年5月31日(火) 07:49
記事: 162
はじめに関数ポインタについてお話しします。

概念的には、関数ポインタとは、言うなればある関数の分身のことです。
変数を指すポインタがその変数の分身であることと同様です。
関数ポインタの実体は、変数を指すポインタと同様、メモリ番地(つまりは単なる数値)です。

変数の目的は、何らかの値を保存しておき、必要なときに読み書きすることですから、その分身たるポインタでもその変数を読み書きできます。
また、分身ですから(関数から抜け出すなどして)その変数が無効になった場合、その変数を指すポインタも同時に無効になります。

一方で関数の目的は、呼び出されることです。
ですから、その分身たる関数ポインタでもその関数を呼び出せます。
また、(通常は考えにくいことですが)何らかの理由でその関数が無効になった場合も、やはりそのポインタも同時に無効になります。

ActiveBasic での関数ポインタの使い方は、ここでは省略しますね。
ActiveBasic に関数ポインタが導入されたのがどうやら 2005 年頃のようで、フォーラムの過去ログにも関数ポインタに関する話が埋もれていると思うので。


次に Select Case についてお話しします。

ActiveBasic の Select Case 構文でどのようなコードが生成されるのかは把握していませんが、一般的な Select Case の実装法としては、次のようなコードの等価変形が考えられます。
コード:
If PtnNo = 0 Then
    Ptn00()
Else If PtnNo = 1 Then
    Ptn01()
Else If PtnNo = 2 Then
    Ptn02()

(中略)

Else If PtnNo = 62 Then
    Ptn62()
Else If PtnNo = 63 Then
    Ptn63()
End If
このコードだと PtnNo の値が小さければわりと早く目的の関数を呼び出せますが、PtnNo の値が大きめになってくると、いつまでも Else If 〜 Else If 〜 の評価を繰り返し続けるので、少々時間がかかります。
具体的には、最悪の場合で PtnNo = 63 のとき、比較処理も 63 回行っていずれの比較も一致せず、最後のブロックの PtnNo = 63 の評価でようやく処理が決定する、ということになります。
(厳密なことをいえば、本当の「最悪の場合」は PtnNo = 63 ではなく、PtnNo の値が想定される値の範囲外だった場合なのですが。)

この変形はあくまで一般的な解法で、トモカズさんのコードの場合、既にお察しの通り Select Case のもっと効率的な等価変形ができる特殊ケースに該当します。
それは次のようなコードになります。
コード:
' pPtn は関数ポインタ型の配列
If 0 <= PtnNo And PtnNo <= 63 Then ' PtnNo の値の範囲チェック:重要!
    pPtn[PtnNo]()
End If
PtnNo の値が 0 〜 63 の範囲で飛び番がなければ、これで OK です。
ただし、pPtn の各要素にはあらかじめ適切なポインタが格納されていることが前提になります。
なお、PtnNo の値が「絶対に」0 〜 63 の範囲内に収まっているという確信があれば If 文は消してしまってもいいでしょう。
(それでもデバッグ用に残しておいたほうが賢明というか、その代わりに assert を入れておくべきでしょうが、ActiveBasic にアサーションってありましたっけ?)

一応 pPtn 配列の初期化コードも、そのまま流用できる形で載せておきます。
コード:
Dim pPtn As *Sub()
pPtn[0] = AddressOf(Ptn00)
pPtn[1] = AddressOf(Ptn01)

(中略)

pPtn[63] = AddressOf(Ptn63)
注意点があります。
配列の初期化には、前出の If 〜 Else If 〜 Else If 〜 と同等程度の手間がかかるということを忘れないでください。
ですから、たとえばループの中に初期化処理を書いて、初期化も一緒に繰り返すことの無いように。
pPtn をグローバル変数にして、プログラムの起動時に一度だけ初期化するのがお勧めです。


この手法は、ジャンプテーブルなどと呼ばれる古典的な Select Case の最適化技法です。
配列を移動先(ジャンプ先)が記してある表(テーブル)に見立ててこう呼びます。


#長ったらしい文章でごめんなさいね。
#僕の書く文章ってどうしてこう、いつももっさりしてしまうのだろう。。。


通報する
ページトップ
 記事の件名:
投稿記事Posted: 2009年8月05日(水) 15:18 
takさん ご丁寧な解説でわかり易かったです。

そもそも「ポインタ」という概念を知ったのが最近の事でして (もちろん使いこなせてはいません・・・使えていたら質問しませんね)
「なんとなくこのケースでは、使えるような・・・気がするかも」と言った感じでした。
少し理解度が高まった気がしています。ありがとうございました。

さて、早速試してみましたが、「pPtn[*] = AddressOf(Ptn**)」の箇所でコンパイル時に「文法が間違っています」となってしまいました。

分からないなりにも(適当に?)別の配列変数を準備してやってみました。(以下のコードです)
一応動作しているようですが、こんなので大丈夫なのでしょうか?(正攻法では無い気がしています)
なにか、事前に他にも定義するもあるのでしょうか?

コード:
Dim pPtn As *Sub()
Dim Ptn(63) As QWord

	Ptn[0] = AddressOf(Ptn00) 
	Ptn[1] = AddressOf(Ptn01) 
	Ptn[2] = AddressOf(Ptn02) 
		(略)
	Ptn[63] = AddressOf(Ptn63)

' pPtn は関数ポインタ型の配列

If 0 <= PtnNo And PtnNo <= 63 Then ' PtnNo の値の範囲チェック:重要!
    pPtn=Ptn(PtnNo) : pPtn()
End If



通報する
ページトップ
   
 記事の件名:
投稿記事Posted: 2009年8月05日(水) 17:00 
オフライン

登録日時: 2005年5月31日(火) 07:49
記事: 162
ごめんなさい。大チョンボやらかしてました。
配列の宣言部分ですが、配列ですので、添え字が必要です。
コード:
Dim pPtn[63] As *Sub()
角括弧 [] は丸括弧 () でも同じです(たぶん)。
ただ丸括弧だと、見た目関数呼び出しと紛らわしいので、僕は角括弧を使っています。
まあ、これは好みの問題ですね。

QWord とは、うーん、なるほど。
いま手元にコンパイルできる環境がないので未検証ですが、もしかしたら pPtn[PtnNo]() の表現はコンパイラが理解してくれないかもしれません。
(コンパイラはそんなにバカじゃないと僕は信じていますが、大丈夫だよね?)
そのときは、トモカズさんのコードの QWord を *Sub() に置き換えて、Ptn でワンクッション挟むようにするといいでしょう。


通報する
ページトップ
 記事の件名:
投稿記事Posted: 2009年8月10日(月) 11:03 
>配列の宣言部分ですが、配列ですので、添え字が必要です。

そうなのですね。普通に考えれば配列だとすぐに気が付きそうなものですが、
「As *Sub()」って、こういうモノなのだと決め付けてしまっていました。
これに気が付かずに悩んでいた自分が恥ずかしいです。

>いま手元にコンパイルできる環境がないので未検証ですが、もしかしたら pPtn[PtnNo]() の表現はコンパイラが理解してくれないかもしれません。

残念ながらコンパイルエラーになってしまいました。

上のコードで検証をしてみたところ、(0~63の値によって処理速度は変化しますが)約7%程の速度向上が見られました。
takさんの推察通り「数値が小さい程効果は少なく、大きい程効果大」でした。

他にも応用が出来そうです。ありがとうございました。

#この処理を検証していたときに疑問に思ったのですが
#Sub と Gosub でもスピードチェックをしてみました。
#・・・ここに書こうかと思いましたが、別の内容なので別スレッドにします。


通報する
ページトップ
   
期間内表示:  ソート  
新しいトピックを投稿する  トピックへ返信する  [ 5 件の記事 ] 

全ての表示時間は UTC+09:00 です


オンラインデータ

このフォーラムを閲覧中のユーザー: なし & ゲスト[21人]


トピック投稿:  可
返信投稿:  可
記事編集: 不可
記事削除: 不可
ファイル添付: 不可

検索:
ページ移動:  
cron
Powered by phpBB® Forum Software © phpBB Limited
Japanese translation principally by ocean