uint_fast32_t보다 uint32_t가 선호되는 이유는 무엇입니까?


81

(나는 이것이 일화적인 증거라는 것을 알고 있습니다) uint32_t보다 훨씬 더 널리 퍼져있는 것 같습니다 uint_fast32_t. 하지만 그것은 나에게 반 직관적 인 것 같습니다.

거의 항상 구현 사용을 볼 때 uint32_t실제로 원하는 것은 최대 4,294,967,295 값을 보유 할 수있는 정수뿐입니다 (일반적으로 65,535에서 4,294,967,295 사이의 훨씬 낮은 범위).

'정확히 32 비트' 보장이 필요하지 않고 '가장 빠른> = 32 비트' 보장 이 정확히 올바른 아이디어 인 것처럼 보이 uint32_t므로 를 사용하는 것이 이상해 보입니다. 또한 일반적으로 구현되지만 실제로 존재한다는 보장은 없습니다.uint_fast32_tuint32_t

그렇다면 왜 uint32_t선호 될까요? 단순히 더 잘 알려져 있습니까 아니면 다른 것에 비해 기술적 이점이 있습니까?


24
간단한 대답, 정확히 32 비트를 가진 정수가 필요할까요?
Stargateur

7
처음에는 uint32_fast_t, 내가 올바르게 이해하고 있다면 적어도 32 비트 (더 많을 수 있다는 뜻입니까? 오해의 소지가있는 것 같음)에 대해 들었습니다. uint32_t이 데이터를 압축하고 네트워크를 통해 전송하고 있기 때문에 현재 프로젝트에서 및 친구를 사용 하고 있으며 발신자와 수신자가 필드의 크기를 정확히 알기를 원합니다. 플랫폼이 구현되지 않을 수 있기 때문에 이것이 가장 강력한 솔루션이 아닐 수도 uint32_t있지만 내 모든 것이 분명히 수행하므로 내가하는 일에 문제가 없습니다.
yano

5
@yano : 네트워킹을 위해, 당신은 또한 바이트 순서 / 엔디안에 대해 관심을 가져야 - uint32_t(그리고 유감가 더 있어요 없다 당신을 제공하지 않습니다 uint32_t_beuint32_t_le거의 모든 가능한 경우에 더 적합 할 것이다, uint32_t현재 최선의 방법입니다).
Brendan

3
@Brendan-_be 및 _le과 관련하여 htonl () 및 ntohl ()이 동일한 기능을 제공합니까?
mpez0 2017-10-27

2
@Brendan은 모두 원시 유형 인 표준 int에 숨길 꽤 무거운 객체입니다. 나는이이 표준 곳에서 처리되어야 함을 원칙으로 당신과 동의하지만 난이 힘이 장소가 될 생각하지 않는다
스티브 콕스

답변:


78

uint32_t지원하는 모든 플랫폼에서 거의 동일한 속성을 갖도록 보장됩니다. 1

uint_fast32_t 비교에서 다른 시스템에서 어떻게 작동하는지에 대한 보장이 거의 없습니다.

uint_fast32_t크기가 다른 플랫폼으로 전환하는 경우 사용하는 모든 코드를 uint_fast32_t다시 테스트하고 유효성을 검사해야합니다. 모든 안정성 가정은 창 밖으로 나올 것입니다. 전체 시스템은 다르게 작동 할 것입니다.

코드를 작성할 때, 당신도 없을 수 있습니다 액세스 A와 uint_fast32_t크기가 32 비트되지 않습니다 시스템.

uint32_t 다르게 작동하지 않습니다 (각주 참조).

속도보다 정확성이 더 중요합니다. 따라서 조기 정확성은 조기 최적화보다 더 나은 계획입니다.

uint_fast32_t64 비트 이상인 시스템에 대한 코드를 작성 하는 경우 두 경우 모두 코드를 테스트하고 사용할 수 있습니다. 필요와 기회를 제외하고 그렇게하는 것은 나쁜 계획입니다.

마지막으로, uint_fast32_t임의의 시간 동안 저장하거나 인스턴스 수 uint32를 저장하면 단순히 캐시 크기 문제 및 메모리 대역폭으로 인해 속도가 느려질 수 있습니다 . 오늘날의 컴퓨터는 CPU 바운드보다 메모리 바운드가 훨씬 더 많으며 uint_fast32_t격리 상태에서는 더 빠를 수 있지만 메모리 오버 헤드를 고려한 후에는 그렇지 않습니다.


1 @chux가 주석에서 언급했듯이 unsigned이보다 크면 uint32_t산술 uint32_t은 일반적인 정수 승격을 거치고 그렇지 않은 경우 uint32_t. 이로 인해 버그가 발생할 수 있습니다. 완벽한 것은 없습니다.


15
"uint32_t는이를 지원하는 모든 플랫폼에서 동일한 속성을 갖도록 보장됩니다." 이 코너의 문제가 unsigned보다 넓은 uint32_t다음 uint32_t하나 개의 플랫폼에서 일반적인 정수 프로모션 통과는 다른 그것을하지 않습니다. 그러나 uint32_t이러한 정수 수학 문제는 크게 감소합니다.
chux-Monica 복원

2
@chux는 곱할 때 UB를 유발할 수있는 코너 케이스입니다. 프로모션은 부호있는 정수를 선호 하고 부호있는 정수 오버플로는 UB 이기 때문 입니다.
CodesInChaos

2
이 대답은 정확하지만 주요 세부 사항을 매우 경시합니다. 요컨대, uint32_t유형의 기계 표현에 대한 정확한 세부 사항이 중요한 반면 uint_fast32_t계산 속도가 가장 중요하고 (무) 부호 및 최소 범위가 중요하며 표현의 세부 사항이 필수적이지 않은 곳입니다. 또한 uint_least32_t(un) signedness와 최소 범위가 가장 중요하고, 속도보다 간결함이 더 중요하며, 정확한 표현이 필수는 아닙니다.
John Bollinger

@JohnBollinger 모두 훌륭하고 좋지만 둘 이상의 변형을 구현하는 실제 하드웨어에서 테스트하지 않으면 가변 크기 유형이 함정입니다. 그리고 사람들 uint32_t이 다른 유형보다 사용하는 이유 는 일반적으로 .NET에서 테스트 할 하드웨어 가 없기 때문 입니다 . ( int32_t적은 정도 는 동일 int하며 및 short).
Yakk-Adam Nevraumont

