Tại sao uint32_t lại được ưu tiên hơn là uint_fast32_t?


81

Nó có vẻ uint32_tlà phổ biến hơn nhiều so với uint_fast32_t(tôi nhận ra đây là bằng chứng giai thoại). Tuy nhiên, điều đó có vẻ phản trực giác đối với tôi.

Hầu như luôn luôn khi tôi thấy việc sử dụng triển khai uint32_t, tất cả những gì nó thực sự muốn là một số nguyên có thể chứa các giá trị lên đến 4,294,967,295 (thường là một giới hạn thấp hơn nhiều trong khoảng từ 65,535 đến 4,294,967,295).

Nó có vẻ lạ để sử dụng sau đó uint32_t, như là 'chính xác 32 bit' đảm bảo là không cần thiết, và 'nhanh nhất có sẵn> = 32 bit' bảo đảm uint_fast32_tdường như được chính xác ý tưởng đúng. Hơn nữa, trong khi nó thường được triển khai, uint32_tkhông thực sự được đảm bảo là tồn tại.

Tại sao, sau đó, sẽ uint32_tđược ưa thích? Nó chỉ đơn giản là được biết đến nhiều hơn hay có những lợi thế kỹ thuật hơn cái khác?


24
Câu trả lời đơn giản, có lẽ họ cần một số nguyên có chính xác 32 bit?
Stargateur

7
Đầu tiên tôi đã nghe nói đến uint32_fast_t, mà nếu tôi hiểu đúng, thì ít nhất là 32 bit (nghĩa là nó có thể nhiều hơn? Nghe có vẻ gây hiểu lầm cho tôi). Tôi hiện đang sử dụng uint32_tvà các bạn trong dự án của mình vì tôi đang đóng gói dữ liệu này và gửi qua mạng, đồng thời tôi muốn người gửi và người nhận biết chính xác kích thước của các trường. Nghe có vẻ như đây có thể không phải là giải pháp mạnh mẽ nhất vì một nền tảng có thể không triển khai uint32_t, nhưng tất cả những gì của tôi dường như đều làm nên tôi ổn với những gì mình đang làm.
yano

5
@yano: Đối với mạng, bạn cũng nên quan tâm đến thứ tự byte / độ bền - uint32_tkhông cung cấp cho bạn điều đó (và thật đáng tiếc là không có uint32_t_beuint32_t_le, điều này sẽ phù hợp hơn cho hầu hết mọi trường hợp có thể mà uint32_thiện tại là lựa chọn tốt nhất).
Brendan

3
@Brendan - liên quan đến _be và _le, liệu htonl () và ntohl () có cung cấp khả năng tương tự không?
mpez0

2
@Brendan đó là một đối tượng khá nặng để ẩn trong một int tiêu chuẩn, tất cả đều là kiểu nguyên thủy. Tôi đồng ý với bạn về nguyên tắc rằng điều này cần được xử lý trong một nơi nào đó chuẩn nhưng tôi nghĩ rằng sức mạnh này không phải là nơi
Steve Cox

Câu trả lời:


78

uint32_tđược đảm bảo có các thuộc tính gần giống nhau trên bất kỳ nền tảng nào hỗ trợ nó. 1

uint_fast32_t có rất ít đảm bảo về cách nó hoạt động trên các hệ thống khác nhau khi so sánh.

Nếu bạn chuyển sang một nền tảng uint_fast32_tcó kích thước khác, tất cả mã sử dụng uint_fast32_tphải được kiểm tra lại và xác thực. Tất cả các giả định về độ ổn định sẽ được đưa ra ngoài cửa sổ. Toàn bộ hệ thống sẽ hoạt động theo cách khác.

Khi viết mã của bạn, bạn thậm chí có thể không có quyền truy cập vào một uint_fast32_thệ thống không có kích thước 32 bit.

uint32_t sẽ không hoạt động khác (xem chú thích).

Tính đúng đắn quan trọng hơn tốc độ. Do đó, tính đúng sớm là một kế hoạch tốt hơn là tối ưu hóa quá sớm.

Trong trường hợp tôi đang viết mã cho các hệ thống có uint_fast32_t64 bit trở lên, tôi có thể kiểm tra mã của mình cho cả hai trường hợp và sử dụng nó. Bỏ qua cả nhu cầu và cơ hội, làm như vậy là một kế hoạch tồi.

Cuối cùng, uint_fast32_tkhi bạn lưu trữ nó trong bất kỳ khoảng thời gian nào hoặc số lượng phiên bản có thể chậm hơn uint32chỉ đơn giản là do các vấn đề về kích thước bộ nhớ cache và băng thông bộ nhớ. Máy tính ngày nay thường bị giới hạn bộ nhớ hơn nhiều so với giới hạn CPU, và uint_fast32_tcó thể nhanh hơn khi bị cô lập nhưng không phải sau khi bạn tính toán chi phí bộ nhớ.


1 Như @chux đã lưu ý trong một nhận xét, nếu unsignedlớn hơn uint32_t, số học trên uint32_tđi qua các chương trình khuyến mãi số nguyên thông thường, và nếu không, nó sẽ giữ nguyên uint32_t. Điều này có thể gây ra lỗi. Không có gì là hoàn hảo.


15
"uint32_t được đảm bảo có các thuộc tính giống nhau trên bất kỳ nền tảng nào hỗ trợ nó." Có một vấn đề ở góc khi unsignedrộng hơn uint32_tvà sau đó uint32_ttrên một nền tảng trải qua các quảng cáo số nguyên thông thường và trên một nền tảng khác thì không. Tuy nhiên, với uint32_tnhững số nguyên bài toán toán học được giảm đáng kể.
chux - Reinstate Monica 26/1017

2
@chux một trường hợp góc có thể gây ra UB khi nhân, vì chương trình khuyến mãi thích int có dấu và tràn số nguyên có dấu là UB.
CodesInChaos

2
Mặc dù câu trả lời này đúng khi nó đi, nhưng nó hiển thị rất nhiều chi tiết quan trọng. Tóm lại, uint32_tđối với trường hợp các chi tiết chính xác của kiểu biểu diễn máy là quan trọng, trong khi uint_fast32_ttốc độ tính toán là quan trọng nhất, độ ký (không) và phạm vi tối thiểu là quan trọng và các chi tiết của biểu diễn là không cần thiết. Ngoài ra uint_least32_t, ở đó độ ký (không) và phạm vi tối thiểu là quan trọng nhất, độ nhỏ gọn quan trọng hơn tốc độ và biểu diễn chính xác là không cần thiết.
John Bollinger

