2009-05-15 2 views
11

패딩 효과를 줄이기 위해 구조체의 필드를 수동으로 다시 정렬하는 데 몇 분을 소비했습니다. [1], 이는 몇 분 정도 지나치게 느껴집니다. 나의 직감은 내 시간이 나를 위해 이런 종류의 최적화를 할 펄 스크립트 또는 기타 등등을 쓰는 것이 더 나을 수도 있다고 말한다.패딩을 피하기 위해 C 구조체의 자동 필드 재정렬

제 질문은이 역시 중복되는지 여부입니다. 이미 알고있는 일부 도구, 또는 구조체를 팩하기 위해 [2]를 켤 수 있어야하는 일부 컴파일러 기능이 있습니까?

이 문제는 몇 가지 아키텍처에서 일관되게 최적화해야하므로 더 복잡한 구조이므로 정렬 도구와 포인터 크기를 고려해야합니다.

EDIT : 빠른 설명 - 패딩을 피하기 위해 소스 코드의 필드를 재정렬하고, 패딩없이 컴파일하는 것처럼 구조체를 "팩하지"않습니다.

EDIT # 2 : 또 다른 복잡함 : 구성에 따라 일부 데이터 유형의 크기가 변경 될 수도 있습니다. 명백한 것들은 다른 아키텍처에 대한 포인터와 포인터 차이뿐 아니라 부동 소수점 유형 ('정확도'에 따라 16, 32 또는 64 비트), 체크섬 ('속도'에 따라 8 또는 16 비트) 및 일부 다른 명백하지 않은 것들.

[1]에 해당 구조체가 인스턴스화되고 임베디드 장치에 천배하므로 구조체의 각 4 바이트 감소는 차이점은이 프로젝트 않음 - 이동 및 이동 없음을 의미 할 수있다.

[2] 사용 가능한 컴파일러는 GCC 3. * 및 4. *, Visual Studio, TCC, ARM ADS 1.2, RVCT 3 * 및 기타 다소 모호합니다.

+1

이 구조체의 필요의 인스턴스로 수행 장치간에 이식 가능하거나 각 아키텍처가 자체 포장을 가지고 있어도 괜찮습니까? – Alnitak

+1

그냥 옆으로 : 나는 이것이 흥미로운 문제라고 생각하고 "perl struct reordering"을 검색했습니다. 이것이 최고의 결과였습니다. 질문은 불과 15 분입니다! –

+1

Alnitak - 그렇습니다. 실제로 이식성이 있어야하는 코드입니다. 각 아키텍처마다 고유 한 구조체 정의가있는 것이 좋지만 직접 아키텍처 고유의 정의를 작성하는 것은 비현실적입니다. – Christoffer

답변

6

은, 그때 손으로 구조체를 최적화 권장합니다. 도구를 사용하면 구성원을 최적으로 정렬 할 수 있지만 예를 들어 16 비트로 저장하는이 값은 실제로 1024를 초과하지 않으므로 에 대한 상위 6 비트를 훔칠 수 있습니다. 값은 여기 ...

이렇게 인간은 거의이 직업에서 로봇을 때릴 것입니다.

[편집]하지만 실제로는 각 아키텍처에 대한 구조를 손으로 최적화하고 싶지 않은 것처럼 보입니다. 어쩌면 정말 많은 아키텍처를 지원할 수 있을까요?

이 문제는 일반적인 해결책으로는 받아 들일 수 없다고 생각하지만 도메인 지식을 각 아키텍처의 구조체 정의를 생성하는 사용자 정의 Perl/Python/something 스크립트로 인코딩 할 수 있습니다.

또한 모든 구성원의 크기가 2의 제곱이면 구성원을 크기별로 정렬하여 최적의 크기를 얻을 수 있습니다.)이 경우, 당신은 좋은 구식 매크로 기반의 구조체 건물을 사용할 수 있습니다 - 뭔가를 같이 :

