Tư vấn sử dụng các trường bit trong cấu trúc


7

Tôi phải theo dõi một lượng lớn dữ liệu (đối với Arduino) trong một chương trình trong khi chăm sóc một lượng lớn doanh nghiệp khác.

Tôi bắt đầu với một cấu trúc như thế này:

struct MyStruct
{
   // note: these names might as well be foo bar baz
   uint8_t color; 
   boolean state; 
   uint8_t area;
   uint8_t number;
   uint8_t len;
};

Tôi có một mảng 800 trong số này. Với 5 byte mỗi cái, đó là 4Kb, hoặc một nửa ram trên Arduino Mega.

Tôi đã thay đổi thành một cấu trúc như thế này:

struct MyStruct
{
   uint8_t color : 3; // max value 7
   // uint8_t state : 1; *** moved, thanks Ignacio ***
   uint8_t area : 5; // m.v. 31
   uint8_t number;
   uint8_t len : 5; // m.v. 31
   uint8_t state : 1; // m.v. 1
};

Tôi hiểu điều này làm giảm kích thước của mỗi phiên bản xuống còn 3 byte, với tổng số 2,4Kb trong mảng của tôi.

Tôi đã đọc được rằng trong một số triển khai / chipset sử dụng các trường bit trong cấu trúc có thể dẫn đến việc thực thi kém hiệu quả hơn. Rõ ràng đây là cách sử dụng bộ nhớ hiệu quả hơn, nhưng câu hỏi của tôi là:

Làm thế nào để Arduino xử lý các trường bit? Nó sẽ tốt hơn, tệ hơn, hoặc khác biệt không đáng kể về mặt hoặc tốc độ khi lặp qua một mảng các cấu trúc trường bit? Có một cách tốt để kiểm tra điều này?


3
Thứ tự đó sẽ sử dụng 4 byte. Lấy trường 1 bit đó từ giữa 8 khác
Ignacio Vazquez-Abrams

1
@Ignacio Tuyệt vời ... bạn vừa giảm 9% tổng số sử dụng bộ nhớ của tôi.
anthropomo

Lý do các trường bit trong cấu trúc có thể dẫn đến việc thực thi kém hiệu quả hơn là do các hạn chế căn chỉnh từ. Ví dụ: nếu chúng vượt qua căn chỉnh 32/64 tự nhiên, gây ra nhiều truy cập. Đây không phải là vấn đề đối với ATmega
Milliways

1
Câu hỏi bạn cần tự hỏi mình là nếu bạn lo lắng nhất về việc hết RAM, bộ nhớ chương trình hay chu kỳ CPU - đây là tình huống bạn có thể tối ưu hóa khá nhiều cho một cái với chi phí khác và một số tác động đến thứ ba. Tại một thời điểm nhất định, bạn cũng nên hỏi xem Arduino dựa trên ATmega có thực sự là nền tảng phù hợp không.
Chris Stratton

Tôi đã có thể cấu trúc lại chương trình để sử dụng chỉ mục mảng nơi tôi đang sử dụng số 8 bit, do đó giảm xuống còn 2 byte cho mỗi cấu trúc, 9% khác bị cạo. Có vẻ như tôi có thể tha cho các chu kỳ. Tôi lặp lại qua 800 cấu trúc cứ sau 50 mili giây đồng thời xử lý các yêu cầu http không liên tục. Không vấn đề gì. Tôi đã đẩy các giới hạn ATmega với cái này, nhưng tôi dường như đang tiến sâu vào.
anthropomo

Câu trả lời:


8

Những gì bạn đang phải đối mặt là sự đánh đổi bộ nhớ thời gian cổ điển. Các trường bit sẽ nhỏ hơn trong bộ nhớ, nhưng sẽ mất nhiều thời gian hơn để hoạt động. Bạn có thể tính rằng bất kể bộ xử lý nào, các trường bit sẽ chậm hơn.

Bạn sử dụng từ hiệu quả , nhưng từ đó không có nghĩa nhất định nếu không có số liệu cho điều gì là tốt hay xấu. Khi bạn chỉ có 8 k RAM, sử dụng bộ nhớ rất tệ, thời gian có thể rẻ. Nếu bạn có các ràng buộc thời gian thực, thì việc sử dụng thời gian là không tốt và bộ nhớ có thể rẻ. Nói chung, bạn chỉ có thể mua theo cách của bạn ra khỏi sự đánh đổi này. Nói cách khác, khi bạn thấy cả thời gian và bộ nhớ đều tệ, hãy tiêu tiền mặt và sử dụng một con chip lớn hơn. Không có câu trả lời duy nhất cho những gì là tốt hay xấu. Đây là một phần lý do tại sao có rất nhiều sự lựa chọn cho vi điều khiển, mọi người lắp chip vào ứng dụng và ứng dụng cho chip.

Việc điền các bit của trường bit sẽ chậm hơn so với việc điền đầy đủ các byte. Lấy ví dụ

x = 5;
...
asimplestruct.len = x;
// vs
abitfield.len = x;

Trường hợp đơn giản đầu tiên sẽ chỉ:

  • tải giá trị của x vào một thanh ghi
  • lưu trữ nó vào byte cho len