@JohnBollinger Tất cả đều tốt và tốt, nhưng không thử nghiệm trên phần cứng thực tế triển khai nhiều hơn 1 biến thể, các loại kích thước thay đổi là một cái bẫy. Và lý do tại sao mọi người sử dụnguint32_t thay vì các loại khác là vì chúng thường không có phần cứng như vậy để thực hiện kiểm tra . (Điều này cũng đúng int32_tở mức độ thấp hơn, và thậm chí intshort).
Yakk - Adam Nevraumont

1
Ví dụ về trường hợp góc: Cho unsigned short== uint32_tint==int48_t . Nếu bạn tính toán một cái gì đó giống như (uint32_t)0xFFFFFFFF * (uint32_t)0xFFFFFFFF, thì các toán hạng được thăng cấp signed intvà sẽ kích hoạt tràn số nguyên có dấu, đây là hành vi không xác định. Xem câu hỏi này.
Nayuki

32

Tại sao nhiều người sử dụng uint32_thơn là uint32_fast_t?

Lưu ý: uint32_fast_tNên đặt tên sai uint_fast32_t.

uint32_tcó đặc điểm kỹ thuật chặt chẽ hơn uint_fast32_tvà do đó tạo ra chức năng nhất quán hơn.


uint32_t ưu điểm:

  • Các thuật toán khác nhau chỉ định loại này. IMO - lý do tốt nhất để sử dụng.
  • Chiều rộng và phạm vi chính xác đã biết.
  • Các mảng kiểu này không bị lãng phí.
  • Phép toán số nguyên không dấu với phần tràn của nó dễ dự đoán hơn.
  • Kết hợp chặt chẽ hơn trong phạm vi và toán học của các loại 32-bit của các ngôn ngữ khác.
  • Không bao giờ độn.

uint32_t khuyết điểm:

  • Không phải lúc nào cũng có sẵn (nhưng điều này rất hiếm trong năm 2018).
    Ví dụ: Nền tảng thiếu số nguyên 8/16/32-bit (9/18/ 36 -bit, các nền tảng khác ).
    Ví dụ: Nền tảng sử dụng phần bổ sung không phải của 2. cũ 2200

uint_fast32_t ưu điểm:

  • Luôn luôn sẵn sàng.
    Điều này luôn cho phép tất cả các nền tảng, mới và cũ, sử dụng các loại nhanh / tối thiểu.
  • Loại "nhanh nhất" hỗ trợ phạm vi 32-bit.

uint_fast32_t khuyết điểm:

  • Phạm vi chỉ được biết đến rất ít. Ví dụ, nó có thể là loại 64 bit.
  • Các mảng kiểu này có thể lãng phí trong bộ nhớ.
  • Tất cả các câu trả lời (lúc đầu cũng vậy), bài đăng và nhận xét đều sử dụng sai tên uint32_fast_t . Có vẻ như nhiều người không cần và sử dụng loại này. Chúng tôi thậm chí đã không sử dụng đúng tên!
  • Có thể đệm - (hiếm).
  • Trong một số trường hợp được chọn, loại "nhanh nhất" thực sự có thể là một loại khác. Vì vậy, uint_fast32_tchỉ là gần đúng bậc 1.

Cuối cùng, điều gì là tốt nhất phụ thuộc vào mục tiêu mã hóa. Trừ khi mã hóa cho khả năng di động rất rộng hoặc một số chức năng hiệu suất hốc, hãy sử dụng uint32_t.


Có một vấn đề khác khi sử dụng các loại này: thứ hạng của chúng so với int/unsigned

Có lẽ uint_fastN_tcó thể là cấp bậc của unsigned. Điều này không được chỉ định, nhưng là một điều kiện nhất định và có thể kiểm tra được.

Do đó, uintN_tcó nhiều khả năng hơn uint_fastN_tlà hẹp hơn unsigned. Điều này có nghĩa là mã sử dụng uintN_ttoán học có nhiều khả năng được quảng cáo số nguyên hơnuint_fastN_t khi liên quan đến tính di động.

Với mối quan tâm này: lợi thế về tính di động uint_fastN_tvới các phép toán chọn lọc.


Lưu ý bên lề về int32_tthay vì int_fast32_t: Trên các máy hiếm, INT_FAST32_MINcó thể là -2,147,483,647 chứ không phải -2,147,483,648. Điểm lớn hơn: (u)intN_tcác loại được chỉ định chặt chẽ và dẫn đến mã di động.


2
Loại nhanh nhất hỗ trợ phạm vi 32-bit => thực sự? Đây là di tích của thời kỳ RAM chạy ở tốc độ CPU, ngày nay sự cân bằng đã thay đổi đáng kể trên PC nên (1) kéo số nguyên 32 bit từ bộ nhớ nhanh gấp đôi so với kéo số nguyên 64 bit và (2) lệnh vector hóa trên số nguyên 32-bit sẽ xử lý gấp đôi số nguyên trên 64-bit. Nó vẫn thực sự là nhanh nhất?
Matthieu M.

4
Nhanh nhất cho một số thứ, chậm hơn cho những thứ khác. Không có câu trả lời duy nhất cho tất cả các kích thước cho "kích thước nhanh nhất của số nguyên là bao nhiêu" khi bạn xem xét mảng so với cần phần mở rộng bằng 0. Trong x86-64 System V ABI, uint32_fast_tlà loại 64 bit, do đó, nó lưu phần mở rộng dấu không thường xuyên và cho phép imul rax, [mem]thay vì một lệnh tải mở rộng bằng 0 riêng biệt khi sử dụng nó với số nguyên hoặc con trỏ 64 bit. Nhưng đó là tất cả các bạn nhận được với mức giá gấp đôi so với dấu chân bộ nhớ cache và thêm mã kích thước (REX tiền tố trên tất cả mọi thứ.)
Peter Cordes

1
Ngoài ra, phân chia 64-bit chậm hơn nhiều so với phân chia 32-bit trên hầu hết các CPU x86 và một số (như Bulldozer-family, Atom và Silvermont) có 64-bit nhân chậm hơn 32. Bulldozer-family cũng có 64-bit chậm hơn popcnt. Và hãy nhớ rằng, chỉ an toàn khi sử dụng kiểu này cho các giá trị 32-bit, vì nó nhỏ hơn trên các kiến ​​trúc khác, vì vậy bạn không phải trả chi phí này.
Peter Cordes

2
Tôi mong đợi rằng là mức trung bình có trọng số trên tất cả các ứng dụng C và C ++, việc tạo uint32_fast_ttrên x86 là một lựa chọn tồi tệ. Các hoạt động nhanh hơn là rất ít và rất ít và lợi ích khi chúng xảy ra chủ yếu là rất nhỏ: sự khác biệt đối với imul rax, [mem]trường hợp mà @PeterCordes đề cập là rất , rất nhỏ: một lần lặp lại trong miền hợp nhất và bằng 0 trong miền không sử dụng. Trong hầu hết các tình huống thú vị, nó thậm chí sẽ không thêm một chu kỳ duy nhất. Cân bằng điều đó để chống lại việc sử dụng bộ nhớ gấp đôi và quá trình vector hóa tệ hơn, thật khó để thấy nó chiến thắng thường xuyên.
BeeOnRope