1
코너 케이스의 예 : Let unsigned short== uint32_tint== int48_t. 과 같은 것을 계산 (uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF하면 피연산자가로 승격되고 signed int정의되지 않은 동작 인 부호있는 정수 오버플로가 트리거됩니다. 이 질문을 참조하십시오.
Nayuki

32

왜 많은 사람들이 uint32_t대신 사용 uint32_fast_t합니까?

참고 : 잘못된 이름 uint32_fast_tuint_fast32_t.

uint32_t보다 엄격한 사양을 가지고 uint_fast32_t있으므로보다 일관된 기능을 제공합니다.


uint32_t 장점 :

  • 다양한 알고리즘이이 유형을 지정합니다. IMO-사용하는 가장 좋은 이유.
  • 정확한 너비와 범위가 알려져 있습니다.
  • 이 유형의 어레이는 낭비가 없습니다.
  • 오버플로가있는 부호없는 정수 수학이 더 예측 가능합니다.
  • 다른 언어의 32 비트 유형의 범위와 수학에서 더 가깝게 일치합니다.
  • 패딩되지 않았습니다.

uint32_t 단점 :

  • 항상 사용할 수있는 것은 아닙니다 (그러나 2018 년에는 드뭅니다).
    예 : 8 / 16 / 32- 비트 정수 (9 / 1018 / 부족 플랫폼 36 비트, 기타 ).
    예 : 2가 아닌 보수를 사용하는 플랫폼. 오래된 2200

uint_fast32_t 장점 :

  • 항상 사용 가능합니다.
    이렇게 하면 새 플랫폼과 이전 플랫폼 모두 항상 빠른 / 최소 유형을 사용할 수 있습니다.
  • 32 비트 범위를 지원하는 "가장 빠른" 유형.

uint_fast32_t 단점 :

  • 범위는 최소한으로 만 알려져 있습니다. 예를 들어 64 비트 유형일 수 있습니다.
  • 이 유형의 배열은 메모리에서 낭비 일 수 있습니다.
  • 모든 답변 (처음에는 내 것), 게시물 및 댓글에 잘못된 이름이 사용되었습니다 uint32_fast_t. 많은 사람들이이 유형을 필요로하지 않고 사용하는 것 같습니다. 우리는 올바른 이름도 사용하지 않았습니다!
  • 패딩 가능-(희귀).
  • 일부 경우에는 "가장 빠른"유형이 실제로 다른 유형일 수 있습니다. 따라서 uint_fast32_t1 차 근사치입니다.

결국 가장 좋은 것은 코딩 목표에 달려 있습니다. 매우 광범위한 이식성 또는 일부 틈새 성능 기능을위한 코딩이 아니라면 uint32_t.


이러한 유형을 사용할 때 또 다른 문제가 있습니다. int/unsigned

아마도 uint_fastN_t순위가 될 수 있습니다 unsigned. 이것은 지정되지 않았지만 특정하고 테스트 가능한 조건입니다.

따라서, uintN_t보다 많은 가능성 uint_fastN_t을 좁게 unsigned. 이것은 uintN_t수학 을 사용하는 코드가 uint_fastN_t이식성에 관한 것보다 정수 승진의 대상이 될 가능성이 더 높다는 것을 의미 합니다.

이 문제는 uint_fastN_t수학 연산을 선택할 때 이식성 이점이 있다는 것 입니다.


int32_t대신 에 대한 참고 사항 int_fast32_t: 희귀 한 컴퓨터에서는 INT_FAST32_MIN-2,147,483,648이 아닌 -2,147,483,647이 될 수 있습니다. 더 큰 요점 (u)intN_t은 유형이 엄격하게 지정되어 이식 가능한 코드로 이어진다는 것입니다.


2
32 비트 범위를 지원하는 가장 빠른 유형 => 정말? 이것은 RAM이 CPU 속도로 실행되는 시대의 유물이며, 요즘 PC에서 균형이 크게 바뀌었기 때문에 (1) 메모리에서 32 비트 정수를 가져 오는 것이 64 비트 정수를 가져 오는 것보다 두 배 빠르며 (2) 벡터화 된 명령어를 가져 오는 것보다 빠릅니다. 32 비트 정수에서는 64 비트 정수보다 두 배나 더 많이 처리됩니다. 여전히 정말 가장 빠른가요?
Matthieu M.

4
어떤 것에는 가장 빠르며 다른 것에는 느립니다. 배열과 제로 확장이 필요한 것을 고려할 때 "가장 빠른 정수 크기"에 대한 한 가지 대답은 없습니다. x86-64 System V ABI에서 uint32_fast_t64 비트 유형이므로 가끔 부호 확장을 저장하고 imul rax, [mem]64 비트 정수 또는 포인터와 함께 사용할 때 별도의 0 확장로드 명령 대신 허용 합니다. 그러나 그것은 (REX 모든 것을에 접두사를.) 당신이 두 배 캐시 풋 프린트 및 추가 코드 크기의 가격에 얻을 전부
피터 코르

1
또한 64 비트 분할은 대부분의 x86 CPU에서 32 비트 분할보다 훨씬 느리며 일부 (불도저 제품군, Atom 및 Silvermont와 같은)는 32 비트보다 64 비트 곱셈이 느립니다. 불도저 제품군도 64 비트가 더 느립니다. popcnt. 그리고이 유형은 다른 아키텍처에서 더 작기 때문에 32 비트 값에 대해서만 안전합니다.
Peter Cordes

2
모든 C 및 C ++ 응용 프로그램에 대한 가중 평균 uint32_fast_t으로 x86 에서 만드는 것은 끔찍한 선택이라고 생각합니다. 가 발생 빨라 좀처럼있는 운영 및 혜택은 대부분 소문자 다음과 같습니다의 차이 imul rax, [mem]@PeterCordes 언급이 경우는 매우 , 매우 작은 : 비 융합 도메인의 융합 도메인 제로의 단일 UOP. 가장 흥미로운 시나리오에서는 단일 주기도 추가하지 않습니다. 두 배 의 메모리 사용과 더 나쁜 벡터화와의 균형을 맞추면 자주 승리하는 것을보기가 어렵습니다.
BeeOnRope

2
@PeterCordes-흥미롭지 만 끔찍한 :). 그것은 만들 것 fast_t더 악화 int: 그것은 다른 플랫폼에서 서로 다른 크기를 가지고 않지만, 최적화 결정과 다른 파일에서 서로 다른 크기에 따라 크기가 다른 것뿐만 아니라! C 및 C ++의 크기가 너무 고정 : 실제적인 문제로서, 나는 심지어 전체 프로그램 최적화 작동하지 않을 수 있다고 생각 sizeof(uint32_fast_t)하거나 심지어 직접 항상 같은 값을 반환하는 결정 아무것도는 것, 그래서 매우 에 컴파일러 힘든 그러한 변화를 만드십시오.
BeeOnRope

