Android / ARMターゲットのDelphi XExコード生成に影響を与える方法は?


266

2017-05-17を更新。私はこの質問の元となった会社で働いていないため、Delphi XExにアクセスできません。私がそこにいる間、問題はFPC + GCC(Pascal + C)の混合に移行することで解決されました。NEON組み込み関数は、それが違いを生むいくつかのルーチンに使用します。(FPC + GCCは、標準ツール、特にValgrindの使用を可能にするため、強く推奨されます。)信頼できる例を使用して、Delphi XExから最適化されたARMコードを実際に生成できる方法を誰かが示すことができる場合、私は答えを受け入れます。


EmbarcaderoのDelphiコンパイラはLLVMバックエンドを使用して、Androidデバイス用のネイティブARMコードを生成します。Androidアプリケーションにコンパイルする必要があるPascalコードが大量にあり、Delphiでより効率的なコードを生成する方法を知りたいです。現在、私は自動SIMD最適化のような高度な機能についてさえ話していません。合理的なコードを生成することについてだけです。確かに、パラメーターをLLVM側に渡す方法、または何らかの方法で結果に影響を与える方法が必要ですか?通常、どのコンパイラにもコードのコンパイルと最適化に影響を与える多くのオプションがありますが、DelphiのARMターゲットは単に「最適化のオン/オフ」であり、それだけです。

LLVMは適度にタイトで実用的なコードを生成できるはずですが、Delphiはその機能を奇妙な方法で使用しているようです。Delphiはスタックを非常に多用したいと考えており、通常、プロセッサのレジスタr0〜r3を一時変数としてのみ使用します。おそらく最もクレイジーなのは、通常の32ビット整数を4つの1バイトのロード操作としてロードすることです。Delphiがより優れたARMコードを生成するようにするにはどうすればよいですか?

最初は、バイトごとの読み込みはビッグエンディアンからバイト順を交換するためのものだと思っていましたが、そうではありません。実際には、32ビットの数値を4つのシングルバイトの読み込みで読み込むだけです。*アライメントされていないワードサイズのメモリロードを行わずに、32ビット全体。(それを避けるべきかどうかは別のことであり、コンパイラのバグであることを示唆しています)*

この簡単な関数を見てみましょう:

function ReadInteger(APInteger : PInteger) : Integer;
begin
  Result := APInteger^;
end;

最適化がオンになっていても、アップデートパック1を適用したDelphi XE7とXE6は、その関数に対して次のARMアセンブリコードを生成します。

Disassembly of section .text._ZN16Uarmcodetestform11ReadIntegerEPi:

00000000 <_ZN16Uarmcodetestform11ReadIntegerEPi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   78c1        ldrb    r1, [r0, #3]
   a:   7882        ldrb    r2, [r0, #2]
   c:   ea42 2101   orr.w   r1, r2, r1, lsl #8
  10:   7842        ldrb    r2, [r0, #1]
  12:   7803        ldrb    r3, [r0, #0]
  14:   ea43 2202   orr.w   r2, r3, r2, lsl #8
  18:   ea42 4101   orr.w   r1, r2, r1, lsl #16
  1c:   9101        str r1, [sp, #4]
  1e:   9000        str r0, [sp, #0]
  20:   4608        mov r0, r1
  22:   b003        add sp, #12
  24:   bd80        pop {r7, pc}

Delphiがそのために必要な命令とメモリアクセスの数を数えるだけです。そして、4つのシングルバイトロードから32ビット整数を構築します...関数を少し変更し、ポインターの代わりにvarパラメーターを使用すると、少し複雑になります。

Disassembly of section .text._ZN16Uarmcodetestform14ReadIntegerVarERi:

00000000 <_ZN16Uarmcodetestform14ReadIntegerVarERi>:
   0:   b580        push    {r7, lr}
   2:   466f        mov r7, sp
   4:   b083        sub sp, #12
   6:   9002        str r0, [sp, #8]
   8:   6801        ldr r1, [r0, #0]
   a:   9101        str r1, [sp, #4]
   c:   9000        str r0, [sp, #0]
   e:   4608        mov r0, r1
  10:   b003        add sp, #12
  12:   bd80        pop {r7, pc}

ここでは逆アセンブリを含めませんが、iOSの場合、Delphiはポインターとvarパラメーターのバージョンに対して同一のコードを生成します。これらは、Androidのvarパラメーターのバージョンとほとんど同じですが、まったく同じではありません。 編集:明確にするために、バイトごとの読み込みはAndroidでのみ行われます。また、Androidのみで、ポインターとvarパラメーターのバージョンが互いに異なります。iOSでは、両方のバージョンでまったく同じコードが生成されます。

比較のために、FPC 2.7.1(2014年3月のSVNトランクバージョン)が最適化レベル-O2の機能についてどのように考えているかを示します。ポインターとvarパラメーターのバージョンはまったく同じです。

Disassembly of section .text.n_p$armcodetest_$$_readinteger$pinteger$$longint:

00000000 <P$ARMCODETEST_$$_READINTEGER$PINTEGER$$LONGINT>:

   0:   6800        ldr r0, [r0, #0]
   2:   46f7        mov pc, lr

また、Android NDKに付属するCコンパイラを使用して、同等のC関数をテストしました。

int ReadInteger(int *APInteger)
{
    return *APInteger;
}

そして、これはFPCが作成したものと本質的に同じものにコンパイルされます:

Disassembly of section .text._Z11ReadIntegerPi:

00000000 <_Z11ReadIntegerPi>:
   0:   6800        ldr r0, [r0, #0]
   2:   4770        bx  lr

14
ところで、これに関するGoogle+のディスカッションで、Sam Shawは、C ++がデバッグビルドでは長い形式のコードを示し、リリースでは最適化されたコードを示していると述べています。Delphiが両方で行う場所。そのため、LLVMが送信するフラグの単純なバグである可能性が高く、バグレポートが提出に値する場合、すぐに修正される可能性があります。
David

9
ああ、わかりました、読み違いました。次に、Notlikethatが言ったように、ポインターのロードが整列していない(または整列を保証できない)と想定しているように思われ、古いARMプラットフォームは必ずしも整列していないロードを行うことができません。ARMv6以降(ARMv5 を想定しているため)にアラインされていないロードをサポートする必要があるため、(このコンパイラーにそのようなオプションがあるかどうかはわかりません)armeabi-v7aではなく、armeabi必ずターゲットをビルドしてくださいarmeabi。(示されている逆アセンブリは、ビッグエンディアンの値を読み取るようには見えません。リトルエンディアンの値を一度に1バイトずつ読み取るだけです。)
mstorsjo

6
これと同じバグのように見えるRSP-9922を見つけました。
David

6
誰かがXE4とXE5の間で最適化が壊れていることについて、embarcadero.public.delphi.platformspecific.iosニュースグループで「ARMコンパイラの最適化が壊れていますか?」と質問していました。devsuperpage.com/search/...
サイドS.フレッシュ

6
@ヨハン:それはどんな実行可能ファイルですか?Delphiのコンパイラ実行可能ファイル内で何らかの形で焼き付けられているような印象を受けました。試してみて、結果をお知らせください。
Side S. Fresh

回答:


8

問題を調査中です。つまり、ポインタによって参照される整数の(32境界への)潜在的な不整合に依存します。すべての答えを得るにはもう少し時間が必要です...そしてこれに対処するための計画。

Delphi開発者のモデレーター、MarcoCantù

また、Delphi zlibおよびzipライブラリが64ビットで非常に遅いのはなぜですか。Win64ライブラリは最適化なしでビルドされて出荷されるため。


QPレポート:RSP-9922コンパイラによって生成された不正なARMコード、$ Oディレクティブは無視されましたか?、マルコは次の説明を追加しました:

ここには複数の問題があります:

  • 前述のとおり、最適化設定はユニットファイル全体にのみ適用され、個々の関数には適用されません。簡単に言えば、同じファイルで最適化をオンまたはオフにしても効果はありません。
  • さらに、単に「デバッグ情報」を有効にすると、最適化がオフになります。したがって、デバッグ中に最適化を明示的にオンにしても効果はありません。したがって、IDEのCPUビューは、最適化されたコードの逆アセンブルビューを表示できません。
  • 第3に、非境界整列の64ビットデータのロードは安全ではなく、エラーが発生するため、特定のシナリオで必要な個別の4つの1バイト操作です。

MarcoCantùは、2015年1月に「問題を調査中」というメモを投稿し、関連するバグレポートRSP-9922は、解決策「期待どおりに機能する」で2016年1月に解決済みとマークされ、「3月2日に終了した内部の問題」という言及があります。 2015」。彼らの説明がわかりません。
サイドS.フレッシュ

1
問題解決にコメントを追加しました。
MarcoCantù2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.