2
@PeterCordes - thú vị nhưng cũng khủng khiếp :). Nó fast_tthậm chí còn tệ hơn int: nó không chỉ có các kích thước khác nhau trên các nền tảng khác nhau mà còn có các kích thước khác nhau tùy thuộc vào các quyết định tối ưu hóa và các kích thước khác nhau trong các tệp khác nhau! Như một vấn đề thực tế, tôi nghĩ rằng nó không thể hoạt động ngay cả với tối ưu hóa toàn bộ chương trình: kích thước trong C và C ++ được cố định vì vậy sizeof(uint32_fast_t)hoặc bất kỳ thứ gì xác định nó thậm chí trực tiếp phải luôn trả về cùng một giá trị, vì vậy sẽ rất khó khăn cho trình biên dịch thực hiện một sự biến đổi như vậy.
BeeOnRope

25

Tại sao nhiều người sử dụng uint32_thơn là uint32_fast_t?

Câu trả lời ngớ ngẩn:

  • Không có kiểu chuẩn uint32_fast_t, cách viết đúng uint_fast32_t.

Câu trả lời thực tế:

  • Nhiều người thực sự sử dụng uint32_thoặc int32_tcho ngữ nghĩa chính xác của họ, chính xác là 32 bit với sự bao bọc không dấu xung quanh uint32_tbiểu diễn phần bù của arithmetic ( ) hoặc 2 ( int32_t). Các xxx_fast32_tloại có thể lớn hơn và do đó không thích hợp để lưu trữ vào các tệp nhị phân, sử dụng trong các mảng và cấu trúc được đóng gói hoặc gửi qua mạng. Hơn nữa, chúng thậm chí có thể không nhanh hơn.

Câu trả lời thực dụng:

  • Nhiều người chỉ không biết (hoặc đơn giản là không quan tâm) về uint_fast32_t, như đã được chứng minh trong các nhận xét và câu trả lời, và có thể cho rằng đơn giản unsigned intlà có cùng ngữ nghĩa, mặc dù nhiều kiến ​​trúc hiện tại vẫn có 16 bit intvà một số mẫu Bảo tàng hiếm có kích thước int lạ khác nhỏ hơn 32.

Câu trả lời UX:

  • Mặc dù có thể nhanh hơn uint32_tnhưng uint_fast32_tsử dụng chậm hơn: mất nhiều thời gian hơn để nhập, đặc biệt là tính đến việc tra cứu chính tả và ngữ nghĩa trong tài liệu C ;-)

Vấn đề về sự thanh lịch, (rõ ràng là dựa trên quan điểm):

  • uint32_ttrông xấu đến mức nhiều lập trình viên thích định nghĩa riêng u32hoặc uint32kiểu của họ ... Từ quan điểm này, uint_fast32_ttrông có vẻ vụng về không thể sửa chữa. Không có gì ngạc nhiên khi nó ngồi trên băng ghế dự bị với những người bạn của nó uint_least32_tvà như vậy.

+1 cho UX. Đó là tốt hơn so với std::reference_wrappertôi đoán, nhưng đôi khi tôi tự hỏi, nếu ủy ban tiêu chuẩn thực sự muốn loại nó chuẩn được sử dụng ...
Matthieu M.

7

Một lý do là nó unsigned intđã "nhanh nhất" mà không cần bất kỳ typedef đặc biệt nào hoặc không cần phải bao gồm một cái gì đó. Vì vậy, nếu bạn cần nó nhanh, chỉ cần sử dụng cơ bản inthoặc unsigned intloại.
Trong khi tiêu chuẩn không đảm bảo rõ ràng rằng nó là nhanh nhất, nó gián tiếp làm như vậy bằng cách nêu rõ "Các int thuần túy có kích thước tự nhiên được đề xuất bởi kiến ​​trúc của môi trường thực thi" trong 3.9.1. Nói cách khác, int(hoặc đối tác không dấu của nó) là thứ mà bộ xử lý cảm thấy thoải mái nhất.

Tất nhiên, bây giờ bạn không biết kích thước unsigned intcó thể là bao nhiêu. Bạn chỉ biết nó ít nhất phải lớn bằng short(và tôi dường như nhớ rằng shortít nhất phải là 16 bit, mặc dù bây giờ tôi không thể tìm thấy nó trong tiêu chuẩn!). Thông thường nó chỉ đơn giản là 4 byte, nhưng về lý thuyết nó có thể lớn hơn hoặc trong những trường hợp cực đoan, thậm chí còn nhỏ hơn ( mặc dù cá nhân tôi chưa bao giờ gặp phải kiến ​​trúc như trường hợp này, thậm chí không phải trên máy tính 8 bit vào những năm 1980. .. có lẽ một số vi điều khiển, những người biết hóa ra tôi bị chứng mất trí nhớ, intrất rõ ràng là 16 bit hồi đó).

Tiêu chuẩn C ++ không bận tâm đến việc chỉ định các <cstdint>loại là gì hoặc chúng đảm bảo những gì, nó chỉ đề cập đến "giống như trong C".

uint32_t, theo tiêu chuẩn C, đảm bảo rằng bạn nhận được chính xác 32 bit. Không có gì khác biệt, không kém hơn và không có bit đệm. Đôi khi đây chính là thứ bạn cần, và do đó nó rất có giá trị.

uint_least32_tđảm bảo rằng bất kể kích thước là gì, nó không thể nhỏ hơn 32 bit (nhưng rất có thể lớn hơn). Đôi khi, nhưng hiếm hơn nhiều so với một thông báo chính xác hoặc "không quan tâm", đây là những gì bạn muốn.

Cuối cùng, uint_fast32_ttheo ý kiến ​​của tôi là hơi thừa, ngoại trừ mục đích tài liệu có mục đích. Tiêu chuẩn C nêu rõ "chỉ định một kiểu số nguyên thường nhanh nhất" (lưu ý từ "thường") và đề cập rõ ràng rằng nó không cần phải nhanh nhất cho mọi mục đích. Nói cách khác, uint_fast32_tcũng giống như uint_least32_t, thường cũng nhanh nhất, chỉ không có đảm bảo nào được đưa ra (nhưng không có bảo đảm nào cả).

Vì hầu hết thời gian bạn không quan tâm đến kích thước chính xác hoặc bạn muốn chính xác 32 (hoặc 64, đôi khi là 16) bit và vì kiểu "không quan tâm" unsigned intlà nhanh nhất, điều này giải thích tại sao uint_fast32_tkhông phải như vậy thường xuyên sử dụng.