#define MYSTRUCT_POINTERS  \ 
    Something* m_pSomeThing; \ 
    OtherThing* m_pOtherThing; 

#define MYSTRUCT_FLOATS  \ 
    FLOAT m_aFloat;   \ 
    FLOAT m_bFloat; 

#if 64_BIT_POINTERS && 64_BIT_FLOATS 
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS MYSTRUCT_FLOATS 
#else if 64_BIT_POINTERS 
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_POINTERS 
#else if 64_BIT_FLOATS 
    #define MYSTRUCT_64_BIT_MEMBERS MYSTRUCT_FLOATS 
#else 
    #define MYSTRUCT_64_BIT_MEMBERS 
#endif 

// blah blah blah 

struct MyStruct 
{ 
    MYSTRUCT_64_BIT_MEMBERS 
    MYSTRUCT_32_BIT_MEMBERS 
    MYSTRUCT_16_BIT_MEMBERS 
    MYSTRUCT_8_BIT_MEMBERS 
}; 
+0

누군가가 똑똑한 로봇을 만들 때까지 (이 직업을 위해)! –

+0

동의; 여기에는 많은 문맥 의존 지식이 관련되어 있습니다. 물론 구조가 매우 많고 도구에서 사용할 수있는 형식으로 해당 지식을 모두 포함 할 수 있다면 자동화 할 수 있습니다. –

+0

답변 해 주셔서 감사합니다. 최적의 주문에 대한 질문이 있습니다. 당신의 대답에서 당신은 최적의 순서가 가장 큰 것에서 가장 작은 것까지 정렬되어 있다고 언급했습니다. 그 진술에 대해 증명이 있습니까? 나는 많은 경우를 시도했으며,이 모든 것이 성명을 깨뜨릴 수 없기 때문에 그것을 입증 할 수있는 방법이 궁금합니다. 고맙습니다. – yoco

0

#pragma pack을 살펴보십시오. 이렇게하면 컴파일러가 구조의 요소를 정렬하는 방법이 변경됩니다. 당신은 그것들을 공간없이 밀접하게 포장하도록 강제 할 수 있습니다. 스토리지에서 짜낼 수있는 모든 단어가 중요한 경우

See more details here

+1

정렬 된 멤버에 액세스하는 것이 더 효율적이기 때문에 기본적으로 구조체가 압축되지 않습니다. 구조체를 재정렬하면 실제로 멤버의 정렬을 끊지 않고 구조체의 크기를 줄일 수 있습니다. – Artelius

+0

그가 요구하는 것이 아니라 ... 그에게 최적의 포장을 줄 것입니다. –

2

