Android / ARM 대상의 Delphi XEx 코드 생성에 영향을주는 방법은 무엇입니까?


266

2017-05-17 업데이트 이 질문이 시작된 회사에서는 더 이상 일하지 않으며 Delphi XEx에 액세스 할 수 없습니다. 내가 거기에 있었을 때 문제를 해결하기 위해 NEON 내장 함수를 사용하여 혼합 FPC + GCC (Pascal + C)로 마이그레이션하여 문제를 해결했습니다. (FPC + GCC는 표준 도구, 특히 Valgrind를 사용할 수 있기 때문에 강력하게 권장됩니다.) 누군가가 믿을만한 예제를 통해 실제로 Delphi XEx에서 최적화 된 ARM 코드를 생성 할 수있는 방법을 시연 할 수 있다면 그 대답을 기꺼이 받아들입니다 .


Embarcadero의 Delphi 컴파일러는 LLVM 백엔드를 사용하여 Android 디바이스 용 기본 ARM 코드를 생성합니다. 안드로이드 응용 프로그램으로 컴파일 해야하는 많은 양의 파스칼 코드가 있으며 델파이가보다 효율적인 코드를 생성하는 방법을 알고 싶습니다. 지금은 자동 SIMD 최적화와 같은 고급 기능에 대해 이야기하지 않고 합리적인 코드를 생성하는 것에 대해서만 이야기하고 있습니다. 분명히 LLVM 측에 매개 변수를 전달하는 방법이 있어야합니까, 아니면 어떻게 든 결과에 영향을 줍니까? 일반적으로 모든 컴파일러에는 코드 컴파일 및 최적화에 영향을주는 많은 옵션이 있지만 Delphi의 ARM 대상은 "최적화 켜기 / 끄기"인 것 같습니다.

LLVM은 상당히 타이트하고 현명한 코드를 생성 할 수 있어야하지만 델파이는 그 기능을 이상한 방식으로 사용하고있는 것 같습니다. 델파이는 스택을 매우 많이 사용하기를 원하며 일반적으로 프로세서의 레지스터 r0-r3 만 임시 변수로 사용합니다. 아마도 가장 열악한 것은 아마도 일반적인 32 비트 정수를 4 개의 1 바이트로드 작업으로로드하는 것 같습니다. 델파이가 더 나은 ARM 코드를 생성하고 바이트 단위 번거 로움없이 안드로이드를 만드는 방법?

처음에는 바이트 단위 로딩이 빅 엔디안에서 바이트 순서를 교체하는 것이라고 생각했지만 실제로는 그렇지 않았습니다. 실제로 4 개의 단일 바이트로드로 32 비트 숫자를로드하는 것입니다. * 정렬되지 않은 워드 크기 메모리로드를 수행하지 않고 전체 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}

델파이가 필요로하는 명령 및 메모리 액세스 수를 세어보십시오. 그리고 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에서만 가능합니다. 그리고 안드로이드에서만 포인터와 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+ 토론의 Btw에서 Sam Shaw는 C ++이 디버그 빌드에서 긴 형식의 코드와 릴리스에서 최적화 된 코드를 보여줍니다. 델파이는 어디에서나 그것을 수행합니다. 따라서 LLVM을 전송하는 플래그에 간단한 버그 일 수 있으며 버그 보고서를 제출할 가치가 있으면 곧 수정 될 수 있습니다.
David

9
오, 알았어요 그런 다음과는 달리 포인터로드가 정렬되지 않거나 정렬을 보장 할 수 없다고 가정하고 구형 ARM 플랫폼에서 정렬되지 않은로드를 수행 할 수있는 것은 아닙니다. ARMv6 ( ARMv5 를 가정 하는 동안) 이후 정렬되지 않은로드를 지원해야하므로 armeabi-v7a대신이 빌드에 타겟팅을 작성해야합니다 armeabi(이 컴파일러에 이러한 옵션이 있는지 확실하지 않음 armeabi). (표시된 디스 어셈블리는 bigendian 값을 읽는 것처럼 보이지 않으며 한 번에 1 바이트
씩만

6
RSP-9922 가 동일한 버그 인 것으로 나타 났습니다 .
David

6
누군가가 embarcadero.public.delphi.platformspecific.ios 뉴스 그룹 "XR4와 XE5 사이에서 최적화가 깨지는 것에 대해 질문했다."ARM 컴파일러 최적화가 실패 했습니까? " devsuperpage.com/search/…
Side S. Fresh

6
@Johan : 어떤 실행 파일입니까? 델파이의 컴파일러 실행 파일에서 어떻게 든 구워 졌다는 인상을 받았습니다. 시도해보고 결과를 알려주십시오.
Side S. Fresh

답변:


8

문제를 조사 중입니다. 간단히 말해서 포인터가 참조하는 정수의 잠재적 정렬 오류 (32 경계까지)에 따라 다릅니다. 모든 답을 얻으려면 조금 더 시간이 필요합니다. 그리고 이것을 해결할 계획입니다.

Marco Cantù, Delphi 개발자의 중재자

참고 자료 왜 Delphi zlib 및 zip 라이브러리가 64 비트에서 그렇게 느려 집니까? Win64 라이브러리는 최적화없이 빌드되어 제공됩니다.


QP 보고서 : RSP-9922 컴파일러에서 생성 된 잘못된 ARM 코드, $ O 지시문은 무시 되었습니까? , Marco는 다음 설명을 추가했습니다.

여기에는 여러 가지 문제가 있습니다.

  • 표시된대로 최적화 설정은 전체 기능 파일에만 적용되며 개별 기능에는 적용되지 않습니다. 간단히 말해, 동일한 파일에서 최적화를 켜고 끄는 것은 효과가 없습니다.
  • 또한 "디버그 정보"를 활성화하면 최적화 기능이 꺼집니다. 따라서 디버깅 할 때 명시 적으로 최적화를 설정해도 효과가 없습니다. 결과적으로 IDE의 CPU보기는 최적화 된 코드의 디스 어셈블 된보기를 표시 할 수 없습니다.
  • 셋째, 정렬되지 않은 64 비트 데이터를로드하는 것은 안전하지 않으며 오류가 발생하므로 지정된 시나리오에서 필요한 별도의 1 바이트 작업이 필요합니다.

Marco Cantù는 2015 년 1 월에 "문제를 조사하고 있습니다"라는 메모를 게시했으며 관련 버그 보고서 RSP-9922는 2016 년 1 월에 "예상대로 작동"으로 해결되었으며 "3 월 2 일에 내부 문제가 종결되었습니다. 2015 ". 나는 그들의 설명을 이해하지 못한다.
Side S. Fresh

1
문제 해결에 의견을 추가했습니다.
Marco Cantù
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.