25

왜 많은 사람들이 uint32_t대신 사용 uint32_fast_t합니까?

어리석은 대답 :

  • 표준 유형이 없으며 uint32_fast_t올바른 철자는 uint_fast32_t입니다.

실용적인 대답 :

  • 많은 사람들이 실제로 사용 uint32_t하거나 int32_t정확한 의미를 위해 산술 ( uint32_t) 또는 2의 보수 표현 ( int32_t) 을 부호없는 랩 어라운드로 정확히 32 비트를 사용 합니다. xxx_fast32_t유형이 크고 포장 배열과 구조에 바이너리 파일, 사용에 가게에 따라서 적절하지, 또는 네트워크를 통해 보낼 수 있습니다. 또한 더 빠르지 않을 수도 있습니다.

실용적인 대답 :

  • 많은 사람들 uint_fast32_t이 댓글과 답변에서 보여준 것처럼 에 대해 알지 못합니다 (또는 단순히 신경 쓰지 않습니다). unsigned int많은 현재 아키텍처에는 여전히 16 비트 int가 있고 일부 희귀 박물관 샘플에는 32보다 작은 다른 이상한 int 크기.

UX 답변 :

  • 아마도보다 빠른 있지만 uint32_t, uint_fast32_t느린 사용하는 것입니다 : 그것은 특히 C 문서에서 철자와 의미를 찾는를 차지, 입력 오래 걸립니다 ;-)

우아함이 중요합니다 (분명히 의견 기반) :

  • uint32_t많은 프로그래머가 자신의 유형 u32이나 uint32유형 을 정의하는 것을 선호 할만큼 나쁘게 보입니다. 이 관점에서 uint_fast32_t보면 수리 할 수 ​​없을 정도로 서투른 것처럼 보입니다. 친구들 uint_least32_t과 함께 벤치에 앉아있는 것은 놀라운 일이 아닙니다 .

UX의 경우 +1. 그것은 더 이상이다 std::reference_wrapper같아요,하지만 때로는 표준위원회는 정말 ... 그것은 표준화 유형이 사용하고자하는 경우 궁금해
마티유 M.

7

한 가지 이유는 unsigned int특별한 typedef가 필요하지 않거나 무언가를 포함 할 필요없이 이미 "가장 빠르다"는 것입니다. 따라서 빠르게 필요하면 기본 int또는 unsigned int유형을 사용하십시오 .
표준은 그것이 가장 빠르다는 것을 명시 적으로 보장하지는 않지만, 3.9.1에서 "Plain int는 실행 환경의 아키텍처에서 제안한 자연적인 크기를 갖는다" 라고 명시함으로써 간접적으로 그렇게합니다 . 즉, (또는 서명되지 않은 대응 물) 프로세서가 가장 편한 것입니다.int

물론 이제 어떤 크기 unsigned int가 될지 모릅니다. 당신은 단지 그것을 알고 적어도 한 큰 short(그리고 나는 그 기억 보인다 short지금은 표준에서 그것을 찾을 수 있지만, 적어도 16 비트해야합니다!). 일반적으로 단순히 4 바이트 일 뿐이지 만 이론적으로는 더 클 수도 있고 극단적 인 경우에는 더 작을 수도 있습니다 ( 개인적으로 1980 년대 8 비트 컴퓨터에서도 이런 아키텍처를 접한 적이 없지만). .. 아마도 내가 치매로 고통받는 것을 아는 일부 마이크로 컨트롤러int 는 당시 16 비트였다.)

C ++ 표준은 <cstdint>유형이 무엇인지 또는 무엇을 보장하는지 지정하지 않고 "C에서와 동일"을 언급 할뿐입니다.

uint32_t, C 표준에 따라, 보장 정확히 32 비트를 얻을. 다르지 않고 그 이하도없고 패딩 비트도 없습니다. 때로는 이것이 정확히 필요한 것이므로 매우 가치가 있습니다.

uint_least32_t크기가 무엇이든간에 32 비트보다 작을 수는 없지만 더 클 수 있음을 보장합니다. 때때로, 그러나 정확한 witdh 또는 "상관 안 함"보다 훨씬 드물게 이것이 당신이 원하는 것입니다.

마지막으로 uint_fast32_t의도 문서화 목적을 제외하고는 내 의견 으로 는 다소 불필요합니다. C 표준은 "보통 가장 빠른 정수 유형을 지정" ( "보통"이라는 단어에 유의)을 명시하고 모든 목적에 대해 가장 빠를 필요는 없음을 명시 적으로 언급합니다. 즉, uint_fast32_t단지와 거의 같다 uint_least32_t없다, 이는 일반적으로 가장 빠른 너무 만 더 주어진 보장한다 (하지만 보증 어느 쪽이든).

대부분의 경우 정확한 크기에 신경 쓰지 않거나 정확히 32 (또는 64, 때로는 16) 비트를 원하기 때문에 "관심 없음" unsigned int유형이 가장 빠르기 uint_fast32_t때문에 그렇지 않은 이유 가 설명됩니다. 자주 사용되는.


3
int8 비트 프로세서에서 16 비트 를 기억하지 못한다는 것이 놀랍습니다. 그 당시에는 더 큰 것을 사용했던 것을 기억할 수 없습니다. 메모리가 제공되는 경우 세그먼트 화 된 x86 아키텍처 용 컴파일러도 16 비트 int를 사용했습니다 .
Mark Ransom

