
■ inc・dec命令 ■
前回はアセンブラ言語を使用して、データをコピーするプログ
ラムを効率の良い方法で作成してみました。
ちょっと復習してみましょう。
LOOP1:
mov al, [esi] ;←データbをレジスタに移動
mov [edi], al ;←レジスタの中身をデータaに移動
add edi, 1 ;←データbのアドレスを1加算
add esi, 1 ;←データaのアドレスを1加算
loop LOOP1 ;←ecxの回数分ループ
というプログラムが全て
rep movsb
の1行に収まってしまう事の説明をしました。
そして、インテルプロセッサではこの「rep」命令等の事を総
称して「リピートプリフィックス命令」と呼び、「movsb」等
の命令の事を総称して「ストリング命令」と呼ぶ事の説明も行
ないました。
また、このように1つの命令で複数の仕事を行ってくれるプロ
セッサの事を「CISC(Complex Instruction Set Computer)複合
命令セットコンピュータ」と呼び、それに対して単純な命令だ
けしか持たないプロセッサの事を「RISC(Reduced Instruction
Set Computer)単純命令セットコンピュータ」と呼ぶ事の説明
も行ないました。
今回は、データをコピーするのではなく、データを加工するプ
ログラムを作ってみたいと思います。
データを加工する、というのは、画像処理や音声処理等の、マ
ルチメディア処理に用いられる事が多いのですが、ここではそ
のような難しい物は作りません。
少し前に作成したプログラムですが、"loop2.c"を流用してし
まいます。
"loop2.c"はデータbをデータaにコピーするプログラムでした。
まずは"loop2.c"をもう1度見てみましょう。
─↓loop2.c──────────────────────
#include <stdio.h>
int main(void);
int sub(char*, char*);
int main()
{
int i;
char a[8]={ 0, 1, 2, 3, 4, 5, 6, 7};
char b[8]={10,11,12,13,14,15,16,17};
for(i=0;i<8;i++)
{
printf("a[%d]:%d b[%d]:%d\n", i, a[i], i, b[i]);
}
sub(&a[0], &b[0]);
for(i=0;i<8;i++)
{
printf("a[%d]:%d b[%d]:%d\n", i, a[i], i, b[i]);
}
return 0;
}
int sub(char* a, char* b)
{
printf("sub routine called\n");
_asm{
mov edi, a
mov esi, b
mov ecx, 8
LOOP1:
mov al, [esi] ;←データbをレジスタに移動
mov [edi], al ;←レジスタの中身をデータaに移動
add edi, 1 ;←データaのアドレスを1加算
add esi, 1 ;←データbのアドレスを1加算
loop LOOP1 ;←ecxの回数分ループ
}
}
─↑ここまで──────────────────────
上のプログラムのサブ関数は、データbをデータaにコピーする
だけのものです。
今回は、データaにデータbを「加算する」プログラムを作って
みましょう。
まず、"loop2.c"をコピー&貼り付けして、新しくできたファ
イルを"inc.c"という名前に変更しておいてください。
今回も変更点はそれほど多くありません。
まず、メイン関数はそのままです。変更しません。
次にサブ関数ですが、以下の様に変更してください。
変更部分にはコメントを付けておきます。
─↓ここから──────────────────────
_asm{
mov edi, a
mov esi, b
mov ecx, 8
LOOP1:
mov al, [edi] ;←データaをレジスタalに移動
mov bl, [esi] ;←データbをレジスタblに移動
add al, bl ;←データa+データbをalレジスタに格納
mov [edi], al ;←alレジスタをデータaに移動
add edi, 1
add esi, 1
loop LOOP1
}
─↑ここまで──────────────────────
今回はデータを移動するのではなくて、加工しなければならな
い為、前回のプログラムとは微妙に動作が変わっています。
まずデータaとデータbをそれぞれレジスタに移動しています。
mov al, [edi] ;←データaをレジスタalに移動
mov bl, [esi] ;←データbをレジスタblに移動
そしてレジスタの値を加算します。
add al, bl ;←データa+データbをalレジスタに格納
最後に答えをデータaに移動します。
mov [edi], al ;←alレジスタをデータaに移動
変更部分はたったこれだけです。
以上で、データbをデータaに加算するプログラムは作成完了で
す。
● ●
● ビルドする ●
● ●
コマンドプロンプト上で以下の様に入力してください。
sc inc.c -j
これでビルドされ、"inc.exe"という実行ファイルが作成され
るはずです。
● ●
● 実行する ●
● ●
コマンドプロンプト上で以下の様に入力してください。
inc.exe
これでサブ関数を呼び出す前のデータaとデータbの中身と、サ
ブ関数を呼び出した後のデータaとデータbの中身がそれぞれ表
示されたはずです。
サブ関数を呼び出した後はデータbがデータaに加算されている
のが分かりますね。
実行結果:
a[0]:0 b[0]:10
a[1]:1 b[1]:11
a[2]:2 b[2]:12
a[3]:3 b[3]:13
a[4]:4 b[4]:14
a[5]:5 b[5]:15
a[6]:6 b[6]:16
a[7]:7 b[7]:17
sub routine called
a[0]:10 b[0]:10
a[1]:12 b[1]:11
a[2]:14 b[2]:12
a[3]:16 b[3]:13
a[4]:18 b[4]:14
a[5]:20 b[5]:15
a[6]:22 b[6]:16
a[7]:24 b[7]:17
では、このプログラムをほんの少しだけ効率の良い物に変更し
てみましょう。
はじめに言っておきますが、計算命令(足し算、引き算、掛け
算、割り算等)の命令に対して、ストリング・リピートプリフ
ィックス命令を使用する事は出来ません。
ですので、前回出てきたように「rep」命令を使用して効率の
良い物に変更しようとしても、それは出来ないのです。
では、どうすれば良いのでしょうか?
ここではアドレスに「1を加算する」という処理に注目しまし
ょう。
mov edi, a
mov esi, b
mov ecx, 8
LOOP1:
mov al, [edi] ;←データaをレジスタalに移動
mov bl, [esi] ;←データbをレジスタblに移動
add al, bl ;←データa+データbをalレジスタに格納
mov [edi], al ;←alレジスタをデータaに移動
●これ→add edi, 1 ;←データbのアドレスを1加算
●これ→add esi, 1 ;←データaのアドレスを1加算
loop LOOP1 ;←ecxの回数分ループ
これは、実はもっと効率よくする事が出来ます。
「1を加算する」という処理を以下の様に書き換えてみてくだ
さい。
inc edi ;←データbのアドレスを1加算
inc esi ;←データaのアドレスを1加算
「add」命令の部分を「inc」(インクリメント)という命令に
書き換えました。
これをビルド&実行してみてください。
結果は先ほどと同じになるはずです。
なんだ、さっきと同じ結果か。
しかもさっきと同じ2行じゃないか。
と思う方も多いかもしれません。
しかし、「1を加算する」という処理を行なわせるには、こち
らの命令を使用した方が速く処理が行なわれるのです。
これはインテルプロセッサの仕様です。
「1を加算する時は」add命令よりinc命令を使用した方が処理
が速いのです。
そして「inc」命令はループ処理の中で使われる事が多いです。
今回作ったプログラムは8回ループするだけですが、これが大
量にループする処理だとすると結構な違いになってきます。
また、「1減算する」命令としては「dec」(デクリメント)
という命令があります。
こちらも「sub edi, 1」等とするよりは「dec edi」とした方
が処理が速くなります。
今回は「inc」命令と「dec」命令を紹介しました。
実はこのプログラム、もっと速くする事が出来ます。
しかも「inc」命令を使用した時のように、「ちょっとだけ」
速くなる、という程度の効果ではありません。
ヒントが少しだけ出ているので既に分かっている方も多いかも
しれませんが、次回に説明します。