Một trong những trường hợp hữu ích nhất mà tôi tìm thấy đối với danh sách được liên kết hoạt động trong các trường quan trọng về hiệu suất như xử lý lưới và hình ảnh, công cụ vật lý và raytracing là khi sử dụng danh sách được liên kết thực sự cải thiện vị trí của tham chiếu và giảm phân bổ heap và đôi khi thậm chí còn giảm sử dụng bộ nhớ so với các lựa chọn thay thế đơn giản.
Bây giờ điều đó có vẻ giống như một oxymoron hoàn chỉnh mà các danh sách được liên kết có thể làm tất cả những điều đó vì chúng nổi tiếng là thường làm ngược lại, nhưng chúng có một thuộc tính duy nhất là mỗi nút danh sách có kích thước cố định và các yêu cầu về căn chỉnh mà chúng ta có thể khai thác để cho phép chúng sẽ được lưu trữ liên tục và được xóa trong thời gian không đổi theo những cách mà những thứ có kích thước thay đổi không thể.
Do đó, chúng ta hãy sử dụng một trường hợp mà chúng ta muốn thực hiện tương tự như việc lưu trữ một chuỗi có độ dài thay đổi chứa một triệu chuỗi con có độ dài thay đổi lồng nhau. Một ví dụ cụ thể là một lưới được lập chỉ mục lưu trữ một triệu đa giác (một số hình tam giác, một số hình tứ giác, một số hình ngũ giác, một số hình lục giác, v.v.) và đôi khi đa giác bị xóa khỏi bất kỳ vị trí nào trong lưới và đôi khi đa giác được xây dựng lại để chèn một đỉnh vào một đa giác hiện có hoặc loại bỏ một. Trong trường hợp đó, nếu chúng ta lưu trữ một triệu nhỏ std::vectors
, thì cuối cùng chúng ta sẽ phải đối mặt với việc phân bổ đống cho mỗi vectơ đơn lẻ cũng như việc sử dụng bộ nhớ có khả năng bùng nổ. Một triệu người nhỏ SmallVectors
có thể không gặp phải vấn đề này nhiều trong các trường hợp phổ biến, nhưng sau đó bộ đệm được phân bổ trước của chúng không được phân bổ riêng biệt theo đống có thể vẫn gây ra việc sử dụng bộ nhớ bùng nổ.
Vấn đề ở đây là một triệu std::vector
phiên bản sẽ cố gắng lưu trữ một triệu thứ có độ dài thay đổi. Những thứ có độ dài thay đổi có xu hướng muốn phân bổ heap vì chúng không thể được lưu trữ liên tục và bị loại bỏ một cách hiệu quả trong thời gian không đổi (ít nhất là theo cách đơn giản mà không có trình phân bổ rất phức tạp) nếu chúng không lưu trữ nội dung của mình ở nơi khác trên heap.
Nếu, thay vào đó, chúng tôi làm điều này:
struct FaceVertex
{
// Points to next vertex in polygon or -1
// if we're at the end of the polygon.
int next;
...
};
struct Polygon
{
// Points to first vertex in polygon.
int first_vertex;
...
};
struct Mesh
{
// Stores all the face vertices for all polygons.
std::vector<FaceVertex> fvs;
// Stores all the polygons.
std::vector<Polygon> polys;
};
... thì chúng tôi đã giảm đáng kể số lượng phân bổ heap và bỏ sót bộ nhớ cache. Thay vì yêu cầu phân bổ heap và bộ nhớ cache bắt buộc có khả năng bỏ lỡ cho mỗi đa giác mà chúng tôi truy cập, giờ đây chúng tôi chỉ yêu cầu phân bổ heap đó khi một trong hai vectơ được lưu trữ trong toàn bộ lưới vượt quá dung lượng của chúng (chi phí phân bổ). Và trong khi nỗ lực để đi từ đỉnh này sang đỉnh tiếp theo vẫn có thể khiến phần chia sẻ bộ nhớ cache của nó bị bỏ lỡ, nó vẫn thường ít hơn nếu mỗi đa giác lưu trữ một mảng động riêng biệt vì các nút được lưu trữ liền kề và có khả năng là một đỉnh lân cận có thể được truy cập trước khi loại bỏ (đặc biệt khi xem xét rằng nhiều đa giác sẽ thêm các đỉnh của chúng cùng một lúc, điều này làm cho phần sư tử của các đỉnh đa giác tiếp giáp hoàn hảo).
Đây là một ví dụ khác:
... nơi các ô lưới được sử dụng để tăng tốc va chạm hạt-hạt, chẳng hạn như 16 triệu hạt chuyển động trong mỗi khung hình. Trong ví dụ về lưới hạt đó, bằng cách sử dụng danh sách được liên kết, chúng ta có thể di chuyển một hạt từ ô lưới này sang ô lưới khác chỉ bằng cách thay đổi 3 chỉ số. Xóa khỏi một vectơ và đẩy trở lại một vectơ khác có thể đắt hơn đáng kể và giới thiệu nhiều phân bổ đống hơn. Các danh sách được liên kết cũng làm giảm bộ nhớ của một ô xuống 32-bit. Một vectơ, tùy thuộc vào việc triển khai, có thể định vị trước mảng động của nó đến điểm mà nó có thể mất 32 byte cho một vectơ trống. Nếu chúng ta có khoảng một triệu ô lưới, đó là một sự khác biệt.
... và đây là nơi tôi tìm thấy danh sách được liên kết hữu ích nhất hiện nay và đặc biệt tôi nhận thấy sự đa dạng "danh sách được liên kết được lập chỉ mục" hữu ích vì các chỉ số 32 bit giảm một nửa yêu cầu bộ nhớ của các liên kết trên máy 64 bit và chúng ngụ ý rằng các nút được lưu trữ liền kề trong một mảng.
Thường thì tôi cũng kết hợp chúng với danh sách miễn phí được lập chỉ mục để cho phép xóa và chèn liên tục ở bất kỳ đâu:
Trong trường hợp đó, next
chỉ mục sẽ trỏ đến chỉ mục miễn phí tiếp theo nếu nút đã bị xóa hoặc chỉ mục được sử dụng tiếp theo nếu nút chưa bị xóa.
Và đây là trường hợp sử dụng số một mà tôi tìm thấy cho các danh sách được liên kết những ngày này. Ví dụ, khi chúng ta muốn lưu trữ một triệu dãy con có độ dài thay đổi, mỗi dãy 4 phần tử (nhưng đôi khi các phần tử bị xóa và thêm vào một trong các dãy con này), danh sách được liên kết cho phép chúng ta lưu trữ 4 triệu các nút danh sách được liên kết liền kề thay vì 1 triệu vùng chứa được phân bổ từng vùng riêng lẻ: một vectơ khổng lồ, tức là, không phải một triệu cái nhỏ.