3
Tôi ngạc nhiên là bạn không nhớ 16-bit inttrên bộ xử lý 8-bit, tôi không thể nhớ bất kỳ thứ gì từ những ngày đó đã sử dụng bất kỳ thứ gì lớn hơn. Nếu bộ nhớ phục vụ, các trình biên dịch cho kiến ​​trúc x86 được phân đoạn cũng sử dụng 16-bit int.
Mark Ransom

@MarkRansom: Chà, bạn nói đúng. Tôi đã rất tin rằng đó intlà 32 bit trên 68000 (mà tôi nghĩ đến, làm ví dụ). Nó không phải là ...
Damon

inttrước đây được coi là kiểu nhanh nhất với độ rộng tối thiểu là 16 bit (đây là lý do tại sao C có quy tắc thăng hạng số nguyên), nhưng ngày nay với kiến ​​trúc 64 bit thì điều này không còn đúng nữa. Ví dụ: số nguyên 8 byte nhanh hơn số nguyên 4 byte trên bit x86_64 bởi vì trình biên dịch số nguyên 4 byte phải chèn thêm lệnh mở rộng giá trị 4 byte thành giá trị 8 byte trước khi so sánh với các giá trị 8 byte khác.
StaceyGirl

"unsigned int" không nhất thiết phải nhanh nhất trên x64. Những điều kỳ lạ đã xảy ra.
Joshua

Một trường hợp phổ biến khác là long, vì lý do lịch sử, cần phải là 32-bit, và intbây giờ được yêu cầu không rộng hơn long, vì vậy intcó thể cần phải giữ nguyên 32-bit ngay cả khi 64 bit sẽ nhanh hơn.
Davislor,

6

Tôi đã không thấy bằng chứng uint32_tđược sử dụng cho phạm vi của nó . Thay vào đó, phần lớn thời gian mà tôi đã thấy uint32_tđược sử dụng, đó là chứa chính xác 4 octet dữ liệu trong các thuật toán khác nhau, với ngữ nghĩa bao quanh và thay đổi được đảm bảo!

Cũng có những lý do khác để sử dụng uint32_tthay vì uint_fast32_t: Thường thì nó sẽ cung cấp ABI ổn định. Ngoài ra, việc sử dụng bộ nhớ có thể được biết chính xác. Điều này bù đắp rất nhiều bất kể mức tăng tốc độ sẽ là từ đâu uint_fast32_t, bất cứ khi nào kiểu đó khác với kiểu của uint32_t.

Đối với các giá trị <65536, đã có một kiểu tiện dụng, nó được gọi unsigned int( unsigned shortbắt buộc phải có ít nhất phạm vi đó, nhưng unsigned intcó kích thước từ gốc) Đối với các giá trị <4294967296, có một kiểu khác được gọi unsigned long.


Và cuối cùng là mọi người đừng dùng uint_fast32_tvì gõ lâu khó chịu và dễ gõ nhầm: D


@ikegami: bạn đã thay đổi ý định của tôi với bản shortchỉnh sửa. intcó lẽ là nhanh nhất khi nó khác biệt với short.
Antti Haapala

1
Vậy thì câu cuối cùng của bạn là hoàn toàn sai. Tuyên bố rằng bạn nên sử dụng unsigned intthay vì uint16_fast_ttuyên bố rằng bạn biết rõ hơn trình biên dịch.
ikegami

Ngoài ra, tôi xin lỗi vì đã thay đổi ý định của văn bản của bạn. Đó không phải là ý định của tôi.
ikegami

unsigned longkhông phải là một lựa chọn tốt nếu nền tảng của bạn có 64-bit longvà bạn chỉ cần số <2^32.
Ruslan

1
@ikegami: Kiểu "unsigned int" sẽ luôn hoạt động như kiểu không dấu, ngay cả khi được thăng cấp. Về mặt này, nó vượt trội hơn cả uint16_tuint_fast16_t. Nếu uint_fast16_tđược chỉ định lỏng lẻo hơn các kiểu số nguyên bình thường, sao cho phạm vi của nó không cần nhất quán đối với các đối tượng có địa chỉ không được sử dụng, điều đó có thể mang lại một số lợi ích về hiệu suất trên các nền tảng thực hiện số học 32 bit bên trong nhưng có bus dữ liệu 16 bit . Tuy nhiên, Tiêu chuẩn không cho phép sự linh hoạt như vậy.
supercat

5

Nhiều lý do.

  1. Nhiều người không biết các loại 'nhanh' tồn tại.
  2. Nó dài dòng hơn để nhập.
  3. Khó hơn để lý luận về hành vi chương trình của bạn khi bạn không biết kích thước thực của loại.
  4. Tiêu chuẩn không thực sự ghim nhanh nhất, cũng không thể thực sự loại nào thực sự nhanh nhất có thể rất phụ thuộc vào ngữ cảnh.
  5. Tôi không thấy bằng chứng nào về việc các nhà phát triển nền tảng đặt bất kỳ suy nghĩ nào về quy mô của các loại này khi xác định nền tảng của họ. Ví dụ trên x86-64 Linux, các loại "nhanh" đều là 64-bit mặc dù x86-64 có hỗ trợ phần cứng cho các hoạt động nhanh trên các giá trị 32-bit.

Tóm lại các loại "nhanh" là rác vô giá trị. Nếu bạn thực sự cần tìm ra loại nào là nhanh nhất cho một ứng dụng nhất định, bạn cần phải chuẩn mã của mình trên trình biên dịch.


Trong lịch sử đã có những bộ vi xử lý có hướng dẫn truy cập bộ nhớ 32-bit và / hoặc 64-bit nhưng không phải là 8 & 16-bit. Vì vậy, int_fast {8,16} _ sẽ không-hoàn-toàn-ngu ngốc hơn 20 năm trước. AFAIK bộ xử lý chính cuối cùng như vậy là DEC Alpha 21064 ban đầu (thế hệ thứ hai 21164 đã được cải tiến). Có lẽ vẫn còn được nhúng DSP hoặc bất cứ điều gì mà chỉ làm các truy cập từ, nhưng tính di động không phải là bình thường một mối quan tâm lớn đến những việc như vậy, vì vậy tôi không thấy lý do tại sao bạn muốn chuyển hàng hóa-giáo phái nhanh _T trên những. Và đã có những cỗ máy Cray "mọi thứ đều là 64-bit" được xây dựng bằng tay.
user1998586

1
Loại 1b: Nhiều người không quan tâm rằng các loại 'nhanh' tồn tại. Đó là danh mục của tôi.
gnasher729

Loại 6: Nhiều người không tin rằng loại 'nhanh' là nhanh nhất. Tôi thuộc loại đó.
Rõ ràng hơn

5

Từ quan điểm về tính đúng đắn và dễ viết mã, uint32_tcó nhiều lợi thế hơn uint_fast32_tđặc biệt vì kích thước và ngữ nghĩa số học được xác định chính xác hơn, như nhiều người dùng ở trên đã chỉ ra.