@MarkRansom : 와우, 맞아요. 나는 int68000에서 32 비트 라고 확신했다 (예를 들어 생각했다). 그것은 ... 아니 었
데이먼

int16 비트의 최소 너비 (이것이 C가 정수 승격 규칙을 갖는 이유)로 과거에 가장 빠른 유형 이었으나 오늘날 64 비트 아키텍처에서는 더 이상 사실이 아닙니다. 예를 들어 8 바이트 정수는 x86_64 비트에서 4 바이트 정수보다 빠릅니다. 4 바이트 정수를 사용하는 경우 컴파일러는 다른 8 바이트 값과 비교하기 전에 4 바이트 값을 8 바이트 값으로 확장하는 추가 명령어를 삽입해야하기 때문입니다.
StaceyGirl 2017-10-27

"unsigned int"는 x64에서 반드시 가장 빠른 것은 아닙니다. 이상한 일이 일어났습니다.
여호수아

또 다른 일반적인 경우는 long역사적 이유로는 32 비트 여야하고 int이제는보다 더 넓지 않아야 long하므로 int64 비트가 더 빠를 때도 32 비트를 유지해야 할 수 있습니다.
Davislor

6

나는 범위에 uint32_t사용되는 증거를 보지 못했습니다 . 대신, 그 대부분의 시간을 내가 한 볼 을 사용, 그것은 보장 빙 둘러 시프트 의미와 함께, 다양한 알고리즘의 데이터 정확히 4 옥텟을 유지하는 것입니다!uint32_t

uint32_t대신 사용할 다른 이유도 있습니다 uint_fast32_t. 종종 안정적인 ABI를 제공하기 때문입니다. 또한 메모리 사용량을 정확하게 알 수 있습니다. 속도 이득에서 일한다 무엇 이건이 매우 오프셋은 uint_fast32_t, 때마다 그 유형의 구별이 될 것입니다 uint32_t.

값이 65536 미만이면 이미 편리한 유형이 있으며 호출됩니다 unsigned int( unsigned short최소한 해당 범위도 있어야하지만 unsigned int기본 단어 크기 임). 값이 4294967296 미만이면라는 또 다른 유형이 unsigned long있습니다.


그리고 마지막으로 uint_fast32_t입력하는 것이 귀찮고 길고 잘못 입력하기 쉽기 때문에 사람들은 사용하지 않습니다 .


@ikegami : 당신은 short편집으로 내 의도를 변경했습니다 . int아마도 그것과 구별 될 때 빠른 것입니다 short.
Antti Haapala

1
그럼 마지막 문장은 완전히 틀렸어요. unsigned int대신 사용해야한다고 주장하는 uint16_fast_t것은 컴파일러보다 더 잘 알고 있다고 주장한다는 것을 의미합니다.
ikegami

또한 텍스트의 의도를 변경하여 죄송합니다. 내 의도가 아니었다.
ikegami

unsigned long플랫폼에 64 비트 long가 있고 숫자 만 필요하면 좋은 선택이 아닙니다 <2^32.
Ruslan

1
@ikegami : "unsigned int"유형은 승격 된 경우에도 항상 unsigned 유형으로 작동합니다. 이와 관련하여 uint16_tuint_fast16_t. uint_fast16_t주소가 사용되지 않는 객체에 대해 범위가 일관 될 필요가 없도록 일반 정수 유형보다 더 느슨하게 지정 되면 내부적으로 32 비트 산술을 수행하지만 16 비트 데이터 버스가있는 플랫폼에서 일부 성능 이점을 제공 할 수 있습니다. . 그러나 표준은 그러한 유연성을 허용하지 않습니다.
supercat 2017-10-26

5

몇 가지 이유.

  1. 많은 사람들은 '빠른'유형이 존재한다는 것을 모릅니다.
  2. 입력하는 것이 더 장황합니다.
  3. 유형의 실제 크기를 모르면 프로그램 동작에 대해 추론하기가 더 어렵습니다.
  4. 표준은 실제로 가장 빠르게 고정되지 않으며 실제로 어떤 유형이 실제로 가장 빠른지도 컨텍스트에 따라 달라질 수 있습니다.
  5. 나는 플랫폼 개발자가 플랫폼을 정의 할 때 이러한 유형의 크기를 고려한다는 증거를 보지 못했습니다. 예를 들어 x86-64 Linux에서 "fast"유형은 x86-64가 32 비트 값에 대한 빠른 작업을위한 하드웨어 지원을 제공하더라도 모두 64 비트입니다.

요약하면 "빠른"유형은 쓸모없는 쓰레기입니다. 특정 응용 프로그램에 대해 어떤 유형이 가장 빠른지 파악해야하는 경우 컴파일러에서 코드를 벤치마킹해야합니다.


과거에는 32 비트 및 / 또는 64 비트 메모리 액세스 명령이 있지만 8 비트 및 16 비트가 아닌 프로세서가있었습니다. 따라서 int_fast {8,16} _t는 20 년 이상 전에는 완전히 어리석지 않았을 것입니다. AFAIK의 마지막 주류 프로세서는 원래 DEC Alpha 21064였습니다 (2 세대 21164가 개선되었습니다). 아마도 여전히 임베디드 DSP 또는 워드 액세스 만 수행하는 모든 것이있을 수 있지만 일반적으로 이식성은 그런 것들에 대해 큰 관심사가 아니기 때문에 왜화물 컬트를 빨리 했는지 모르겠습니다 . 그리고 수작업으로 제작 된 Cray "모든 것은 64 비트"기계였습니다.
user1998586

1
카테고리 1b : 많은 사람들이 '빠른'유형이 존재하는 것을 신경 쓰지 않습니다. 그게 내 카테고리입니다.
gnasher729

카테고리 6 : 많은 사람들이 '빠른'유형이 가장 빠르다고 믿지 않습니다. 나는 그 범주에 속합니다.
선명

5

정확성과 코딩의 용이성의 관점 에서, 위의 많은 사용자가 지적했듯이 더 정확하게 정의 된 크기와 산술 의미로 인해 특히 uint32_t많은 이점 uint_fast32_t이 있습니다.

