메모리 정렬이 얼마나 중요합니까? 여전히 문제가 되나요?


15

얼마 전부터 메모리 정렬, 작동 방식 및 사용 방법에 대해 많은 것을 검색하고 읽었습니다. 내가 찾은 가장 관련성 높은 기사는 이것 입니다.

그러나 그럼에도 불구하고 여전히 그것에 대해 몇 가지 질문이 있습니다.

  1. 임베디드 시스템에서 우리는 종종 메모리 관리를 덜 비판적으로 만드는 컴퓨터에 엄청난 양의 메모리를 가지고 있습니다. 완전히 최적화에 있지만 지금은 동일한 프로그램을 또는와 비교하면 차이를 만들 수있는 것입니다. 메모리를 재정렬하고 정렬하지 않고?
  2. 메모리 정렬에 다른 장점이 있습니까? CPU가 정렬 된 메모리로 더 잘 작동하고 빠르다는 어딘가를 읽었습니다. 처리하는 데 필요한 지침이 적기 때문에 (당신 중 하나가 기사 / 벤치 마크에 대한 링크가 있습니까?), 그 차이가 실제로 중요합니까? 이 두 가지보다 더 많은 장점이 있습니까?
  3. 기사 링크의 5 장에서 저자는 다음과 같이 말합니다.

    C ++에서 구조체처럼 보이는 클래스는이 규칙을 어길 수 있습니다! 기본 클래스와 가상 멤버 함수의 구현 방법에 따라 달라 지든 컴파일러에 따라 다릅니다.

  4. 이 기사는 주로 구조에 대해 이야기하지만 지역 변수 선언도 이러한 요구에 영향을 받습니까?

    C ++에서 메모리 정렬이 어떻게 작동하는지에 대한 아이디어가 있습니까?

이 이전 질문 에는 "정렬"이라는 단어가 포함되어 있지만 위의 질문에 대한 답변은 제공하지 않습니다.


C ++ 컴파일러는이를 위해 더 필요하다 (필요하거나 유리한 곳에 패딩 삽입). 언급 한 링크에서 섹션 12 "도구"를 참조하십시오.
rwong

답변:


11

그렇습니다. 데이터 정렬과 배열 모두 성능에 큰 차이를 만들 수 있습니다. 몇 퍼센트뿐만 아니라 몇 퍼센트에서 수백 퍼센트까지도 마찬가지입니다.

루프를 충분히 실행하면 두 가지 지침이 중요합니다.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

캐시 유무와 분기 예측에서 캐시 토스 유무에 따른 정렬을 통해 두 명령 성능을 상당한 양 (타이머 틱)으로 변경할 수 있습니다.

min      max      difference
00016DDE 003E025D 003C947F

스스로 쉽게 수행 할 수있는 성능 테스트. 테스트중인 코드 주위에 nops를 추가 또는 제거하고 정확한 타이밍 작업을 수행하고, 테스트중인 명령어를 넓은 범위의 주소를 따라 이동하여 캐시 라인의 가장자리 등을 만지십시오.

데이터 액세스와 같은 종류의 것. 일부 아키텍처는 데이터 오류를 제공하여 정렬되지 않은 액세스 (예 : 주소 0x1001에서 32 비트 읽기 수행)에 대해 불평합니다. 그중 일부는 오류를 비활성화하고 성능을 저하시킬 수 있습니다. 정렬되지 않은 액세스를 허용하는 다른 사용자는 성능 저하를 얻습니다.

때때로 "지침"이지만 대부분의 경우 클럭 / 버스 사이클입니다.

다양한 대상에 대한 gcc의 memcpy 구현을 살펴보십시오. 0x43 바이트의 구조를 복사한다고 가정하면 0x42를 떠나는 1 바이트를 복사 한 다음 0x40 바이트를 큰 효율적인 청크로 복사 한 다음 마지막 0x2는 두 개의 개별 바이트 또는 16 비트 전송으로 수행 할 수 있습니다. 소스 및 대상 주소가 0x1003 및 0x2003과 같은 동일한 정렬에 있으면 정렬 및 대상이 작동합니다. 한 바이트를 수행 한 다음 큰 청크에서 0x40을 누른 다음 0x2를 수행 할 수 있지만 하나는 0x1002이고 다른 하나는 0x1003이면 얻을 수 있습니다 진짜 못 생겼고 천천히