Điều gì đã có lẽ bị bỏ qua đó là một lợi thế cho là của uint_fast32_t- mà nó có thể được nhanh hơn , chỉ cần không bao giờ cụ thể hóa trong bất kỳ cách có ý nghĩa. Hầu hết các bộ vi xử lý 64 bit đã thống trị kỷ nguyên 64 bit (chủ yếu là x86-64 và Aarch64) phát triển từ kiến ​​trúc 32 bit và có các hoạt động gốc 32 bit nhanh chóng ngay cả ở chế độ 64 bit. Vì vậy, uint_fast32_tcũng giống như uint32_ttrên các nền tảng đó.

Ngay cả khi một số nền tảng "cũng chạy" như POWER, MIPS64, SPARC chỉ cung cấp các hoạt động ALU 64 bit, thì phần lớn các hoạt động 32 bit thú vị có thể được thực hiện tốt trên các thanh ghi 64 bit: 32 bit dưới cùng sẽ có kết quả mong muốn (và tất cả các nền tảng chính thống ít nhất cho phép bạn tải / lưu trữ 32-bit). Dịch sang trái là vấn đề chính, nhưng thậm chí điều đó có thể được tối ưu hóa trong nhiều trường hợp bằng cách tối ưu hóa theo dõi giá trị / phạm vi trong trình biên dịch.

Tôi nghi ngờ sự dịch chuyển sang trái đôi khi hơi chậm hơn hoặc phép nhân 32x32 -> 64 sẽ làm tăng gấp đôi mức sử dụng bộ nhớ cho các giá trị như vậy, trong tất cả trừ các ứng dụng khó hiểu nhất.

