Bài 53.226 phút đọc
Sắp xếp trường để tiết kiệm bộ nhớ
Sau bài này bạn sẽ làm được
- Sắp xếp trường theo kích thước giảm dần
- Đo mức tiết kiệm trên một mảng lớn
- Dùng pahole để tìm chỗ lãng phí
- Biết khi nào thứ tự khai báo quan trọng hơn kích thước
Đổi thứ tự ba dòng khai báo, tiết kiệm ba mươi ba phần trăm bộ nhớ, không đổi một dòng logic nào. Đây là thay đổi có tỉ lệ lợi ích trên công sức cao nhất trong toàn bộ phần này.
#Hai mươi bốn xuống mười sáu
Cùng ba trường, hai thứ tự
struct Kem { char a; double b; char c; };
struct Tot { double b; char a; char c; };terminal
gcc -std=c11 -O2 -o bocuc.exe bocuc.c && ./bocuc.exe
sizeof(Kem) = 24 sizeof(Tot) = 16
Bản đồ byte của cả hai
struct Kem { char a; double b; char c; };
byte: 0 1 2 3 4 5 6 7 8 .. 15 16 17 .. 23
+----+--------------+-------------+----+-----------+
| a | DEM 7 | b | c | DEM 7 |
+----+--------------+-------------+----+-----------+
sizeof = 24, du lieu 10 byte, dem 14 byte
struct Tot { double b; char a; char c; };
byte: 0 .. 7 8 9 10 .. 15
+-----------+----+----+-----------+
| b | a | c | DEM 6 |
+-----------+----+----+-----------+
sizeof = 16, du lieu 10 byte, dem 6 byte
Tiet kiem 8 byte tren 24, tuc 33 phan tram.
Voi mang mot trieu phan tu: tiet kiem 8 MB.#Quy tắc sắp xếp
Thứ tự tự nhiên
typedef struct {
bool hoat_dong; /* 1 */
char ten[32]; /* 32 */
double diem; /* 8, can chinh 8 */
char lop[8]; /* 8 */
int ma; /* 4 */
bool tot_nghiep; /* 1 */
long ngay_sinh; /* 4 hoac 8 */
} SinhVien;
/* Bo cuc tren x86-64 Linux, noi long la 8 byte:
hoat_dong 0 1 byte
ten 1..32 32 byte
DEM 33..39 7 byte <- lo trong
diem 40..47 8 byte
lop 48..55 8 byte
ma 56..59 4 byte
tot_nghiep 60 1 byte
DEM 61..63 3 byte <- lo trong
ngay_sinh 64..71 8 byte
sizeof = 72, du lieu 62 byte, dem 10 byte */Sắp theo căn chỉnh giảm dần
typedef struct {
double diem; /* 8, can chinh 8 */
long ngay_sinh; /* 8 */
char ten[32]; /* 32, can chinh 1 */
char lop[8]; /* 8 */
int ma; /* 4 */
bool hoat_dong; /* 1 */
bool tot_nghiep; /* 1 */
} SinhVien;
/* Bo cuc:
diem 0..7 8 byte
ngay_sinh 8..15 8 byte
ten 16..47 32 byte
lop 48..55 8 byte
ma 56..59 4 byte
hoat_dong 60 1 byte
tot_nghiep 61 1 byte
DEM 62..63 2 byte
sizeof = 64, du lieu 62 byte, dem 2 byte
72 xuong 64. Tren mot trieu ban ghi: tiet kiem 8 MB. */#Đo trên mảng lớn
Đo bộ nhớ và tốc độ duyệt
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct { char a; double b; char c; } Kem;
typedef struct { double b; char a; char c; } Tot;
enum { N = 5000000 };
static double duyet_kem(const Kem *ds) {
double t = 0;
for (size_t i = 0; i < N; ++i) t += ds[i].b;
return t;
}
static double duyet_tot(const Tot *ds) {
double t = 0;
for (size_t i = 0; i < N; ++i) t += ds[i].b;
return t;
}
int main(void) {
printf("Kem: %d byte moi phan tu, %.1f MB cho %d phan tu\n",
(int)sizeof(Kem), (double)sizeof(Kem) * N / 1048576.0, N);
printf("Tot: %d byte moi phan tu, %.1f MB cho %d phan tu\n",
(int)sizeof(Tot), (double)sizeof(Tot) * N / 1048576.0, N);
Kem *a = calloc(N, sizeof *a);
Tot *b = calloc(N, sizeof *b);
if (a == NULL || b == NULL) return 1;
clock_t t0 = clock();
double s1 = duyet_kem(a);
clock_t t1 = clock();
double s2 = duyet_tot(b);
clock_t t2 = clock();
printf("duyet Kem: %.0f ms\n", (double)(t1 - t0) * 1000 / CLOCKS_PER_SEC);
printf("duyet Tot: %.0f ms\n", (double)(t2 - t1) * 1000 / CLOCKS_PER_SEC);
printf("(%.0f %.0f)\n", s1, s2);
free(a); free(b);
return 0;
}#Khi nào thứ tự khai báo quan trọng hơn
Kế thừa thủ công: trường đầu tiên là bắt buộc
typedef struct { int loai; } CoSo;
typedef struct {
CoSo co_so; /* PHAI la truong dau tien */
int ban_kinh;
} HinhTron;
typedef struct {
CoSo co_so; /* PHAI la truong dau tien */
int rong, cao;
} HinhChuNhat;
void ve(CoSo *h) {
switch (h->loai) {
case 1: ve_tron((HinhTron *)h); break;
case 2: ve_chu_nhat((HinhChuNhat *)h); break;
}
}
HinhTron t = { { 1 }, 5 };
ve((CoSo *)&t); /* HOP LE: con tro toi struct tro toi
thanh vien dau tien */
/* Neu ban sap xep lai va dua co_so xuong duoi thi phep ep kieu
nay thanh UB, va chuong trinh doc sai truong loai.
Nen: khi dung mau nay, THEM chu thich va _Static_assert: */
_Static_assert(offsetof(HinhTron, co_so) == 0, "co_so phai o dau");#Để công cụ làm giúp
# 1. pahole -R de xuat thu tu toi uu
$ pahole -R --reorganize duan.o
struct SinhVien {
double diem; /* 0 8 */
long ngay_sinh; /* 8 8 */
char ten[32]; /* 16 32 */
...
/* size: 64, cachelines: 1 */
/* saved 8 bytes! */
# 2. pahole --packable liet ke moi struct co the nen lai
$ pahole --packable duan.o
SinhVien 72 64 8
KetNoi 104 96 8
CauHinh 48 40 8
# ten, kich thuoc hien tai, kich thuoc toi uu, tiet kiem duoc
# 3. clang dump bo cuc
$ clang -Xclang -fdump-record-layouts -c bocuc.c
*** Dumping AST Record Layout
0 | struct Kem
0 | char a
8 | double b
16 | char c
| [sizeof=24, align=8]
# 4. GDB, da co san moi noi
(gdb) ptype/o struct SinhVienTự làm thử
- Đo
sizeofcủaKemvàTotvà vẽ bản đồ byte của cả hai. - Sắp xếp lại một struct trong dự án của bạn và đo mức tiết kiệm.
- Cấp phát năm triệu phần tử ở cả hai bố cục và đo thời gian duyệt.
- Chạy
pahole --packabletrên một dự án và tìm struct lãng phí nhất. - Viết macro
IN_TRUONGvà dùng nó để in bố cục ba struct. - Thêm
_Static_assertchốt trường đầu tiên cho một struct dùng kế thừa thủ công.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
{ char; double; char; }là 24 byte,{ double; char; char; }là 16 byte. Cùng dữ liệu, khác thứ tự.- Quy tắc là sắp theo căn chỉnh giảm dần, và mảng
charcó căn chỉnh 1 dù kích thước lớn. - Lợi ích tốc độ đến từ việc nhiều phần tử vào chung một dòng bộ đệm, và thường lớn hơn lợi ích bộ nhớ.
- Bốn trường hợp không được sắp xếp lại: struct công khai, thanh ghi phần cứng, kế thừa thủ công, và khi thứ tự có nghĩa.
pahole -Rđề xuất thứ tự tối ưu, vàpahole --packableliệt kê mọi struct đáng sửa.