대부분의 경우 버스주기입니다. 또는 전송 횟수가 더 나쁩니다. ARM과 같은 64 비트 폭의 데이터 버스가있는 프로세서를 가져 와서 주소 0x1004에서 4 워드 전송 (읽기 또는 쓰기, LDM 또는 STM)을 수행하십시오. 즉, 워드 정렬 주소이며 완벽하게 합법적이지만 버스가 64 인 경우 비트 폭이 경우 단일 명령어는이 경우 0x1004에서 32 비트, 0x1008에서 64 비트, 0x100A에서 32 비트로 3 개의 전송으로 바뀔 수 있습니다. 그러나 동일한 명령을 가지고 있지만 주소 0x1008에서는 주소 0x1008에서 단일 4 워드 전송을 수행 할 수 있습니다. 각 전송에는 관련 설정 시간이 있습니다. 따라서 0x1004 ~ 0x1008 주소 차이 자체는 캐시를 사용할 때 짝수 / 더 빠를 수 있으며 모두 캐시 적중입니다.

말하자면, 주소 0x1000 대 0x0FFC에서 두 단어 읽기를하더라도 캐시 누락이있는 0x0FFC는 두 개의 캐시 라인 읽기를 유발합니다 .0x1000은 하나의 캐시 라인입니다. 액세스 (사용하는 것보다 많은 데이터 읽기)하지만 두 배가됩니다. 구조가 일반적으로 정렬되거나 데이터가 일반적인 방식 및 해당 데이터에 액세스하는 빈도 등으로 인해 캐시 스 래싱이 발생할 수 있습니다.

퇴거를 생성 할 수있는 데이터를 처리 할 때 캐시의 일부만 사용하여 실제 운이 나빠질 수 있으며 데이터를 건너 뛰면 다음 데이터 블롭이 이전 블롭과 충돌 할 수 있습니다. . 소스 코드 등에서 데이터를 믹싱하거나 함수를 다시 정렬하면 충돌을 생성하거나 제거 할 수 있습니다. 모든 캐시가 동일하게 생성되는 것은 아니기 때문에 컴파일러가 여기에있는 것은 아닙니다. 성능 저 하나 개선을 감지하는 것조차 당신에게 달려 있습니다.

성능 향상, 더 넓은 데이터 버스, 파이프 라인, 캐시, 분기 예측, 다중 실행 단위 / 경로 등을 개선하기 위해 추가 한 모든 사항은 대부분 도움이되지만 의도적으로 또는 우연히 악용 될 수있는 약점을 가지고 있습니다. 성능에 관심이 있고 튜닝해야 할 가장 큰 튜닝 요소 중 하나는 32, 64, 128, 256뿐만 아니라 코드와 데이터의 정렬입니다. 비트 경계뿐만 아니라 사물이 서로 관련되어있는 경우 루프를 많이 사용하거나 재사용 된 데이터를 동일한 캐시 방식으로 랜딩하지 않기를 원할 경우 각각 자체적으로 원합니다. 컴파일러는 예를 들어 슈퍼 스칼라 아키텍처에 대한 명령어 순서를 정하고 서로에 대해 중요하지 않은 명령어를 다시 정렬 할 수 있습니다.

가장 큰 감독은 프로세서가 병목 상태라는 가정입니다. 10 년 이상 사실이 아니었다면, 프로세서를 공급하는 것이 문제이며, 정렬 성능 저하, 캐시 스 래싱 등과 같은 문제가 발생합니다. 소스 코드 수준에서도 약간의 작업만으로 구조에서 데이터 재정렬, 변수 / 구조 선언 순서, 소스 코드 내 함수 순서 및 데이터 정렬을위한 약간의 추가 코드로 여러 번의 성능을 향상시킬 수 있습니다. 더.


마지막 단락에만 +1합니다. 메모리 대역폭은 오늘날 명령 수가 아닌 빠른 코드를 작성하려는 모든 사람에게 가장 중요한 문제입니다. 이는 여러 상황에서 정렬을 수정하여 수행 할 수있는 캐시 미스를 줄이기 위해 최적화하는 것이 매우 중요하다는 것을 의미합니다.
Jules

코드와 데이터가 캐시되고 해당 데이터에 대해 충분한 루프 / 사이클을 수행하는 경우 명령 카운트 및 명령이 페치 라인 내 위치, 분기가 파이프 내에서 의존하는 지점과 관련된 지점이 중요합니다. 그러나 드람 및 / 또는 플래시 기반 시스템에서는 먼저 프로세서 공급에 대해 걱정해야합니다.
old_timer