Cuối cùng, tôi sẽ lưu ý rằng mặc dù sự cân bằng phần lớn được mô tả là "khả năng sử dụng bộ nhớ và vectơ hóa" (có lợi uint32_t) so với số lượng / tốc độ lệnh (có lợi uint_fast32_t) - thậm chí điều đó không rõ ràng đối với tôi. Có, trên một số nền tảng, bạn sẽ cần hướng dẫn bổ sung cho một số hoạt động 32 bit, nhưng bạn cũng sẽ lưu một số hướng dẫn vì:

  • Sử dụng loại nhỏ hơn thường cho phép trình biên dịch kết hợp khéo léo các hoạt động liền kề bằng cách sử dụng một hoạt động 64 bit để thực hiện hai hoạt động 32 bit. Một ví dụ về kiểu "vectơ hóa của người nghèo" không phải là hiếm. Ví dụ: việc tạo một hằng số struct two32{ uint32_t a, b; }thành raxlike two32{1, 2} có thể được tối ưu hóa thành một bản duy nhất mov rax, 0x20001trong khi phiên bản 64 bit cần hai hướng dẫn. Về nguyên tắc, điều này cũng có thể thực hiện được đối với các phép toán số học liền kề (cùng một phép toán, khác toán hạng), nhưng tôi chưa thấy nó trong thực tế.
  • "Sử dụng bộ nhớ" thấp hơn cũng thường dẫn đến ít hướng dẫn hơn, ngay cả khi bộ nhớ hoặc dấu vết bộ nhớ cache không phải là vấn đề, vì bất kỳ cấu trúc kiểu hoặc mảng nào thuộc loại này được sao chép, bạn sẽ nhận được gấp đôi cho số tiền của mình cho mỗi thanh ghi được sao chép.
  • Các kiểu dữ liệu nhỏ hơn thường khai thác các quy ước gọi hiện đại tốt hơn như SysV ABI, gói dữ liệu cấu trúc dữ liệu một cách hiệu quả vào các thanh ghi. Ví dụ, bạn có thể trả về cấu trúc 16 byte trong thanh ghi rdx:rax. Đối với một cấu trúc trả về hàm có 4 uint32_tgiá trị (được khởi tạo từ một hằng số), điều đó sẽ chuyển thành

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

    Cấu trúc tương tự với 4 64-bit uint_fast32_tcần một thanh ghi di chuyển và bốn lưu trữ vào bộ nhớ để làm điều tương tự (và người gọi có thể sẽ đọc các giá trị trở lại từ bộ nhớ sau khi trả về):

    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
    

    Tương tự như vậy, khi truyền các đối số cấu trúc, các giá trị 32 bit được đóng gói với mật độ gấp đôi vào các thanh ghi có sẵn cho các tham số, vì vậy ít có khả năng bạn sẽ dùng hết các đối số đăng ký và phải tràn vào ngăn xếp 1 .

  • Ngay cả khi bạn chọn sử dụng uint_fast32_tcho những nơi "quan trọng về tốc độ", bạn cũng sẽ có những nơi bạn cần loại kích thước cố định. Ví dụ: khi chuyển các giá trị cho đầu ra bên ngoài, từ đầu vào bên ngoài, như một phần của ABI của bạn, như một phần của cấu trúc cần bố cục cụ thể hoặc do bạn sử dụng thông minh uint32_tcho các tập hợp giá trị lớn để tiết kiệm diện tích bộ nhớ. Ở những nơi mà kiểu của bạn uint_fast32_tvà kiểu `` uint32_t` cần giao diện, bạn có thể tìm thấy (ngoài độ phức tạp của việc phát triển), các phần mở rộng ký hiệu không cần thiết hoặc mã liên quan đến kích thước không khớp khác. Các trình biên dịch thực hiện một công việc tốt trong việc tối ưu hóa điều này trong nhiều trường hợp, nhưng vẫn không có gì lạ khi thấy điều này trong đầu ra được tối ưu hóa khi trộn các loại kích thước khác nhau.

Bạn có thể chơi với một số ví dụ ở trên và hơn thế nữa trên Godbolt .


1 Để rõ ràng, quy ước đóng gói các cấu trúc chặt chẽ vào các thanh ghi không phải lúc nào cũng chiến thắng rõ ràng cho các giá trị nhỏ hơn. Điều đó có nghĩa là các giá trị nhỏ hơn có thể phải được "trích xuất" trước khi chúng có thể được sử dụng. Ví dụ, một hàm đơn giản trả về tổng của hai thành viên cấu trúc cùng nhau cần một khoảng mov rax, rdi; shr rax, 32; add edi, eaxthời gian đối với phiên bản 64-bit, mỗi đối số nhận được thanh ghi của riêng nó và chỉ cần một addhoặc lea. Tuy nhiên, nếu bạn chấp nhận rằng thiết kế "đóng gói chặt chẽ các cấu trúc trong khi đi qua" có ý nghĩa tổng thể, thì các giá trị nhỏ hơn sẽ tận dụng nhiều hơn tính năng này.


glibc trên x86-64 Linux sử dụng 64-bit uint_fast32_t, đó là một lỗi IMO. (Rõ ràng Windows uint_fast32_tlà loại 32 bit trên Windows.) Là 64 bit trên Linux x86-64 là lý do tại sao tôi không bao giờ khuyên mọi người nên sử dụng uint_fast32_t: nó được tối ưu hóa cho số lượng lệnh thấp (hàm args và giá trị trả về không bao giờ cần phần mở rộng 0 cho sử dụng làm chỉ mục mảng) không phải cho tốc độ tổng thể hoặc kích thước mã trên một trong những nền tảng quan trọng chính.
Peter Cordes

2
Ồ đúng rồi, tôi đã đọc bình luận của bạn ở trên về SysV ABI, nhưng như bạn đã chỉ ra sau này có thể do một nhóm / tài liệu khác quyết định nó - nhưng tôi đoán một khi điều đó xảy ra thì nó khá nhiều. Tôi nghĩ thậm chí còn có vấn đề khi số lượng chu kỳ / số lượng lệnh thuần túy ủng hộ các loại lớn hơn, thậm chí bỏ qua các hiệu ứng dấu chân bộ nhớ và vector hóa, ngay cả trên các nền tảng không hỗ trợ hoạt động 32-bit tốt - vì vẫn có trường hợp trình biên dịch có thể tối ưu hóa tốt hơn các loại nhỏ hơn. Tôi đã thêm một số ví dụ ở trên. @PeterCordes
BeeOnRope

SysV đóng gói nhiều thành viên cấu trúc vào cùng một thanh ghi sẽ tốn nhiều hướng dẫn hơn khá thường xuyên khi trả về một pair<int,bool>hoặc pair<int,int>. Nếu cả hai thành viên không phải là hằng số thời gian biên dịch, thường có nhiều hơn là OR và người gọi phải giải nén các giá trị trả về. ( bug.llvm.org/show_bug.cgi?id=34840 LLVM tối ưu hóa việc truyền giá trị trả về cho các hàm riêng tư và nên coi int 32-bit là lấy toàn bộ raxđể giá trị boolriêng biệt dlthay vì cần hằng số 64-bit để testnó.)
Peter Cordes

1
Tôi nghĩ rằng các trình biên dịch thường không phân chia các chức năng. Loại bỏ một đường dẫn nhanh như một chức năng riêng biệt là một cách tối ưu hóa cấp nguồn hữu ích (đặc biệt là trong tiêu đề nơi nó có thể nội dòng). Có thể rất tốt nếu 90% đầu vào là "trường hợp không làm gì cả"; thực hiện lọc đó trong vòng lặp của người gọi là một chiến thắng lớn. IIRC, Linux sử dụng __attribute__((noinline))chính xác để đảm bảo rằng gcc không nội tuyến chức năng xử lý lỗi và đặt một loạt push rbx/ .../ pop rbx/ ... vào đường dẫn nhanh của một số hàm nhân quan trọng có nhiều người gọi và không nội tuyến.
Peter Cordes

1
Trong Java, nó thực sự quan trọng vì nội tuyến là chìa khóa để tối ưu hóa hơn nữa (đặc biệt là khử ảo hóa phổ biến không giống như C ++), vì vậy nó thường trả tiền để chia ra một đường dẫn nhanh ở đó và "tối ưu hóa bytecode" thực sự là một điều (mặc dù sự khôn ngoan thông thường rằng nó không có ý nghĩa gì vì JIT thực hiện biên dịch cuối cùng) chỉ để đếm ngược bytecode vì các quyết định nội tuyến dựa trên kích thước bytecode, không phải kích thước mã máy nội tuyến (và mối tương quan có thể thay đổi theo thứ tự cường độ).
BeeOnRope

4

Đối với các mục đích thực tế, uint_fast32_tlà hoàn toàn vô dụng. Nó được định nghĩa không chính xác trên nền tảng phổ biến nhất (x86_64) và không thực sự mang lại bất kỳ lợi thế nào trừ khi bạn có một trình biên dịch chất lượng rất thấp. Về mặt khái niệm, việc sử dụng các loại "nhanh" trong cấu trúc / mảng dữ liệu không bao giờ có ý nghĩa - bất kỳ khoản tiết kiệm nào bạn nhận được từ loại hoạt động hiệu quả hơn sẽ bị thu hẹp bởi chi phí (bỏ sót bộ nhớ cache, v.v.) khi tăng kích thước của tập dữ liệu làm việc của bạn. Và đối với các biến cục bộ riêng lẻ (bộ đếm vòng lặp, nhiệt độ, v.v.), một trình biên dịch không phải đồ chơi thường chỉ có thể hoạt động với một loại lớn hơn trong mã được tạo nếu điều đó hiệu quả hơn và chỉ cắt ngắn đến kích thước danh nghĩa khi cần thiết để đúng (và với các loại đã ký, nó không bao giờ cần thiết).

Một biến thể hữu ích về mặt lý thuyết là uint_least32_t, khi bạn cần có thể lưu trữ bất kỳ giá trị 32-bit nào, nhưng muốn có thể di động được với các máy thiếu loại 32-bit có kích thước chính xác. Tuy nhiên, trên thực tế, đó không phải là điều bạn cần lo lắng.


4

Theo hiểu biết của tôi, intban đầu được cho là một kiểu số nguyên "gốc" với đảm bảo bổ sung rằng nó phải có kích thước ít nhất là 16 bit - một thứ được coi là kích thước "hợp lý" hồi đó.

Khi nền tảng 32 bit trở nên phổ biến hơn, chúng ta có thể nói rằng kích thước "hợp lý" đã thay đổi thành 32 bit:

  • Windows hiện đại sử dụng 32-bit inttrên tất cả các nền tảng.
  • POSIX đảm bảo rằng intít nhất là 32 bit.
  • C #, Java có kiểu intđược đảm bảo chính xác là 32 bit.

Nhưng khi nền tảng 64-bit trở thành tiêu chuẩn, không ai mở rộng intthành số nguyên 64-bit vì:

  • Tính di động: rất nhiều mã phụ thuộc vào intkích thước 32 bit.
  • Tiêu thụ bộ nhớ: tăng gấp đôi mức sử dụng bộ nhớ cho mọi inttrường hợp có thể là không hợp lý đối với hầu hết các trường hợp, vì trong hầu hết các trường hợp, con số được sử dụng nhỏ hơn nhiều dưới 2 tỷ.

Bây giờ, tại sao bạn muốn uint32_tđến uint_fast32_t? Vì lý do tương tự các ngôn ngữ C # và Java luôn sử dụng số nguyên có kích thước cố định: lập trình viên không viết mã khi nghĩ về các kích thước có thể có của các loại khác nhau, họ viết cho một nền tảng và mã kiểm tra trên nền tảng đó. Hầu hết mã phụ thuộc hoàn toàn vào kích thước cụ thể của kiểu dữ liệu. Và đây là lý do tại sao uint32_tlà một lựa chọn tốt hơn cho hầu hết các trường hợp - nó không cho phép bất kỳ sự mơ hồ nào liên quan đến hành vi của nó.

Hơn nữa, có uint_fast32_tthực sự là loại nhanh nhất trên nền tảng có kích thước bằng hoặc lớn hơn đến 32 bit? Không hẳn vậy. Hãy xem xét trình biên dịch mã này của GCC cho x86_64 trên Windows:

extern uint64_t get(void);

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

Lắp ráp đã tạo trông như thế này:

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

Bây giờ nếu bạn thay đổi get()giá trị trả về của thành uint_fast32_t(là 4 byte trên Windows x86_64), bạn sẽ nhận được điều này:

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

Lưu ý rằng mã được tạo gần như giống nhau ngoại trừ mov %eax,%eaxlệnh bổ sung sau lệnh gọi hàm có nghĩa là để mở rộng giá trị 32 bit thành giá trị 64 bit.

Không có vấn đề như vậy nếu bạn chỉ sử dụng các giá trị 32-bit, nhưng có thể bạn sẽ sử dụng các giá trị có size_tbiến (kích thước mảng có thể là?) Và đó là 64 bit trên x86_64. Trên Linux uint_fast32_tlà 8 byte, vì vậy tình hình sẽ khác.

Nhiều lập trình viên sử dụng intkhi họ cần trả về giá trị nhỏ (giả sử trong phạm vi [-32,32]). Điều này sẽ hoạt động hoàn hảo nếu intlà kích thước số nguyên gốc của nền tảng, nhưng vì nó không phải trên nền tảng 64-bit, nên một loại khác phù hợp với loại gốc của nền tảng là lựa chọn tốt hơn (trừ khi nó thường được sử dụng với các số nguyên khác có kích thước nhỏ hơn).

Về cơ bản, bất kể tiêu chuẩn nói gì, uint_fast32_tvẫn bị hỏng trên một số triển khai. Nếu bạn quan tâm đến hướng dẫn bổ sung được tạo ở một số nơi, bạn nên xác định kiểu số nguyên "gốc" của riêng mình. Hoặc bạn có thể sử dụng size_tcho mục đích này, vì nó thường sẽ phù hợp với nativekích thước (Tôi không bao gồm các nền tảng cũ và ít người biết đến như 8086, chỉ các nền tảng có thể chạy Windows, Linux, v.v.).


Một dấu hiệu khác cho thấy intđược cho là kiểu số nguyên gốc là "quy tắc thăng hạng số nguyên". Hầu hết các CPU chỉ có thể thực hiện các hoạt động trên nguyên bản, do đó, CPU 32 bit thường chỉ có thể thực hiện các phép cộng, trừ, v.v. 32 bit (CPU Intel là một ngoại lệ ở đây). Các loại số nguyên có kích thước khác chỉ được hỗ trợ thông qua hướng dẫn tải và lưu trữ. Ví dụ: giá trị 8 bit nên được tải bằng lệnh "tải 8 bit có dấu" hoặc "tải 8 bit không dấu" thích hợp và sẽ mở rộng giá trị thành 32 bit sau khi tải. Nếu không có quy tắc thăng hạng số nguyên, trình biên dịch C sẽ phải thêm một chút mã cho các biểu thức sử dụng kiểu nhỏ hơn kiểu gốc. Thật không may, điều này không còn tồn tại với kiến ​​trúc 64-bit vì các trình biên dịch giờ đây phải phát ra các lệnh bổ sung trong một số trường hợp (như được hiển thị ở trên).


2
Những suy nghĩ về "không ai mở rộng int thành số nguyên 64 bit bởi vì" và "Thật không may, điều này không còn giữ được nữa với kiến ​​trúc 64 bit" là những điểm rất tốt . Công bằng mà nói, mặc dù về "nhanh nhất" và so sánh mã lắp ráp: Trong trường hợp này, đoạn mã thứ 2 có vẻ chậm hơn với lệnh bổ sung của nó, nhưng độ dài và tốc độ mã đôi khi không tương quan tốt. So sánh mạnh hơn sẽ báo cáo thời gian chạy - nhưng điều đó không dễ thực hiện.
chux - Reinstate Monica 27/1017

Tôi sẽ không dễ dàng để đo độ chậm của mã thứ 2, CPU Intel có thể đang hoạt động rất tốt, nhưng mã dài hơn cũng có nghĩa là ô nhiễm bộ nhớ cache lớn. Chỉ một lần thỉnh thoảng có thể không ảnh hưởng gì, nhưng tính hữu dụng của uint_fast32_t trở nên mơ hồ.
StaceyGirl

Tôi hoàn toàn đồng ý về tính hữu dụng của việc uint_fast32_ttrở nên mơ hồ, trong tất cả các trường hợp nhưng rất chọn lọc. Tôi nghi ngờ lý do thúc đẩy uint_fastN_ttất cả là để phù hợp với "chúng ta không sử dụng unsignednhư 64-bit, mặc dù nó thường nhanh nhất trên nền tảng mới, vì quá nhiều mã sẽ bị hỏng" nhưng "Tôi vẫn muốn một loại nhanh ít nhất là N-bit . " Tôi sẽ UV bạn một lần nữa nếu tôi có thể.
chux - Phục hồi Monica

Hầu hết các kiến ​​trúc 64-bit có thể dễ dàng hoạt động trên các số nguyên 32-bit. Ngay cả DEC Alpha (là một kiến ​​trúc 64-bit mới nhánh chứ không phải là một phần mở rộng cho ISA 32-bit hiện có như PowerPC64 hoặc MIPS64) cũng có tải / lưu trữ 32 và 64-bit. (Nhưng không phải tải / lưu trữ byte hoặc 16 bit!). Hầu hết các lệnh chỉ là 64-bit, nhưng nó có hỗ trợ HW gốc cho thêm / phụ 32-bit và nhân số đó cắt ngắn kết quả thành 32 bit. ( alasir.com/articles/alpha_history/press/alpha_intro.html ) Vì vậy, hầu như không có tốc độ tăng khi tạo int64 bit và thường là giảm tốc độ do dấu chân bộ nhớ cache.
Peter Cordes

Ngoài ra, nếu bạn tạo int64 bit, uint32_ttypedef có chiều rộng cố định của bạn sẽ cần một __attribute__hoặc một bản hack khác, hoặc một số loại tùy chỉnh nhỏ hơn int. (Hoặc short, nhưng sau đó bạn có cùng một vấn đề uint16_t.) Không ai muốn điều đó. 32-bit đủ rộng cho hầu hết mọi thứ (không giống như 16-bit); sử dụng số nguyên 32 bit khi đó là tất cả những gì bạn cần không phải là "kém hiệu quả" theo bất kỳ cách nào có ý nghĩa trên máy 64 bit.
Peter Cordes

2

Trong nhiều trường hợp, khi một thuật toán hoạt động trên một mảng dữ liệu, cách tốt nhất để cải thiện hiệu suất là giảm thiểu số lần bỏ sót bộ nhớ cache. Mỗi phần tử càng nhỏ thì càng có nhiều phần tử có thể vừa với bộ nhớ cache. Đây là lý do tại sao rất nhiều mã vẫn được viết để sử dụng con trỏ 32 bit trên các máy 64 bit: chúng không cần bất cứ thứ gì gần 4 GiB dữ liệu, nhưng chi phí tạo tất cả các con trỏ và phần bù lại cần 8 byte thay vì 4 byte. sẽ là đáng kể.

Cũng có một số ABI và giao thức được chỉ định cần chính xác 32 bit, ví dụ: địa chỉ IPv4. Đó là điều uint32_tthực sự có nghĩa: sử dụng chính xác 32 bit, bất kể điều đó có hiệu quả trên CPU hay không. Chúng từng được khai báo là longhoặc unsigned long, gây ra rất nhiều vấn đề trong quá trình chuyển đổi 64-bit. Nếu bạn chỉ cần một loại không dấu chứa các số lên đến ít nhất 2³²-1, đó là định nghĩa unsigned longkể từ khi tiêu chuẩn C đầu tiên ra đời. Tuy nhiên, trên thực tế, đủ mã cũ giả định rằng a longcó thể giữ bất kỳ con trỏ hoặc tệp bù đắp hoặc dấu thời gian nào, và đủ mã cũ giả định rằng nó có chiều rộng chính xác là 32 bit, các trình biên dịch không thể nhất thiết phải làm longnhư vậy int_fast32_tmà không làm hỏng quá nhiều thứ.

Về lý thuyết, chương trình sẽ dễ sử dụng hơn trong tương lai uint_least32_tvà thậm chí có thể tải uint_least32_tcác phần tử vào một uint_fast32_tbiến để tính toán. Một triển khai không có uint32_tloại nào thậm chí có thể tự tuyên bố tuân thủ chính thức với tiêu chuẩn! (Nó chỉ sẽ không thể để biên dịch nhiều chương trình hiện có.) Trong thực tế, không có kiến trúc nào nữa đâu int, uint32_tuint_least32_tkhông giống nhau, và không có lợi thế, hiện nay , đến việc thực hiện uint_fast32_t. Vậy tại sao những thứ quá phức tạp?

Tuy nhiên, hãy nhìn vào lý do tất cả các 32_tloại cần thiết tồn tại khi chúng ta đã có long, và bạn sẽ thấy rằng những giả định đó đã bùng lên trong khuôn mặt chúng ta trước đây. Mã của bạn có thể sẽ chạy vào một ngày nào đó trên một máy mà các phép tính 32 bit chiều rộng chính xác chậm hơn kích thước từ gốc và tốt hơn là bạn nên sử dụng uint_least32_tđể lưu trữ và uint_fast32_ttính toán một cách tôn giáo. Hoặc nếu bạn sẽ băng qua cây cầu đó khi bạn đến đó và chỉ muốn một cái gì đó đơn giản, có unsigned long.


Nhưng có những kiến ​​trúc intkhông phải là 32 bit, ví dụ ILP64. Không phải là chúng phổ biến.
Antti Haapala

Tôi không nghĩ ILP64 tồn tại ở thì hiện tại? Một số trang web cho rằng “Cray” sử dụng nó, tất cả đều trích dẫn cùng một trang Unix.org từ năm 1997, nhưng UNICOS vào giữa những năm 90 thực sự đã làm một điều khác thường và Crays ngày nay sử dụng phần cứng của Intel. Cũng trang đó tuyên bố rằng các siêu máy tính ETA đã sử dụng ILP64, nhưng chúng đã ngừng hoạt động từ lâu. Wikipedia tuyên bố rằng cổng Solaris đến SPARC64 của HAL đã sử dụng ILP64, nhưng chúng cũng đã ngừng hoạt động trong nhiều năm. CppReference nói rằng ILP64 chỉ được sử dụng trong một số Unices 64-bit ban đầu. Vì vậy, nó chỉ có liên quan đến một số máy tính retro bí truyền.
Davislor

Lưu ý rằng nếu bạn sử dụng “giao diện ILP64” của Thư viện Hạt nhân Toán học của Intel ngày nay, intsẽ rộng 32 bit. LoạiMKL_INT là những gì sẽ thay đổi.
Davislor

1

Để đưa ra câu trả lời trực tiếp: Tôi nghĩ lý do thực sự tại sao uint32_tđược sử dụng quá nhiều uint_fast32_thoặc uint_least32_tđơn giản là nó dễ nhập hơn, và do ngắn hơn, dễ đọc hơn nhiều: Nếu bạn tạo cấu trúc với một số kiểu, và một số kiểu uint_fast32_thoặc tương tự, thì thường rất khó để sắp xếp chúng một cách đẹp mắt với inthoặc boolhoặc các loại khác trong C, chúng khá ngắn (trong trường hợp này: charso với character). Tất nhiên tôi không thể sao lưu điều này bằng dữ liệu cứng, nhưng các câu trả lời khác cũng chỉ có thể đoán được lý do.

Đối với lý do kỹ thuật để thích uint32_t, tôi không nghĩ là có - khi bạn thực sự cần một int 32 bit không dấu chính xác, thì kiểu này là lựa chọn tiêu chuẩn hóa duy nhất của bạn . Trong hầu hết các trường hợp khác, các biến thể khác thích hợp hơn về mặt kỹ thuật - cụ thể là uint_fast32_tnếu bạn lo lắng về tốc độ và uint_least32_tnếu bạn lo lắng về không gian lưu trữ. Sử dụng uint32_ttrong một trong hai trường hợp này có nguy cơ không thể biên dịch vì kiểu không bắt buộc phải tồn tại.

Trên thực tế, các kiểu uint32_tvà các kiểu liên quan tồn tại trên tất cả các nền tảng hiện tại, ngoại trừ một số DSP rất hiếm (ngày nay) hoặc triển khai trò đùa, vì vậy có rất ít rủi ro thực tế khi sử dụng kiểu chính xác. Tương tự như vậy, trong khi bạn có thể gặp phải các hình phạt về tốc độ với các loại chiều rộng cố định, chúng (trên các cpu hiện đại) không làm tê liệt nữa.

Đó là lý do tại sao, tôi nghĩ, loại ngắn hơn đơn giản là chiến thắng trong hầu hết các trường hợp, do sự lười biếng của lập trình viên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.