Clean C 코드를 작성하는 동안 ARM 미정렬 메모리 액세스의 이점 활용
이전에는 ARM 프로세서가 정렬되지 않은 메모리 액세스(ARMv5 이하)를 제대로 처리할 수 없었습니다.뭐 이런 거.u32 var32 = *(u32*)ptr;만약에 실패한다면 (raise 예외)ptr4-bytes에 제대로 정렬되지 않았습니다
그러나 이러한 CPU는 항상 이러한 상황을 매우 효율적으로 처리했기 때문에 이러한 문을 작성하는 것이 x86/x64에서는 잘 작동할 것입니다.그러나 C 표준에 따르면, 이것은 그것을 쓰는 "적절한" 방법이 아닙니다.u32는 4 바이트 구조와 동일하며 4 바이트에 정렬되어야 합니다.
정통적인 정확성을 유지하고 모든 CPU와의 완전한 호환성을 보장하면서 동일한 결과를 얻을 수 있는 적절한 방법은 다음과 같습니다.
u32 read32(const void* ptr)
{
u32 result;
memcpy(&result, ptr, 4);
return result;
}
이것은 정확하며, CPU가 정렬되지 않은 위치에서 판독할 수 있거나 판독하지 않을 수 있는 적절한 코드를 생성합니다.또한 x86/x64에서는 단일 읽기 작업에 적절하게 최적화되어 있으므로 첫 번째 문과 동일한 성능을 가집니다.휴대성이 좋고 안전하며 빠릅니다.누가 더 물어볼 수 있습니까?
음, 문제는, ARM에서는, 우리가 그렇게 운이 좋지 않다는 것입니다.
글쓰는 중memcpy버전은 정말로 안전하지만, 체계적인 신중한 작동을 초래하는 것으로 보이는데, 이는 ARMv6와 ARMv7(기본적으로 어떤 스마트폰이든)의 경우 매우 느립니다.
읽기 동작에 크게 의존하는 성능 지향 애플리케이션에서 첫 번째 버전과 두 번째 버전의 차이를 측정할 수 있었습니다.gcc -O2설정. 엔 너무 과분한 일입니다이것은 너무 지나쳐서 무시할 수 없습니다.
ARMv6/v7 기능을 사용하는 방법을 찾기 위해 몇 가지 예시 코드에 대한 지침을 찾았습니다.유감스럽게도, 그들은 첫번째 진술문을 선택하는 것 같습니다. (직접적으로)u32 않아야 access)합니다. 이는 올바르지 않아야 합니다.
이것이 전부가 아닙니다. 새로운 GCC 버전은 이제 자동 벡터화를 구현하려고 합니다.x64에서는 SSE/AVX, ARMv7에서는 NEON을 의미합니다.ARMv7은 또한 포인터를 정렬해야 하는 새로운 LDM(Load Multiple) 및 STM(Store Multiple) opcode를 지원합니다.
그게 무슨 의미죠?컴파일러는 이러한 고급 명령어를 자유롭게 사용할 수 있습니다. C 코드에서 특별히 호출되지 않았더라도 말입니다.그런 결정을 내리기 위해서는 안이라는 사실을 이용합니다.u32* pointer4바이트로 정렬됩니다.그렇지 않으면 정의되지 않은 동작, 충돌과 같은 모든 베팅이 중단됩니다.
즉, 정렬되지 않은 메모리 액세스를 지원하는 CPU에서도 직접 사용하는 것은 위험합니다.u32높은 최적화 설정에서 버그 코드를 생성할 수 있으므로 액세스(-O3).
따라서, 이것은 딜레마입니다: 잘못된 버전을 쓰지 않고도 정렬되지 않은 메모리 액세스에서 ARMv6/v7의 기본 성능에 액세스하는 방법입니다.u32근?
추신: 저도 해봤습니다.__packed()명령어들, 그리고 성능적 관점에서, 그것들은 그것들과 완전히 동일하게 작동하는 것처럼 보입니다.memcpy방법.
[편집] : 지금까지 받은 우수한 요소 감사합니다.
@Not that @Not like that finding finding.memcpy버전이 제대로 생성됩니다.ldropcode(정렬되지 않은 부하).그러나 생성된 어셈블리가 불필요하게 호출된다는 사실도 발견했습니다.str(명령) 그럼 이제 전체 되지 않은 , 된 부하가 것입니다.따라서 전체 작업은 이제 정렬되지 않은 부하, 정렬된 저장소, 그리고 최종 정렬된 부하가 됩니다.그것은 필요 이상의 일입니다.
@haneeefmubarak라고 대답합니다. 예, 코드가 제대로 입력되어 있습니다.그리고 아니요.memcpy코드가 직접 수용하도록 강요하기 때문에 가능한 최고의 속도를 제공하는 것과는 매우 거리가 멉니다.u32액세스는 엄청난 성능 향상으로 이어집니다.그래서 더 나은 가능성이 존재해야 합니다.
@artless_noise에 큰 감사를 드립니다.godbolt 서비스에 대한 링크는 가치가 없습니다.C 소스 코드와 어셈블리 표현 사이의 동등성을 이렇게 명확하게 볼 수는 없었습니다.이것은 매우 고무적입니다.
@artless 예제 중 하나를 완성했는데 다음을 제공합니다.
#include <stdlib.h>
#include <memory.h>
typedef unsigned int u32;
u32 reada32(const void* ptr) { return *(const u32*) ptr; }
u32 readu32(const void* ptr)
{
u32 result;
memcpy(&result, ptr, 4);
return result;
}
-O3 또는 -O2에서 ARM GCC 4.8.2를 사용하여 컴파일된 경우:
reada32(void const*):
ldr r0, [r0]
bx lr
readu32(void const*):
ldr r0, [r0] @ unaligned
sub sp, sp, #8
str r0, [sp, #4] @ unaligned
ldr r0, [sp, #4]
add sp, sp, #8
bx lr
정말로..
알겠습니다, 상황이 생각보다 혼란스럽습니다.이를 명확히 하기 위한 노력의 일환으로, 다음은 이 여정에 대한 결과는 다음과 같습니다.
정렬되지 않은 메모리 액세스
- 정렬되지 않은 메모리에 접근할 수 있는 유일한 휴대용 C 표준 솔루션은
memcpy하나요. 이 질문을 통해 또 다른 질문을 받고 싶었는데, 지금까지 발견된 것은 이것뿐인 것 같습니다.
예제 코드:
u32 read32(const void* ptr) {
u32 value;
memcpy(&value, ptr, sizeof(value));
return value; }
이 솔루션은 모든 상황에서 안전합니다.사소한 것으로 압축하기도 합니다.load registerGCC를 사용하는 x86 타겟에 대한 작업.
그러나 GCC를 사용하는 ARM 타겟에서는 너무 크고 쓸모없는 조립 순서로 해석되어 성능이 저하됩니다.
클랑 온 ARM 표적을 이용해서memcpy잘 작동합니다(아래 @not like that comment 참조).전반적으로 GCC를 비난하는 것은 쉽겠지만, 그렇게 간단하지는 않습니다.memcpy솔루션은 x86/x64, PPC 및 ARM64 타겟을 사용하는 GCC에서 잘 작동합니다.마지막으로, 다른 컴파일러인 icc13을 시도해보면, memcpy 버전은 x86/x64에서 놀라울 정도로 무겁습니다(4개의 명령어, 하나면 충분합니다).지금까지 제가 테스트할 수 있었던 조합들입니다.
저는 godbolt의 프로젝트에 감사해야 합니다. 그러한 진술을 쉽게 관찰할 수 있도록 말이죠.
- 두번째 해결책은 사용하는 것입니다.
__packed구조물들. 솔루션은 C 표준이 아니며 컴파일러의 확장자에 전적으로 의존합니다.이 솔루션은 C 표준이 아니며 컴파일러의 확장에 전적으로 의존합니다.결과적으로, 그것을 쓰는 방법은 컴파일러에 따라 그리고 때때로 그것의 버전에 따라 달라집니다.이것은 휴대용 코드의 유지보수를 위해 엉망입니다.
하지만, 대부분의 상황에서, 그것은 보다 더 나은 코드 생성으로 이어집니다.memcpy 대부분의 상황에서...
예를 들어, 위와 같은 경우들에 관하여,memcpy용액이 작동하지 않습니다. 다음과 같은 결과가 있습니다.
- ICC x86 에서:
__packed솔루션 워크 - GCC 를하는 ARMv7 의:
__packed솔루션 워크 GCC가 있는 ARMv6에서 : 작동하지 않습니다.조립품이 더 못생기게 보입니다.
memcpy.- 마지막 해결책은 직접 사용하는 것입니다.
u32정렬되지 않은 메모리 위치에 액세스할 수 있습니다.이 솔루션은 x86 cpu에서 수십 년 동안 작동했지만 일부 C 표준 원칙을 위반하기 때문에 권장되지 않습니다. 컴파일러는 이 문을 데이터가 적절하게 정렬되어 버그 코드가 생성된다는 보장으로 간주할 수 있습니다.
- 마지막 해결책은 직접 사용하는 것입니다.
불행하게도, 적어도 한 가지 경우에는 목표에서 성능을 추출할 수 있는 유일한 솔루션입니다.즉, ARMv6의 GCC를 위해서입니다.
이 하지 마십시오. 액세스를 , 즉 의를 할 수 . GCC는 정렬된 메모리 액세스를 위해 예약된 명령을 생성할 수 있습니다. 즉,LDM(Load Multiple), 충돌로 이어집니다.
요즘은 x86/x64에서도 코드를 이런 식으로 쓰는 것이 위험해지는데, 새로운 세대 컴파일러들이 호환되는 루프를 자동 벡터화하여 SSE/AVX 코드를 생성할 수도 있기 때문입니다. 이러한 메모리 위치가 적절하게 정렬되어 있다는 가정에 따라 프로그램을 충돌시킵니다.
요약하자면, 여기 규칙 : memcpy > packed > direct를 사용하여 표로 요약된 결과가 있습니다.
| compiler | x86/x64 | ARMv7 | ARMv6 | ARM64 | PPC |
|-----------|---------|--------|--------|--------|--------|
| GCC 4.8 | memcpy | packed | direct | memcpy | memcpy |
| clang 3.6 | memcpy | memcpy | memcpy | memcpy | ? |
| icc 13 | packed | N/A | N/A | N/A | N/A |
문제의 일부는 쉽게 설명할 수 없고 추가적인 최적화를 허용하지 않는 것일 수 있습니다.부하에 특화된 기능이 있다는 것은 각 호출 시마다 기능 호출이 발생하여 성능이 저하될 수 있음을 의미합니다.
당신이 할 수 있는 한가지는static inline, 이것은 컴파일러가 함수를 인라인으로 만들 수 있게 해 줄 것입니다.load32(), 따라서 성능이 향상됩니다.그러나 더 높은 최적화 수준에서 컴파일러는 이미 당신을 위해 이것을 줄을 그어야 합니다.
컴파일러가 4바이트 memcpy를 인라인화하면 가장 효율적인 일련의 로드 또는 저장소로 변환되어 정렬되지 않은 경계에서 여전히 작동할 가능성이 높습니다.따라서 컴파일러 최적화를 사용해도 성능이 여전히 낮은 경우 사용 중인 프로세서에서 정렬되지 않은 읽기 및 쓰기에 대해 최대 성능을 발휘할 수 있습니다.당신이 말한 이후로"__packed명령"은 동일한 성능을 제공합니다.memcpy(), 이런 경우가 있을 겁니다.
이 시점에서 데이터를 정렬하는 것 외에는 할 수 있는 일이 거의 없습니다.그러나 정렬되지 않은 연속 배열을 처리하는 경우u32's, 당신이 할 수 있는 한 가지가 있습니다.
#include <stdint.h>
#include <stdlib.h>
// get array of aligned u32
uint32_t *align32 (const void *p, size_t n) {
uint32_t *r = malloc (n * sizeof (uint32_t));
if (r)
memcpy (r, p, n);
return r;
}
이는 단지 다음을 사용하여 새 배열을 할당합니다.malloc(),왜냐면malloc()그리고 친구들은 모든 것에 대해 올바른 정렬로 메모리를 할당합니다.
malloc() 및 calloc() 함수는 모든 종류의 변수에 적합하게 정렬된 할당된 메모리에 포인터를 반환합니다.
- ,
malloc(3)리눅스 프로그래머 매뉴얼
데이터 집합당 한 번만 이 작업을 수행하면 되므로 비교적 빠릅니다.그리고 복사를 하면서.memcpy()초기 정렬 부족에 대해서만 조정할 수 있고, 그 후에는 가능한 가장 빠른 정렬 로드 및 저장 지침을 사용할 수 있으며, 그 후에는 정상적으로 정렬된 읽기 및 쓰기를 사용하여 최대 성능으로 데이터를 처리할 수 있습니다.
언급URL : https://stackoverflow.com/questions/32062894/take-advantage-of-arm-unaligned-memory-access-while-writing-clean-c-code
'programing' 카테고리의 다른 글
| 열을 날짜 형식으로 변환(Pandas Dataframe) (0) | 2023.10.18 |
|---|---|
| 기본 FirebaseApp이 초기화되지 않았습니다. (0) | 2023.10.18 |
| Jquery AJAX로 페이지 프레임 로드 (0) | 2023.10.18 |
| 코드 점화기 트랜잭션 (0) | 2023.10.18 |
| 오라클의 스파크 쿼리(로드)가 SQOOP에 비해 매우 느린 이유는 무엇입니까? (0) | 2023.10.18 |