15

예, 메모리 정렬은 여전히 ​​중요합니다.

일부 프로세서는 실제로 정렬되지 않은 주소에서 읽기를 수행 할 수 없습니다. 이러한 하드웨어에서 실행 중이고 정렬되지 않은 정수를 저장하는 경우 실제로 사용할 수 있도록 다양한 바이트를 올바른 위치로 가져 오기 위해 두 개의 명령어와 몇 가지 추가 명령어로 정수를 읽어야 할 것입니다 . 따라서 정렬 된 데이터는 성능이 중요합니다.

좋은 소식은 대부분 실제로 신경 쓸 필요가 없다는 것입니다. 거의 모든 언어의 거의 모든 컴파일러는 대상 시스템의 정렬 요구 사항을 준수하는 기계어 코드를 생성합니다. 데이터의 메모리 내 표현을 직접 제어하는 ​​경우에만 생각하면됩니다. 이전과 같이 자주 필요한 곳은 아닙니다. 작성하는 다양한 구조의 메모리 사용을 이해하려면 패딩을 피하면서 더 효율적으로 작업을 재구성하는 방법을 알아야합니다. 그러나 그러한 종류의 제어가 필요하지 않은 한 (그리고 대부분의 시스템에서는 그렇지 않습니다), 알지 못하거나 신경 쓰지 않고 전체 경력을 즐겁게 보낼 수 있습니다.


1
특히 ARM은 정렬되지 않은 액세스를 지원하지 않습니다. 그리고 그것은 모바일이 사용하는 거의 모든 CPU입니다.
Jan Hudec

또한 Linux는 일부 런타임 비용으로 정렬되지 않은 액세스를 에뮬레이트하지만 Windows (CE 및 전화)는 정렬하지 않은 액세스를 시도하면 응용 프로그램이 중단됩니다.
Jan Hudec

2
이것이 대부분 사실이지만, x86을 포함한 일부 플랫폼 은 사용될 명령에 따라 다른 정렬 요구 사항을 가지고 있습니다 . 이는 컴파일러가 자체적으로 해결하기 쉽지 않기 때문에 때로는 패딩해야합니다. 일부 작업에는 특정 작업 (예 : SSE 명령어, 대부분 16 바이트 정렬이 필요함)을 사용할 수 있습니다. 또한 자주 사용되는 두 항목이 동일한 캐시 라인 (16 바이트)에서 발생하도록 패딩을 추가하면 경우에 따라 성능에 큰 영향을 미칠 수 있으며 자동화되지도 않습니다.
Jules

3

예, 여전히 중요하며 일부 성능 결정 알고리즘에서는 컴파일러에 의존 할 수 없습니다.

몇 가지 예만 나열하겠습니다.

  1. 에서 이 대답 :

일반적으로 마이크로 코드는 메모리에서 적절한 4 바이트 수량을 가져 오지만, 정렬되지 않은 경우 메모리에서 두 개의 4 바이트 위치를 가져 와서 두 위치의 해당 바이트에서 원하는 4 바이트 수량을 재구성해야합니다.

  1. SSE 명령어 세트에는 특별한 정렬이 필요합니다. 충족되지 않으면 특수 기능을 사용하여 정렬되지 않은 메모리에 데이터를로드하고 저장해야합니다. 그것은 두 가지 추가 지침을 의미합니다.

성능이 중요한 알고리즘을 사용하지 않는 경우 메모리 정렬을 잊어 버리십시오. 일반 프로그래밍에는 실제로 필요하지 않습니다.


1

우리는 중요한 상황을 피하는 경향이 있습니다. 중요한 경우 중요합니다. 예를 들어 오늘날에는 피해야 할 이진 데이터를 처리 할 때 발생하는 정렬되지 않은 데이터 (사람들은 XML 또는 JSON을 많이 사용함).

어쨌든 정렬되지 않은 정수 배열을 만들면 일반적인 인텔 프로세서에서 해당 배열을 처리하는 코드가 정렬 된 데이터보다 약간 느리게 실행됩니다. ARM 프로세서에서는 컴파일러에 데이터가 정렬되지 않은 경우 약간 느리게 실행됩니다. 컴파일러에 알리지 않고 정렬되지 않은 데이터를 사용하는 경우 프로세서 모델 및 운영 체제에 따라 끔찍하거나 끔찍하게 느리게 실행되거나 잘못된 결과를 초래할 수 있습니다.