Thứ hai làm một cái gì đó như:

  • tải giá trị hiện tại của abitfield
  • tải mặt nạ
  • xóa bit cho len
  • tải giá trị hiện tại của x
  • tải mặt nạ
  • xóa các bit không sử dụng của x
  • dịch chuyển các bit của x
  • hoặc là x với abitfield
  • lưu trữ giá trị hiện tại trở lại bộ nhớ

Nếu tất cả các hoạt động của bạn đang đóng gói dữ liệu vào trường bit hoặc giải nén ra khỏi bitfield, bạn sẽ mong đợi việc thực thi chậm hơn. Các trường bit là một loại nén - chúng có giá trị tích tắc.

Nhưng di chuyển xung quanh các trường bit sẽ nhanh hơn vì có ít byte hơn để tải và lưu trữ. Nếu bạn sắp xếp mảng này, số byte nhỏ hơn có thể là một lợi thế. Nếu bạn chuyển chúng qua cổng nối tiếp, kích thước nén của bitfield có thể là người chiến thắng.

Vì vậy, đối với câu hỏi của bạn:

Có một cách tốt để kiểm tra điều này?

Cách tốt nhất để kiểm tra điều này là viết các trường hợp thử nghiệm cho cả hai cách tiếp cận bằng cách sử dụng một mẫu phù hợp chặt chẽ với ứng dụng của bạn. Nó thực sự quan trọng kết hợp các hoạt động bạn thực hiện để quyết định xem sự khác biệt là không đáng kể hay đáng kể.

Khi thực hiện loại thử nghiệm tối ưu hóa này, chắc chắn sử dụng kiểm soát nguồn trên dự án của bạn. Bạn có thể tạo một kho lưu trữ GIT hoặc Mercurial cục bộ chỉ bằng vài cú nhấp chuột. Giữ một chuỗi các điểm kiểm tra cho phép bạn xé mã của mình để khám phá các hiệu ứng của các triển khai khác nhau. Nếu bạn rẽ sai, kho lưu trữ cho phép bạn chỉ cần quay lại điểm tốt cuối cùng và thử một đường dẫn khác.

(Lưu ý bên cạnh: Sự cân bằng bộ nhớ thời gian này cũng tồn tại theo hướng ngược lại. Nếu bạn xem qua các tùy chọn trình biên dịch cho bộ xử lý lớp máy tính để bàn, bạn sẽ tìm thấy một thứ gọi là đóng gói cấu trúc . Tùy chọn này cho phép bạn thêm các byte trống giữa các trường byte đơn sao cho chúng Giữ liên kết ở ranh giới từ hoặc từ kép. Điều này có vẻ điên rồ khi vứt bỏ RAM trên mục đích, nhưng trên các bộ xử lý có thanh ghi và bus rộng 16 hoặc 32 bit, do đó, hoạt động bộ nhớ được căn chỉnh từ hoặc từ có thể nhanh hơn hoạt động khôn ngoan của byte.)


3

Một cuộc thảo luận về các trường bit sẽ không được hoàn thành mà không đề cập đến điều đó:

Các cấu trúc có trường bit có thể được sử dụng như một cách di động để cố gắng giảm dung lượng lưu trữ cần thiết cho một cấu trúc (với chi phí có thể xảy ra là tăng không gian hướng dẫn và thời gian, cần thiết để truy cập vào các trường) hoặc như một cách không di động để mô tả một bố trí lưu trữ được biết đến ở cấp độ bit.

[Kernighan & Ritchie, Ngôn ngữ lập trình C, Ấn bản thứ hai , Phụ lục A-8.3]

Các áp phích khác, bao gồm cả chính bạn, đã đề cập đến sự đánh đổi không gian / thời gian. Nhưng điều đôi khi bị bỏ lỡ (có thể không liên quan đến ứng dụng của bạn -?) Là sự phụ thuộc hoàn toàn vào việc bố trí lưu trữ . Điều này quan trọng khi các ứng dụng chia sẻ dữ liệu này với một quy trình khác: Đọc hoặc ghi phần cứng, có các bài tập bit đăng ký nhất thiết phải được sửa; việc truyền dữ liệu tới một hệ thống khác (qua mạng hoặc thiết bị lưu trữ không thành vấn đề); hoặc đến một ứng dụng khác trên cùng hệ thống, có thể đã được biên dịch bằng trình biên dịch hoặc phiên bản khác của trình biên dịch.


1

Một mẹo nhỏ - Tôi thấy rằng cấu trúc không phải là 1B được căn chỉnh cho CPU 8 bit. Ở cuối cấu trúc của bạn, bạn có thể thêm chiều dài đệm 2 bit hoặc suy nghĩ lại về kích thước của trạng thái .

struct MyStruct
{
   uint8_t color : 3; // max value 7
   // uint8_t state : 1; *** moved, thanks Ignacio ***
   uint8_t area : 5; // m.v. 31
   uint8_t number;
   uint8_t len : 5; // m.v. 31
   uint8_t state : 1; // m.v. 1
   uint8_t padding : 2;
};

Thêm phần đệm không thay đổi bộ nhớ động tại thời gian biên dịch. Tha thứ cho sự thiếu hiểu biết của tôi ở đây, nhưng có vẻ như trình biên dịch sẽ chăm sóc phần đệm. Hoặc có điều gì đó xảy ra trong thời gian chạy để bù đắp nếu tôi không bao gồm phần đệm?
anthropomo

Đúng. Trong trường hợp này giống như một thực hành tốt.
soerium
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.