インラインアセンブラ

先日のわんくま勉強会アセンブラのセッションがありましたが、たいへん興味深く見させてもらいました。
アセンブラは触り程度にしか判らないのですが、久々にアセンブラの話しを聞いて思い出したのが、 Delphi-ML の以下のスレッド。わたしのレスもしっかり残ってますね(汗


【Delphi-ML:49205】文字列の置換速度について


10年ほど前のスレッドですが、このスレッドで始めてインラインアセンブラコードを見た時の驚きは忘れられません。
今も ML にいるか判りませんが、このコードを書いた pcode 氏には尊敬の念を抱いたものです。以下、pcode 氏が書いたコードの改造版です。

procedure CharReplace( var S: string; const Old, New: Char );
var
  ConvTbl : array[char] of char;
var
  ch : char;
  P : PChar;
begin
  for ch := low(char) to High(char) do ConvTbl[ch] := ch;
  ConvTbl[Old] := New;
  UniqueString(S);
  P := PChar(S);
  asm
    push eax
    push ebx
    push ecx
    push edx
    push esi
    push edi

    MOV ESI, P
    MOV ECX, [ESI - 4]
    LEA EDI, ConvTbl

    MOV EBX, ECX
    SHR ECX, 1
    AND EBX, 1
    JZ @@Loop

    MOVZX EAX, byte ptr [ESI]
    MOV BL, byte ptr [EDI + EAX]
    MOV byte ptr [ESI], BL
    INC ESI

@@loop:
    MOVZX EAX, byte ptr [ESI]
    MOVZX EBX, byte ptr [ESI+1]
    MOV DL, byte ptr [EDI + EAX]
    MOV DH, byte ptr [EDI + EBX]

    MOV byte ptr[ESI], DL
    MOV byte ptr[ESI+1], DH

    DEC ECX
    LEA ESI, [ESI + 2]
    JNZ @@loop

    pop edi
    pop esi
    pop ecx
    pop edx
    pop ebx
    pop eax
  end;
end;


pcode 氏は極めて辛口なレスが多いので嫌ってる人かなりいましたが、私は嫌いじゃなかったです。(でも苦手;)

思えば Delphi って、オブジェクト指向に対応し VB を遥かに凌駕するコンポーネントを用意している上に、ポインターも使えて Win32API も宣言なしで呼び出せ、さらにインラインアセンブラも書けてしまい、その上ネイティブコンパイラが異常に速く、一部の処理では C++ より速いという、当時としては鬼のような開発環境でした。

その頃、私はごんたさんという名古屋のエンジニアが作った DelphiARX というライブラリを使って AutoCAD のカスタマイズを行ってた訳ですが、ともすれば処理が重くなりがちな AutoCAD で如何にパフォーマンスを上げるか、日夜悩んでいただけに、インラインアセンブラを始めて見たときの感動はいまだに忘れることができません。

少し C++ で遊んでみました。

 で、面白そうなので、セッション資料を基に C++ で少し遊んでみました。

#include <iostream>
using namespace std;

int main() {

	int a = 10;
	int b = 20;
	_asm
	{
		mov eax, a;
		add eax, b;
		mov b, eax;
	}
	printf( "%d\n", b );

	return 0;
}

結果

30


この辺は簡単ですね、EAX レジスタと ADD 命令を使った単純な加算処理です。お次は SUB 命令による減算処理です。

#include <iostream>
using namespace std;

int main() {

	int a = 10;
	int b = 3;
	_asm
	{
		mov eax, a;
		sub eax, b;
		mov a, eax;
	}
	printf( "%d\n", a );

	return 0;
}

結果

7


次は SHR 命令によるビットシフト演算です。SHR は右に指定ビット数分ずらします。下のサンプルは、右に1ビットずらしてます。

#include <iostream>
using namespace std;

int main() {

	int a = 10;
	_asm
	{
		mov eax, a;
		shr eax, 1;
		mov a, eax;
	}
	printf( "%d\n", a );

	return 0;
}

結果

5


続いて配列の演算。これもセッションでやってました。

#include <iostream>
using namespace std;

int main() {

	unsigned char src[] = { 100, 101, 102, 103, 104, 105, 106, 107 };
	unsigned char dst[] = { 15, 16, 17, 18, 19, 20, 21, 22 };
	__asm
	{
		movq  mm0, dst;
		paddb mm0, src;
		movq  dst, mm0;
		emms;
	}
	for (int i = 0; i < 8; i++ ){
		printf( "%d\n", dst[i] );
	}

	return 0;
}

結果

115
117
119
121
123
125
127
129

これはほぼセッション資料どおりですが、この辺りになるとさすがに何やってるか判らなくなってくる(汗

C++/CLI で試してみました。

お次は C++/CLIインラインアセンブラが使えるか試してみました。結果はマネージ関数内だとインラインアセンブラは使えない。別にネイティブ関数を用意してその中で使う形になるようです。

以下はダメ。ビルドできない。

#include "stdafx.h"
using namespace System;

// マネージ関数
int main(array<System::String ^> ^args)
{
	int a = 10;
	int b = 20;
	_asm
	{
		mov eax, a;
		add eax, b;
		mov b, eax;
	}
	Console::WriteLine(b.ToString());

	return 0;
}

これなら OK

#include "stdafx.h"
using namespace System;

// ネイティブ関数
int sum(int a, int b)
{
	_asm
	{
		mov eax, a;
		add eax, b;
		mov b, eax;
	}
	return b;
}

// マネージ関数
int main(array<System::String ^> ^args)
{
	int a = sum(10, 20);
	Console::WriteLine(a.ToString());

	return 0;
}

結果

30

 
どうでしょう?さすがにアセンブラをゴリゴリ書いて実装したいとは思いませんが、アセンブラを通して CPU のことをより詳しく学んでみるのもたまにはいいかも知れません。アセンブラのより詳しい記事はこちらをどうぞ。

MASM32によるアセンブラ入門