아마도 놓친 것은 하나의 장점더 빠를uint_fast32_t 수 있고 의미있는 방식으로 결코 구체화되지 않았다는 것입니다. 64 비트 시대를 장악 한 대부분의 64 비트 프로세서 (대부분 x86-64 및 Aarch64)는 32 비트 아키텍처에서 발전 했으며 64 비트 모드에서도 빠른 32 비트 기본 작업을 수행합니다. 따라서 해당 플랫폼 에서와 동일 합니다.uint_fast32_tuint32_t

POWER, MIPS64, SPARC와 같은 "또한 실행되는"플랫폼 중 일부가 64 비트 ALU 작업 만 제공하더라도 대부분 의 흥미로운 32 비트 작업은 64 비트 레지스터에서 잘 수행 할 수 있습니다. 하위 32 비트는 원하는 결과를 얻을 수 있습니다 (그리고 모든 주류 플랫폼은 적어도 32 비트를로드 / 저장할 수 있습니다). 왼쪽 시프트가 주요 문제이지만 컴파일러의 값 / 범위 추적 최적화를 통해 많은 경우에 최적화 될 수 있습니다.

나는 때때로 약간 느린 왼쪽 시프트 또는 32x32-> 64 곱셈이 그러한 값에 대한 메모리 사용의 두 배 를 능가 할 것이라고 의심합니다 .