대부분의 C 컴파일러하지 않습니다 할이 당신이 이상한 물건을 할 수 있다는 사실에 기초 (struct에서 요소의 주소를 취한 다음 포인터 매직 (pointer magic)을 사용하여 나머지 부분에 액세스하고 컴파일러를 우회합니다. 유명한 예가 목록의 머리와 꼬리로 가디언 노드를 사용하는 AmigaOS의 이중 링크 목록입니다 (이렇게하면 목록을 탐색 할 때 if를 피할 수 있습니다). 보호자 헤드 노드는 항상 pred == null이고 테일 노드는 next == null이고 개발자는 두 노드를 단일 포인터 포인터 head_next null tail_pred으로 굴립니다. head_next 또는 null의 주소를 머리 및 꼬리 노드의 주소로 사용하여 4 바이트와 하나의 메모리 할당을 저장했습니다 (전체 구조가 한 번만 필요했기 때문에).

그래서 가장 좋은 방법은 구조를 의사 코드로 작성한 다음 그로부터 실제 구조를 만드는 전 처리기 스크립트를 작성하는 것입니다.

+1

구조체의 필드가 구조체에 선언 된 순서대로 메모리에 나타나야하는 명세를 위반할 수 있으므로 C 컴파일러는이를 수행하지 않습니다. – unwind

+0

사양을 어기는 느낌이 들지 않았습니다. –

+0

@unwind는 기본적으로 완료되지 않았지만 gcc의 구버전에는'-fipa-struct-reorg' 옵션을 사용하여 구조체 멤버를 재정렬합니다. http://stackoverflow.com/a/28780286/995714 –

6

일반적으로 Perl 설치에 포함되어있는 pstruct라는 Perl 스크립트가 있습니다. 스크립트는 구조체 멤버 오프셋 및 크기를 덤프합니다. pstruct를 수정하거나 구조를 원하는대로 포장하는 유틸리티를 만들기위한 출발점으로 출력을 사용할 수 있습니다.

$ cat foo.h 
struct foo { 
    int x; 
    char y; 
    int b[5]; 
    char c; 
}; 

$ pstruct foo.h 
struct foo { 
    int    foo.x      0  4 
    char    foo.y      4  1 
        foo.b      8  20 
    char    foo.c      28  1 
} 
+0

좋은 생각이지만 pstruct가 가지고있는 것 같습니다. C + + 문제 : – Jezz

0

플랫폼/컴파일러에 따라 다릅니다. 그래서이 반바지와 긴있는 구조체를 가정 (! 또는 악화) 4 바이트 정렬로, 대부분의 컴파일러 패드 모두를 언급 한 바와 같이 :

short 
long 
short 

패딩의 2 * 2 바이트와 12 바이트 (소요됩니다). 수를 재정렬

short 
short 
long 

여전히 (그들은 메모리 사용을 통해 빠른 액세스를 선호으로, 대부분의 데스크톱 기본값)은 빠른 데이터 접근을 할 수있는 컴파일러 의지 패드와 같은 12 바이트를 차지합니다 . 임베디드 시스템에는 다양한 요구가 있으므로 #pragma pack을 관계없이 사용해야합니다.

재주문 도구는 구조 레이아웃을 간단하게 (수동으로) 재구성하여 여러 유형이 함께 배치되도록합니다. 모든 반바지를 먼저 끼워 넣은 다음 모든 긴 끈을 넣으십시오. 포장을 완성하려면 공구가 어쨌든 할 것입니다. 유형 사이의 전환 지점에서 가운데에 2 바이트의 패딩이있을 수 있지만 걱정할 필요가 없다고 생각합니다.

+0

좋은 조언을하지만 최신 편집을 참조하십시오 ... – Christoffer

+0

그리고 다른 데이터 형식 크기에 관한 내 대답을 삭제했다고 생각합니다! 같은 유형의 모든 필드를 함께 넣으면 각 필드의 크기에 상관없이 최적의 패킹을 얻으십시오 – gbjbaanb

+0

"모든 것이 4 바이트 정렬"에 대해 확실하지 않습니다. 컴파일러는 각 멤버가 최소 정렬 요구 사항을 충족하는지 확인합니다. 예를 들어, long double에 16 바이트가 필요한 경우 정렬을 수행하면 char에 이어 long double이 15 바이트 홀을 남기고 일반적으로 short는 2 바이트 정렬이 필요하고 short 뒤에 char이 1 바이트 홀을 남겨 둡니다 (그리고 앙상블 - char, short - long double이 뒤 따르면 12 바이트 구멍이 남지만 32 비트 int이면 short와 int 사이에 아무런 구멍도 남지 않습니다. 기타 –

0

컴파일러는 구조체의 필드를 자체 머리로 재정렬 할 수 없습니다. 표준에서는 정의 된 순서대로 필드를 레이아웃해야한다고 규정하고 있습니다. 다른 것을하면 미묘한 방법으로 코드가 손상 될 수 있습니다.

글을 쓰는 동안 효율적으로 필드 주변을 뒤섞는 일종의 코드 생성기를 만드는 것은 물론 가능합니다. 그러나 저는 이것을 수동으로하는 것을 선호합니다.

0

그런 도구를 만드는 방법에 대해 생각해 봅시다. 디버깅 정보부터 시작할 것입니다.

소스에서 각 구조의 크기를 얻는 것은 고통입니다. 컴파일러가 이미 수행 한 많은 작업과 겹칩니다. 필자는 ELF가 디버그 바이너리에서 구조체 크기 정보를 추출하는 방법을 정확하게 설명하지는 못했지만 디버거가 표시 할 수 있기 때문에 정보가 존재한다는 것을 알고 있습니다.아마도 objdump 나 binutils 패키지의 다른 것들이 당신을 위해 쉽게 얻을 수 있습니다. (적어도 ELF를 사용하는 플랫폼의 경우).

정보를 얻은 후에는 나머지는 매우 간단합니다. 가능한 한 원래 구조체의 순서를 유지하려고 시도하면서 멤버를 큰 순서에서 작은 순서로 정렬합니다. perl이나 python을 사용하면 다른 소스와 상호 참조하고 주석이나 #ifdefs를 사용하는 방법에 따라 #ifdefs를 유지하기가 쉽습니다. 가장 큰 고통은 전체 코드베이스에서 구조체의 모든 초기화를 변경하는 것입니다. Yikes.

여기 있습니다. 정말 좋은 생각이 들지만, 이것을하는 기존의 도구에 대해 알고 싶지는 않습니다. 직접 작성할 때가되면 ... 여러분이 직접 구조체의 대부분을 재정렬 할 수있을 것이라고 생각합니다. 프로그램.

0

동일한 문제가있었습니다. 다른 대답에서 제안 된 것처럼 pstruct가 도움이 될 수 있습니다. 그러나 그것은 우리에게 필요한 것을 정확하게 제공합니다. 사실 pstruct는 gcc가 제공하는 디버그 정보를 사용합니다. 나는 같은 생각을 바탕으로 또 다른 스크립트를 썼다.

STUBS 디버그 정보 (-gstubs)로 어셈블리 파일을 생성해야합니다. (드워프로부터 같은 정보를 얻을 수도 있지만 pstruct와 같은 방법을 사용했습니다). 컴파일 과정을 수정하지 않고이 작업을 수행하는 좋은 방법은 컴파일 옵션에 "-gstubs -save-temps=obj"을 추가하는 것입니다.

는 다음 스크립트는 어셈블리 파일을 읽고 추가 바이트 구조체에 추가 될 때 감지 :

#!/usr/bin/perl -n 

    if (/.stabs[\t ]*"([^:]*):T[()0-9,]*=s([0-9]*)(.*),128,0,0,0/) { 
     my $struct_name = $1; 
     my $struct_size = $2; 
     my $desc = $3; 
     # Remove unused information from input 
     $desc =~ s/=ar\([0-9,]*\);[0-9]*;[-0-9]*;\([-0-9,]*\)//g; 
     $desc =~ s/=[a-zA-Z_0-9]+://g; 
     $desc =~ s/=[\*f]?\([0-9,]*\)//g; 
     $desc =~ s/:\([0-9,]*\)*//g; 
     my @members = split /;/, $desc; 
     my ($prev_size, $prev_offset, $prev_name) = (0, 0, ""); 
     for $i (@members) { 
      my ($name, $offset, $size) = split /,/, $i; 
      my $correct_offset = $prev_offset + $prev_size; 
      if ($correct_offset < $offset) { 
      my $diff = ($offset - $correct_offset)/8; 
      print "$struct_name.$name looks misplaced: $prev_offset + $prev_size = $correct_offset < $offset (diff = $diff bytes)\n"; 
      } 
      # Skip static members 
      if ($offset != 0 || $size != 0) { 
      ($prev_name, $prev_offset, $prev_size) = ($name, $offset, $size); 
      } 
     } 
    } 

좋은 방법은 그것을 호출 :

find . -name *.s | xargs ./detectPaddedStructs.pl | sort | un 
관련 문제