C ++에 대한 참조 설명 : C에서 구조체의 모든 필드는 오름차순 메모리 순서로 저장해야합니다. 따라서 char / double / char 필드가 있고 모든 것이 정렬되도록하려면 1 바이트 문자, 7 바이트 사용되지 않음, 8 바이트 더블, 1 바이트 문자, 7 바이트 사용되지 않습니다. C ++ 구조체에서는 호환성에 대해 동일합니다. 그러나 구조체의 경우 컴파일러가 필드를 재정렬 할 수 있으므로 1 바이트 문자, 다른 바이트 문자, 6 바이트 미사용, 8 바이트 더블이있을 수 있습니다. 24 바이트 대신 16을 사용합니다. C 구조체에서 개발자는 일반적으로 이러한 상황을 피하고 필드를 다른 순서로 갖습니다.


1
정렬되지 않은 데이터는 메모리에서 발생합니다. 적절하게 압축 된 데이터 구조를 갖지 않는 프로그램은 겉으로는 중요하지 않은 값의 순서로 인해 막대한 성능 저하를 겪을 수 있습니다. 예를 들어 lthreaded 코드에서 단일 캐시 라인의 두 값은 두 스레드가 동시에 액세스 할 때 막대한 파이프 라인 중단을 유발합니다 (물론 스레드 안전 문제 무시).
greyfade

C ++ 컴파일러는 특정 조건에서만 필드를 재정렬 할 수 있으며 이러한 규칙을 모르면 충족되지 않을 수 있습니다. 또한 실제로이 자유를 사용하는 C ++ 컴파일러를 알지 못합니다.
Sjoerd

1
C 컴파일러 재정렬 필드를 본 적이 없습니다. 예를 들어 char / int 사이에 많은 인서트 패딩과 정렬을 보았습니다.
PaulHK

1

위의 답변에는 많은 좋은 점이 이미 언급되어 있습니다. 메모리 검색 및 마이닝 성능을 다루는 데이터 검색 / 마이닝을 다루는 내장되지 않은 시스템에서도 추가하는 것은 정렬 어셈블리 코드가 아닌 다른 어셈블리 코드가 작성되도록 중요합니다.

나는 또한 가치있는 독서를 추천합니다 : http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf


1

메모리 정렬이 얼마나 중요합니까? 여전히 문제가 되나요?

예. 아뇨.

임베디드 시스템에서 우리는 종종 메모리 관리를 덜 비판적으로 만드는 컴퓨터에 엄청난 양의 메모리를 가지고 있습니다. 완전히 최적화에 있지만 지금은 동일한 프로그램을 또는와 비교하면 차이를 만들 수있는 것입니다. 메모리를 재정렬하고 정렬하지 않고?

응용 프로그램의 메모리 공간이 더 작고 올바르게 정렬되면 더 빠르게 작동합니다. 일반적인 데스크톱 응용 프로그램에서는 응용 프로그램이 항상 동일한 성능 병목 현상으로 끝나고 최적화가 필요한 경우와 같이 드문 / 비정상적인 경우를 제외하고는 문제가되지 않습니다. 즉, 적절하게 정렬되면 앱이 더 작고 빨라지지만 대부분의 실제 경우 앱이 사용자에게 영향을 미치지 않아야합니다.

메모리 정렬에 다른 장점이 있습니까? CPU가 정렬 된 메모리로 더 잘 작동하고 빠르다는 어딘가를 읽었습니다. 처리하는 데 필요한 지침이 적기 때문에 (당신 중 하나가 기사 / 벤치 마크에 대한 링크가 있습니까?), 그 차이가 실제로 중요합니까? 이 두 가지보다 더 많은 장점이 있습니까?

그것은 될 수 있습니다. 코드를 작성하는 동안 (아마도) 명심해야 할 것이지만 대부분의 경우 단순히 중요하지 않습니다 (즉, 메모리 공간과 액세스 빈도에 따라 멤버 변수를 정렬합니다-캐싱이 쉬워야 함). 캐싱 목적이 아닌 코드의 사용 / 읽기 및 리팩토링 용이성).

C ++에서 메모리 정렬이 어떻게 작동하는지에 대한 아이디어가 있습니까?

alignof 물건이 나왔을 때 그것에 대해 읽었습니다 (C ++ 11?) 그 이후로 신경 쓰지 않았습니다 (주로 데스크톱 응용 프로그램과 백엔드 서버 개발을하고 있습니다).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.