마지막으로, 트레이드 오프는 대체로 "메모리 사용 및 벡터화 잠재력"(에 찬성 uint32_t) 대 명령 수 / 속도 (에 찬성 uint_fast32_t) 로 특징 지워졌지만 나에게 분명하지 않습니다. 예, 일부 플랫폼에서는 일부 32 비트 작업에 대한 추가 지침이 필요 하지만 다음과 같은 이유로 몇 가지 지침 도 저장 해야합니다.

  • 더 작은 유형을 사용하면 컴파일러가 하나의 64 비트 연산을 사용하여 두 개의 32 비트 연산을 수행함으로써 인접한 연산을 영리하게 결합 할 수 있습니다. 이러한 유형의 "가난한 사람의 벡터화"의 예는 드물지 않습니다. 예를 들어 상수 struct two32{ uint32_t a, b; }raxlike 로 생성 하는 것은 단일 two32{1, 2} 로 최적화 할 수 있지만mov rax, 0x20001 64 비트 버전은 두 가지 명령이 필요합니다. 원칙적으로 이것은 인접한 산술 연산 (동일한 연산, 다른 피연산자)에도 가능해야하지만 실제로는 보지 못했습니다.
  • "메모리 사용"이 낮 으면 메모리 또는 캐시 풋 프린트가 문제가되지 않더라도 이러한 유형의 구조 또는 배열이 복사되기 때문에 레지스터 복사 당 비용이 두 배가됩니다.
  • 더 작은 데이터 유형은 종종 데이터 구조 데이터를 레지스터에 효율적으로 압축하는 SysV ABI와 같은 더 나은 최신 호출 규칙을 활용합니다. 예를 들어 registers에서 최대 16 바이트 구조를 반환 할 수 있습니다 rdx:rax. 4 개의 uint32_t값 (상수에서 초기화 됨)을 가진 구조를 반환하는 함수의 경우 다음과 같이 변환됩니다.

    ret_constant32():
        movabs  rax, 8589934593
        movabs  rdx, 17179869187
        ret
    

    64 비트 uint_fast32_t가 4 개인 동일한 구조 는 동일한 작업을 수행하기 위해 레지스터 이동과 메모리에 4 개의 저장소 가 필요합니다 (호출자는 반환 후 메모리에서 값을 다시 읽어야합니다).

    ret_constant64():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 1
        mov     QWORD PTR [rdi+8], 2
        mov     QWORD PTR [rdi+16], 3
        mov     QWORD PTR [rdi+24], 4
        ret
    

    마찬가지로 구조 인수를 전달할 때 32 비트 값은 매개 변수에 사용할 수있는 레지스터에 약 2 배 조밀하게 압축되므로 레지스터 인수가 부족하여 스택 1 로 넘쳐 야 할 가능성이 줄어 듭니다 .

  • uint_fast32_t"속도가 중요한"장소에서 사용 하기로 선택하더라도 고정 크기 유형이 필요한 장소도 종종 있습니다. 예를 들어 외부 출력, 외부 입력, ABI의 일부로, 특정 레이아웃이 필요한 구조의 일부로 또는 uint32_t메모리 공간을 절약하기 위해 값의 대규모 집계에 현명하게 사용 하기 때문에 값을 전달할 때 . 사용자 uint_fast32_t와``uint32_t` 유형이 인터페이스해야하는 곳에서 (개발 복잡성 외에도) 불필요한 부호 확장 또는 기타 크기 불일치 관련 코드를 찾을 수 있습니다. 컴파일러는 많은 경우에 이것을 최적화하는 데 OK 작업을 수행하지만 다른 크기의 유형을 혼합 할 때 최적화 된 출력에서 ​​이것을 보는 것은 여전히 ​​드문 일이 아닙니다.

위의 일부 예제와 godbolt에서 더 많은 것을 재생할 수 있습니다 .


1 명확하게 말하면, 구조를 레지스터에 단단히 묶는 규칙이 작은 값에 대해 항상 확실한 승리는 아닙니다. 이는 더 작은 값을 사용하기 전에 "추출"해야 할 수도 있음을 의미합니다. 예를 들어 두 구조체 멤버의 합을 함께 반환하는 간단한 함수 mov rax, rdi; shr rax, 32; add edi, eax는 64 비트 버전의 경우 각 인수가 자체 레지스터를 가져오고 하나의 add또는 lea. 그래도 "통과하는 동안 구조를 단단히 묶는"디자인이 전체적으로 합리적이라는 점을 받아 들인다면 값이 작을수록이 기능을 더 많이 활용할 수 있습니다.


x86-64 Linux의 glibc는 64 비트 uint_fast32_t를 사용하는데 이는 IMO의 실수입니다. (분명히 Windows uint_fast32_t는 Windows 에서 32 비트 유형입니다.) x86-64 Linux에서 64 비트이기 때문에 누구에게도 사용을 권장하지 않습니다 uint_fast32_t. 중요한 플랫폼 중 하나에서 전체 속도 또는 코드 크기가 아닌 배열 인덱스로 사용).
Peter Cordes

2
아 맞다, 위의 SysV ABI에 대한 귀하의 의견을 읽었지만 나중에 지적했듯이 아마도 그것을 결정한 다른 그룹 / 문서 일 수 있습니다.하지만 일단 그런 일이 발생하면 거의 결정된 것 같습니다. 컴파일러가 더 작은 유형을 더 잘 최적화 할 수있는 경우가 여전히 있기 때문에 순수한 사이클 수 / 명령 수가 메모리 풋 프린트 효과와 벡터화를 무시하더라도 더 큰 유형을 선호한다는 것은 의문의 여지가 있습니다. 위에 몇 가지 예를 추가했습니다. @PeterCordes
BeeOnRope

같은 레지스터에 여러 개의 구조체 멤버를 포장 시스템 V는 비용이 돌아 오는 경우 상당히 자주 지시를 pair<int,bool>하거나 pair<int,int>. 두 멤버가 모두 컴파일 타임 상수가 아닌 경우 일반적으로 OR 이상이 있으며 호출자는 반환 값의 압축을 풀어야합니다. ( bugs.llvm.org/show_bug.cgi?id=34840 LLVM 전용 전달 함수 반환 값을 최적화하여, 전체 촬영 32 비트 INT 대해야 rax그래서 bool에서 분리 dl하는 대신에 64 비트 정수 필요의 testit.)
Peter Cordes

1
일반적으로 컴파일러는 함수를 분할하지 않는다고 생각합니다. 빠른 경로를 별도의 함수로 제거하는 것은 유용한 소스 수준 최적화입니다 (특히 인라인 할 수있는 헤더에서). 입력의 90 %가 "아무것도하지 않는 경우"라면 매우 좋을 수 있습니다. 호출자의 루프에서 필터링하는 것이 큰 승리입니다. IIRC, Linux는 __attribute__((noinline))gcc가 오류 처리 기능 을 인라인하지 않고 많은 호출자가 있고 자체적으로 인라인되지 않는 일부 중요한 커널 함수의 빠른 경로에 많은 push rbx/ .../ pop rbx/ ...를 배치하도록 정확히 사용 합니다 .
Peter Cordes

1
Java에서는 인라인이 추가 최적화 (특히 C ++와 달리 널리 퍼져있는 비 가상화)에 매우 중요하기 때문에 매우 중요하므로 빠른 경로를 분할하는 데 종종 비용을 지불하고 "바이트 코드 최적화"가 실제로 문제입니다 (임에도 불구하고 인라인 결정은 인라인 머신 코드 크기가 아닌 바이트 코드 크기를 기반으로하기 때문에 JIT가 최종 컴파일을 수행하기 때문에 의미가 없다는 기존의 통념) 바이트 코드 카운트 다운을 얻는 것입니다 (상관 관계는 크기에 따라 달라질 수 있음).
BeeOnRope

4

실용적인 목적으로 uint_fast32_t완전히 쓸모가 없습니다. 가장 널리 퍼진 플랫폼 (x86_64)에서 잘못 정의되어 있으며, 매우 낮은 품질의 컴파일러가없는 한 어떠한 이점도 제공하지 않습니다. 개념적으로, 데이터 구조 / 배열에서 "빠른"유형을 사용하는 것은 의미가 없습니다. 유형에서 더 효율적으로 작업 할 수있는 비용 절감은 크기를 늘리는 데 드는 비용 (캐시 누락 등)에 의해 왜소 해집니다. 작업 데이터 세트. 개별 지역 변수 (루프 카운터, 임시 등)의 경우 장난감이 아닌 컴파일러는 일반적으로 생성 된 코드에서 더 큰 유형으로 작업 할 수 있습니다. 더 효율적일 경우, 정확성을 위해 필요한 경우에만 공칭 크기로자를 수 있습니다. 서명 된 유형은 필요하지 않습니다.)

이론적으로 유용한 한 가지 변형은 uint_least32_t32 비트 값을 저장할 수 있어야하지만 정확한 크기의 32 비트 유형이없는 컴퓨터로 이식하려는 경우에 사용됩니다. 그러나 실제로는 걱정할 필요가 없습니다.


4

내 이해에 따르면, int처음에는 크기가 16 비트 이상이어야한다는 추가 보장이있는 "기본"정수 유형이어야했습니다. 그 당시에는 "합리적인"크기로 간주되었습니다.

32 비트 플랫폼이 일반화되었을 때 "합리적인"크기가 32 비트로 변경되었다고 말할 수 있습니다.

  • 최신 Windows는 int모든 플랫폼에서 32 비트 를 사용 합니다.
  • POSIX int는 최소 32 비트를 보장합니다 .
  • C #, Java에는 int정확히 32 비트가 보장되는 유형 이 있습니다.

그러나 64 비트 플랫폼이 표준이되었을 때 아무도 int다음과 같은 이유로 64 비트 정수로 확장되지 않았습니다 .

  • 이식성 : 많은 코드 int가 32 비트 크기 에 달려 있습니다.
  • 메모리 소비 : int대부분의 경우 사용중인 숫자가 20 억보다 훨씬 적기 때문에 모든 경우에 대해 메모리 사용량을 두 배로 늘리는 것은 대부분의 경우 비합리적 일 수 있습니다.

이제, 당신은 왜 선호 uint32_tuint_fast32_t? 같은 이유로 C #과 Java는 항상 고정 크기 정수를 사용합니다. 프로그래머는 다른 유형의 가능한 크기에 대해 생각하는 코드를 작성하지 않고 하나의 플랫폼에 대해 작성하고 해당 플랫폼에서 코드를 테스트합니다. 대부분의 코드는 특정 크기의 데이터 유형에 암시 적으로 의존합니다. 그리고 이것이 uint32_t대부분의 경우에 더 나은 선택 인 이유 입니다. 동작에 대한 모호성을 허용하지 않습니다.

또한 uint_fast32_t크기가 32 비트 이상인 플랫폼에서 실제로 가장 빠른 유형입니까? 별로. Windows에서 x86_64 용 GCC의 다음 코드 컴파일러를 고려하십시오.

extern uint64_t get(void);

uint64_t sum(uint64_t value)
{
    return value + get();
}

생성 된 어셈블리는 다음과 같습니다.

push   %rbx
sub    $0x20,%rsp
mov    %rcx,%rbx
callq  d <sum+0xd>
add    %rbx,%rax
add    $0x20,%rsp
pop    %rbx
retq

이제 get()의 반환 값을 uint_fast32_t(Windows x86_64에서 4 바이트)로 변경하면 다음과 같은 결과가 나타납니다.

push   %rbx
sub    $0x20,%rsp
mov    %rcx,%rbx
callq  d <sum+0xd>
mov    %eax,%eax        ; <-- additional instruction
add    %rbx,%rax
add    $0x20,%rsp
pop    %rbx
retq

생성 된 코드는 mov %eax,%eax32 비트 값을 64 비트 값으로 확장하는 함수 호출 후 추가 명령을 제외하고는 거의 동일 합니다.

32 비트 값만 사용하는 경우에는 이러한 문제가 없지만 size_t변수 (배열 크기)와 함께 사용 하고 x86_64에서 64 비트 값을 사용하게 될 것입니다 . Linux uint_fast32_t에서는 8 바이트이므로 상황이 다릅니다.

많은 프로그래머가 int작은 값을 반환해야 할 때 사용 합니다 ([-32,32] 범위에 있다고 가정 해 봅시다). 이는 int플랫폼 네이티브 정수 크기 인 경우 완벽하게 작동 하지만 64 비트 플랫폼이 아니기 때문에 플랫폼 네이티브 형식과 일치하는 다른 형식이 더 나은 선택입니다 (작은 크기의 다른 정수와 자주 사용되지 않는 한).

기본적으로 표준이 말하는 것과 상관없이 uint_fast32_t어쨌든 일부 구현에서는 깨집니다. 일부 위치에서 생성 된 추가 명령어에 관심이 있다면 고유 한 "네이티브"정수 유형을 정의해야합니다. 또는 size_t일반적으로 native크기 와 일치하므로이 목적으로 사용할 수 있습니다 (8086과 같은 오래되고 모호한 플랫폼은 포함하지 않고 Windows, Linux 등을 실행할 수있는 플랫폼 만 포함).


int기본 정수 유형이어야하는 또 다른 기호 는 "정수 승격 규칙"입니다. 대부분의 CPU는 네이티브에서만 작업을 수행 할 수 있으므로 32 비트 CPU는 일반적으로 32 비트 더하기, 빼기 등 만 수행 할 수 있습니다 (인텔 CPU는 여기에서 예외 임). 다른 크기의 정수 유형은로드 및 저장 명령어를 통해서만 지원됩니다. 예를 들어, 8 비트 값은 적절한 "부호화 된 8 비트로드"또는 "부호없는 8 비트로드"명령어를 사용하여로드해야하며로드 후 값을 32 비트로 확장합니다. 정수 승격 규칙이 없으면 C 컴파일러는 원시 유형보다 작은 유형을 사용하는 표현식에 대해 약간 더 많은 코드를 추가해야합니다. 불행히도 64 비트 아키텍처에서는 더 이상 컴파일러가 일부 경우 (위에 표시된대로) 추가 명령을 내 보내야하기 때문에 더 이상 적용되지 않습니다.


2
"아무도 64 비트 정수로 확장 된 int가 없습니다."와 "불행히도 64 비트 아키텍처에서는 더 이상 유지되지 않습니다 . "라는 생각은 매우 좋은 점 입니다. "가장 빠르다"고 어셈블리 코드를 비교하는 것에 대해 공정하게 말하면 :이 경우 두 번째 코드 스 니펫은 추가 명령으로 더 느리지 만 코드 길이와 속도는 때때로 그렇게 잘 연관되지 않습니다. 더 강력한 비교는 실행 시간을보고 할 것입니다. 그러나 그렇게하기는 쉽지 않습니다.
chux-Monica 복원

두 번째 코드의 느린 속도를 측정하는 것은 쉽지 않을 것입니다. 인텔 CPU는 정말 좋은 일을 할 수 있지만 코드가 길면 캐시 오염도 커집니다. 가끔씩 단일 명령어는 아프지 않지만 uint_fast32_t의 유용성은 모호해집니다.
StaceyGirl

나는 uint_fast32_t매우 선택적인 상황을 제외한 모든 상황에서의 유용성 이 모호 해지는 것에 대해 강력히 동의합니다 . 그 이유 uint_fastN_t는 " unsigned64 비트로 사용하지 말자 . 새 플랫폼에서는 가장 빠르다. 너무 많은 코드가 깨질 수 있기 때문이다"라고 생각하지만 "나는 여전히 N 비트 유형 이상의 빠른 속도를 원한다" . " 내가 할 수 있다면 다시 UV를하겠습니다.
chux-Monica 복원

대부분의 64 비트 아키텍처는 32 비트 정수에서 쉽게 작동 할 수 있습니다. DEC Alpha (PowerPC64 또는 MIPS64와 같은 기존 32 비트 ISA의 확장이 아닌 새로운 분기 64 비트 아키텍처 임)조차도 32 비트 및 64 비트로드 / 스토어를 가졌습니다. (하지만 바이트 또는 16 비트로드 / 저장이 아닙니다!). 대부분의 명령어는 64 비트 전용 이었지만 결과를 32 비트로 자르는 32 비트 추가 / 구독 및 곱하기에 대한 기본 HW 지원이있었습니다. ( alasir.com/articles/alpha_history/press/alpha_intro.html ) 따라서 int64 비트 를 만들어도 속도가 거의 향상되지 않으며 일반적으로 캐시 공간으로 인한 속도 손실이 발생합니다.
Peter Cordes

또한 int64 비트 를 만든 경우 uint32_t고정 너비 typedef에는 __attribute__또는 다른 해킹이나 int. (또는 short,하지만 당신은에 대해 같은 문제가 uint16_t있습니다.) 아무도 그것을 원하지 않습니다. 32 비트는 16 비트와는 달리 거의 모든 것에 대해 충분히 넓습니다. 필요한 경우 32 비트 정수를 사용하는 것은 64 비트 시스템에서 의미있는 방식으로 "비효율적"이지 않습니다.
Peter Cordes

2

대부분의 경우 알고리즘이 데이터 배열에서 작동 할 때 성능을 개선하는 가장 좋은 방법은 캐시 누락 수를 최소화하는 것입니다. 각 요소가 작을수록 캐시에 더 많이 들어갈 수 있습니다. 이것이 64 비트 컴퓨터에서 32 비트 포인터를 사용하기 위해 많은 코드가 작성되는 이유입니다. 4GiB에 가까운 데이터는 필요하지 않지만 모든 포인터와 오프셋을 만드는 데 드는 비용은 4 바이트가 아닌 8 바이트가 필요합니다. 상당 할 것입니다.

정확히 32 비트가 필요하도록 지정된 일부 ABI 및 프로토콜도 있습니다 (예 : IPv4 주소). 이것이 uint32_t실제로 의미하는 바입니다 . CPU에서 효율적인지 여부에 관계없이 정확히 32 비트를 사용 합니다 . 이전에는 long또는 으로 선언되어 unsigned long64 비트 전환 중에 많은 문제가 발생했습니다. 최소 2³²-1까지의 숫자를 보유하는 부호없는 유형이 필요한 경우 unsigned long첫 번째 C 표준이 나온 이후로 정의되었습니다 . 그러나 실제로는 a long가 포인터 나 파일 오프셋 또는 타임 스탬프를 보유 할 수 있다고 가정 한 오래된 코드가 충분했고, 이전 코드는 정확히 32 비트 너비라고 가정했기 때문에 컴파일러가 너무 많은 것을 깨뜨리지 않고 long는 동일 하게 만들 수는 없습니다 int_fast32_t.

이론적으로는 프로그램이를 사용하는 것이 더 미래 지향적 이며 계산을 위해 요소를 변수에 uint_least32_t로드 할 수도 있습니다. 유형 이 전혀없는 구현 은 표준을 공식적으로 준수한다고 선언 할 수도 있습니다! (그냥 기존의 많은 프로그램을 컴파일 할 수 없습니다.) 실제로, 어떤 아키텍처는 더 이상 없다 곳 , 그리고 , 같은, 어떤 장점이없는 현재 의 성능은 . 그렇다면 왜 너무 복잡할까요?uint_least32_tuint_fast32_tuint32_tintuint32_tuint_least32_tuint_fast32_t

그러나 32_t우리가 이미 가지고 있었을 때 모든 유형이 존재해야하는 이유를 살펴보면 long이러한 가정이 이전에 우리의 얼굴에 날아 오른 것을 알 수 있습니다. 코드가 언젠가는 정확한 너비의 32 비트 계산이 네이티브 단어 크기보다 느린 시스템에서 실행될 수 있으며, uint_least32_t저장 및 uint_fast32_t계산에 종교적으로 사용 하는 것이 더 나았을 것 입니다. 또는 당신이 그것에 도착했을 때 그 다리를 건너고 단순한 것을 원한다면 unsigned long.


그러나 intILP64와 같이 32 비트가 아닌 아키텍처가 있습니다 . 그들이 흔한 것은 아닙니다.
안티 Haapala

ILP64가 현재 시제로 존재하지 않는다고 생각합니까? 몇몇 웹 페이지는 "Cray"가 그것을 사용한다고 주장하는데, 모두 1997 년의 동일한 Unix.org 페이지를 인용했지만, 90 년대 중반의 UNICOS는 실제로 이상한 일을했고 오늘날의 Crays는 Intel 하드웨어를 사용합니다. 같은 페이지에서는 ETA 슈퍼 컴퓨터가 ILP64를 사용했다고 주장하지만 오래 전에 폐업했습니다. Wikipedia는 HAL의 Solaris에서 SPARC64 로의 포팅이 ILP64를 사용했다고 주장하지만 그들은 또한 수년 동안 사업을 중단했습니다. CppReference에 따르면 ILP64는 초기 64 비트 Unices에서만 사용되었습니다. 따라서 매우 난해한 레트로 컴퓨팅에만 관련이 있습니다.
Davislor 2017-10-27

현재 인텔 수학 커널 라이브러리의 "ILP64 인터페이스"를 사용하는 경우 int너비는 32 비트가됩니다. 유형 MKL_INT이 변경됩니다.
Davislor 2017-10-27

1

직접적인 대답을하려면 내가 진짜 이유 생각 uint32_t이상 사용 uint_fast32_t또는 uint_least32_t일부 유형의 구조체를 만들 경우, 그들 중 일부는 다음과 같습니다 인해 짧은 것, 입력하기 쉽다는 간단하고,을, 훨씬 좋네요 읽을 uint_fast32_t또는 이와 유사한, 그것은 멋지게을 정렬하는 것이 어렵 int거나 bool아주 짧은 C에서 다른 유형 (: 포인트 경우 또는 charcharacter). 물론 하드 데이터로 이것을 백업 할 수는 없지만 다른 답변은 이유 만 추측 할 수 있습니다.

선호하는 기술적 인 이유에 관해서는 uint32_t, 정확히 32 비트 unsigned int 절대적으로 필요한 경우이 유형이 유일한 표준화 된 선택입니다. 거의 모든 다른 경우에는 다른 변형이 기술적으로 더 바람직합니다. 특히 uint_fast32_t속도 uint_least32_t에 관심이 있고 저장 공간에 관심이 있다면 더욱 그렇습니다. uint32_t이 두 경우 모두 사용 하면 유형이 존재하지 않아도되므로 컴파일 할 수 없습니다.

실제로 uint32_t및 관련 유형은 매우 드문 (현재) DSP 또는 농담 구현을 제외한 모든 현재 플랫폼에 존재하므로 정확한 유형을 사용하는 데 실제 위험이 거의 없습니다. 마찬가지로 고정 너비 유형으로 속도 불이익을받을 수 있지만 (현대 CPU에서는) 더 이상 손상되지 않습니다.

그렇기 때문에 프로그래머의 게으름 때문에 더 짧은 유형이 대부분의 경우 단순히 승리한다고 생각합니다.

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