Là mảng không liên tục thực hiện?


12

Trong C #, khi người dùng tạo List<byte>và thêm byte cho nó, có khả năng nó hết dung lượng và cần phân bổ nhiều không gian hơn. Nó phân bổ gấp đôi (hoặc một số số nhân khác) kích thước của mảng trước đó, sao chép các byte qua và loại bỏ tham chiếu đến mảng cũ. Tôi biết rằng danh sách này tăng theo cấp số nhân vì mỗi phân bổ đều đắt đỏ và điều này giới hạn nó trong O(log n)phân bổ, trong đó chỉ cần thêm 10các mục bổ sung mỗi lần sẽ dẫn đến O(n)phân bổ.

Tuy nhiên, đối với kích thước mảng lớn có thể có rất nhiều không gian bị lãng phí, có thể gần một nửa mảng. Để giảm bộ nhớ, tôi đã viết một lớp tương tự NonContiguousArrayListsử dụng List<byte>làm kho lưu trữ dự phòng nếu có ít hơn 4 MB trong danh sách, sau đó nó sẽ phân bổ thêm các mảng 4 MB byte nhưNonContiguousArrayList tăng kích thước.

không giống List<byte> các mảng này không liền kề nhau nên không có sự sao chép dữ liệu xung quanh, chỉ cần phân bổ thêm 4M. Khi một mục được tra cứu, chỉ mục được chia cho 4M để lấy chỉ mục của mảng chứa mục đó, sau đó modulo 4M để lấy chỉ mục trong mảng.

Bạn có thể chỉ ra vấn đề với phương pháp này? Đây là danh sách của tôi:

  • Các mảng không liên tục không có bộ nhớ cache dẫn đến hiệu năng kém. Tuy nhiên, ở kích thước khối 4M, có vẻ như sẽ có đủ địa phương để lưu trữ tốt.
  • Truy cập vào một mục không hoàn toàn đơn giản, có thêm một mức độ gián tiếp. Điều này sẽ được tối ưu hóa đi? Nó sẽ gây ra vấn đề bộ nhớ cache?
  • Vì có sự tăng trưởng tuyến tính sau khi đạt đến giới hạn 4M, bạn có thể có nhiều phân bổ hơn so với thông thường (giả sử, tối đa 250 phân bổ cho 1GB bộ nhớ). Không có bộ nhớ bổ sung được sao chép sau 4M, tuy nhiên tôi không chắc việc phân bổ thêm có đắt hơn so với sao chép bộ nhớ lớn hay không.

8
Bạn đã sử dụng hết lý thuyết (đã lưu vào bộ nhớ cache, thảo luận về độ phức tạp tiệm cận), tất cả những gì còn lại là cắm các tham số (ở đây, 4 triệu mục trên mỗi danh sách phụ) và có thể tối ưu hóa vi mô. Bây giờ là lúc để điểm chuẩn, bởi vì không sửa chữa phần cứng và triển khai, có quá ít dữ liệu để thảo luận về hiệu suất hơn nữa.

3
Nếu bạn đang làm việc với hơn 4 triệu thành phần trong một bộ sưu tập, tôi hy vọng rằng tối ưu hóa vi mô container là điều ít quan tâm nhất về hiệu suất của bạn.
Telastyn

2
Những gì bạn mô tả tương tự như một danh sách liên kết không được kiểm soát (với các nút rất lớn). Khẳng định của bạn rằng họ không có bộ nhớ cache cục bộ là hơi sai. Chỉ có rất nhiều mảng phù hợp bên trong một dòng bộ đệm; giả sử 64 byte. Vì vậy, cứ 64 byte bạn sẽ có một lỗi nhớ cache. Bây giờ hãy xem xét một danh sách được liên kết không được kiểm soát có các nút chính xác là một số bội của 64 byte lớn (bao gồm cả tiêu đề đối tượng để thu gom rác). Bạn vẫn chỉ nhận được một bộ nhớ cache cứ sau 64 byte và thậm chí sẽ không có vấn đề gì khi các nút không liền kề trong bộ nhớ.
Doval

@Doval Đây thực sự không phải là một danh sách được liên kết không được kiểm soát, vì các khối 4M được lưu trữ trong một mảng, do đó, việc truy cập bất kỳ phần tử nào là O (1) chứ không phải O (n / B) trong đó B là kích thước khối.

2
@ user2313838 Nếu có 1000 MB bộ nhớ và một mảng 350 MB, bộ nhớ cần để tăng mảng sẽ là 1050 MB, lớn hơn những gì có sẵn, đó là vấn đề chính, giới hạn hiệu quả của bạn là 1/3 tổng dung lượng của bạn. TrimExcesssẽ chỉ giúp khi danh sách đã được tạo và thậm chí sau đó nó vẫn cần đủ dung lượng cho bản sao.
noisecapella

Câu trả lời:


5

Ở quy mô bạn đã đề cập, mối quan tâm hoàn toàn khác với những gì bạn đã đề cập.

Bộ nhớ cache cục bộ

  • Có hai khái niệm liên quan:
    1. Địa phương, việc sử dụng lại dữ liệu trên cùng một dòng bộ đệm (địa phương không gian) đã được truy cập gần đây (địa phương tạm thời)
    2. Tự động tìm nạp trước bộ đệm (phát trực tuyến).
  • Ở quy mô bạn đã đề cập (hàng trăm MB đến gigabyte, trong các khối 4 MB), hai yếu tố có liên quan nhiều đến mẫu truy cập phần tử dữ liệu của bạn hơn là bố cục bộ nhớ.
  • Dự đoán (không biết gì) của tôi là về mặt thống kê có thể không có nhiều khác biệt về hiệu năng so với phân bổ bộ nhớ liền kề khổng lồ. Không được, không mất.

Mẫu truy cập phần tử dữ liệu

  • bài viết này minh họa trực quan cách các mẫu truy cập bộ nhớ sẽ ảnh hưởng đến hiệu suất.
  • Nói tóm lại, hãy nhớ rằng nếu thuật toán của bạn đã bị tắc nghẽn bởi băng thông bộ nhớ, cách duy nhất để cải thiện hiệu suất là thực hiện nhiều công việc hữu ích hơn với dữ liệu đã được tải vào bộ đệm.
  • Nói cách khác, ngay cả khi YourList[k]YourList[k+1]có xác suất cao liên tiếp (một trong bốn triệu cơ hội không có), thực tế đó sẽ không giúp ích gì cho hiệu suất nếu bạn truy cập danh sách của mình một cách ngẫu nhiên, hoặc trong những bước tiến lớn không thể đoán trước, ví dụwhile { index += random.Next(1024); DoStuff(YourList[index]); }

Tương tác với hệ thống GC

  • Theo tôi, đây là nơi bạn nên tập trung vào nhất.
  • Tối thiểu, hiểu cách thiết kế của bạn sẽ tương tác với:
  • Tôi không am hiểu về các chủ đề này nên tôi sẽ để người khác đóng góp.

Chi phí tính toán bù địa chỉ

  • Mã C # điển hình đã thực hiện rất nhiều tính toán bù địa chỉ, do đó, chi phí bổ sung từ lược đồ của bạn sẽ không tệ hơn mã C # điển hình làm việc trên một mảng.
    • Hãy nhớ rằng mã C # cũng kiểm tra phạm vi mảng; và thực tế này không ngăn C # đạt được hiệu năng xử lý mảng tương đương với mã C ++.
    • Lý do là hiệu năng chủ yếu bị tắc nghẽn bởi băng thông bộ nhớ.
    • Mẹo để tối đa hóa tiện ích từ băng thông bộ nhớ là sử dụng các hướng dẫn SIMD cho các hoạt động đọc / ghi bộ nhớ. Cả C # điển hình và C ++ điển hình đều không làm điều này; bạn phải dùng đến thư viện hoặc các tiện ích ngôn ngữ.

Để minh họa tại sao:

  • Làm địa chỉ tính toán
  • (Trong trường hợp của OP, tải địa chỉ cơ sở chunk (đã có trong bộ đệm) và sau đó thực hiện thêm tính toán địa chỉ)
  • Đọc từ / ghi vào địa chỉ phần tử

Bước cuối cùng vẫn chiếm phần thời gian của con sư tử.

Đề nghị cá nhân

  • Bạn có thể cung cấp một CopyRangechức năng, hoạt động giống như Array.Copychức năng nhưng sẽ hoạt động giữa hai phiên bản của bạn NonContiguousByteArrayhoặc giữa một phiên bản khác và bình thường khác byte[]. các chức năng này có thể sử dụng mã SIMD (C ++ hoặc C #) để tối đa hóa việc sử dụng băng thông bộ nhớ và sau đó mã C # của bạn có thể hoạt động trên phạm vi được sao chép mà không cần tính toán nhiều hội nghị hoặc địa chỉ.

Mối quan tâm về khả năng sử dụng và khả năng tương tác

  • Rõ ràng bạn không thể sử dụng điều này NonContiguousByteArrayvới bất kỳ thư viện C #, C ++ hoặc ngôn ngữ nước ngoài nào mong đợi các mảng byte liền kề hoặc mảng byte có thể được ghim.
  • Tuy nhiên, nếu bạn viết thư viện tăng tốc C ++ của riêng bạn (với P / Invoke hoặc C ++ / CLI), bạn có thể chuyển vào danh sách các địa chỉ cơ sở của một số khối 4 MB vào mã bên dưới.
    • Ví dụ: nếu bạn cần cấp quyền truy cập cho các phần tử bắt đầu từ (3 * 1024 * 1024)và kết thúc tại (5 * 1024 * 1024 - 1), điều này có nghĩa là quyền truy cập sẽ trải dài chunk[0]chunk[1]. Sau đó, bạn có thể xây dựng một mảng (kích thước 2) của các mảng byte (kích thước 4M), ghim các địa chỉ khối này và chuyển chúng đến mã bên dưới.
  • Một mối quan tâm về khả năng sử dụng khác là bạn sẽ không thể thực hiện IList<byte>giao diện một cách hiệu quả: InsertRemovesẽ mất quá nhiều thời gian để xử lý vì chúng sẽ yêu cầuO(N) thời gian.
    • Trên thực tế, có vẻ như bạn không thể thực hiện bất kỳ điều gì khác ngoài IEnumerable<byte>, tức là nó có thể được quét liên tục và đó là điều đó.

2
Bạn dường như đã bỏ lỡ lợi thế chính của cấu trúc dữ liệu, đó là cho phép bạn tạo các danh sách rất lớn, mà không hết bộ nhớ. Khi mở rộng Danh sách <T>, nó cần một mảng mới lớn gấp đôi mảng cũ và cả hai phải có mặt trong bộ nhớ cùng một lúc.
Frank Hileman

6

Điều đáng chú ý là C ++ đã có cấu trúc tương đương theo Standard , std::deque. Hiện tại, nó được đề xuất là lựa chọn mặc định cho việc cần một chuỗi các công cụ truy cập ngẫu nhiên.

Thực tế là bộ nhớ liền kề gần như hoàn toàn không cần thiết một khi dữ liệu đi qua một kích thước nhất định - một dòng bộ đệm chỉ là 64 byte và kích thước trang chỉ là 4-8KB (giá trị điển hình hiện tại). Khi bạn bắt đầu nói về một vài MB, nó thực sự là một vấn đề đáng lo ngại. Điều tương tự cũng đúng với chi phí phân bổ. Giá của việc xử lý tất cả dữ liệu đó - thậm chí chỉ cần đọc nó - còn thấp hơn giá của phân bổ.

Lý do duy nhất khác để lo lắng về vấn đề này là vì giao tiếp với API C. Nhưng dù sao bạn cũng không thể lấy một con trỏ tới bộ đệm của Danh sách để không phải lo lắng ở đây.


Điều đó thật thú vị, tôi không biết rằng dequeđã có một triển khai tương tự
noisecapella

Ai hiện đang giới thiệu std :: deque? Bạn có thể cung cấp một nguồn? Tôi luôn nghĩ rằng std :: vector là lựa chọn mặc định được đề xuất.
Teimpz

std::dequeTrên thực tế rất nản lòng, một phần vì việc triển khai thư viện chuẩn MS rất tệ.
Sebastian Redl

3

Khi các khối bộ nhớ được phân bổ tại các thời điểm khác nhau, như trong các mảng con trong cấu trúc dữ liệu của bạn, chúng có thể được đặt cách xa nhau trong bộ nhớ. Đây có phải là vấn đề hay không phụ thuộc vào CPU và rất khó để dự đoán nữa. Bạn phải kiểm tra nó.

Đây là một ý tưởng tuyệt vời, và nó là một ý tưởng tôi đã sử dụng trong quá khứ. Tất nhiên, bạn chỉ nên sử dụng quyền hạn hai cho kích thước mảng con của mình và dịch chuyển bit để phân chia (có thể xảy ra như một phần của tối ưu hóa). Tôi thấy loại cấu trúc này chậm hơn một chút, trong đó trình biên dịch có thể tối ưu hóa một mảng đơn hướng dễ dàng hơn. Bạn phải kiểm tra, vì các loại tối ưu hóa này luôn thay đổi.

Ưu điểm chính là bạn có thể chạy gần đến giới hạn trên của bộ nhớ trong hệ thống của mình, miễn là bạn sử dụng các loại cấu trúc này một cách nhất quán. Miễn là bạn đang làm cho cấu trúc dữ liệu của mình lớn hơn và không tạo ra rác, bạn sẽ tránh các bộ sưu tập rác bổ sung sẽ xảy ra cho Danh sách thông thường. Đối với một danh sách khổng lồ, nó có thể tạo ra một sự khác biệt lớn: sự khác biệt giữa tiếp tục chạy và hết bộ nhớ.

Việc phân bổ bổ sung chỉ là một vấn đề nếu các mảng mảng phụ của bạn nhỏ, bởi vì có phân bổ bộ nhớ trong mỗi phân bổ mảng.

Tôi đã tạo các cấu trúc tương tự cho từ điển (bảng băm). Từ điển được cung cấp bởi khung .net có cùng vấn đề với Danh sách. Từ điển khó hơn ở chỗ bạn cũng cần tránh luyện lại.


Một bộ sưu tập nén có thể nén các khối cạnh nhau.
DeadMG

@DeadMG Tôi đã đề cập đến tình huống không thể xảy ra: có những khối khác ở giữa, đó không phải là rác. Với Danh sách <T>, bạn được đảm bảo bộ nhớ liền kề cho mảng của mình. Với một danh sách chunk, bộ nhớ chỉ tiếp giáp trong một đoạn, trừ khi bạn có tình huống nén may mắn mà bạn đề cập. Nhưng một nén cũng có thể yêu cầu di chuyển nhiều dữ liệu xung quanh và các mảng lớn đi vào Heap đối tượng lớn. Nó phức tạp.
Frank Hileman

2

Với kích thước khối 4M, ngay cả một khối không được đảm bảo liền kề trong bộ nhớ vật lý; nó lớn hơn kích thước trang VM thông thường. Địa phương không có ý nghĩa ở quy mô đó.

Bạn sẽ phải lo lắng về sự phân mảnh heap: nếu việc phân bổ xảy ra sao cho các khối của bạn phần lớn không liền kề trong heap, thì khi chúng được thu hồi bởi GC, bạn sẽ kết thúc với một đống có thể quá phân mảnh để phù hợp với phân bổ tiếp theo. Đó thường là một tình huống tồi tệ hơn vì các lỗi sẽ xảy ra ở những nơi không liên quan và có thể buộc phải khởi động lại ứng dụng.


Các GC nén không bị phân mảnh.
DeadMG

Điều này là đúng, nhưng tính năng nén LOH chỉ khả dụng kể từ .NET 4.5 nếu tôi nhớ lại chính xác.
dùng2313838

Nén heap cũng có thể phải chịu nhiều chi phí hơn so với hành vi sao chép trên tái phân bổ của tiêu chuẩn List.
dùng2313838

Một đối tượng đủ lớn và có kích thước phù hợp dù sao cũng không bị phân mảnh một cách hiệu quả.
DeadMG

2
@DeadMG: Mối quan tâm thực sự với việc nén GC (với sơ đồ 4 MB này) là có thể sẽ tốn thời gian vô ích để di chuyển xung quanh những chiếc bánh bò 4 MB này. Kết quả là nó có thể dẫn đến tạm dừng GC lớn. Vì lý do này, khi sử dụng sơ đồ 4 MB này, điều quan trọng là phải theo dõi số liệu thống kê quan trọng của GC để xem nó đang làm gì và thực hiện các hành động khắc phục.
rwong

1

Tôi xoay quanh một số phần trung tâm nhất của cơ sở mã của tôi (một công cụ ECS) xung quanh loại cấu trúc dữ liệu mà bạn đã mô tả, mặc dù nó sử dụng các khối liền kề nhỏ hơn (giống như 4 kilobyte thay vì 4 megabyte).

nhập mô tả hình ảnh ở đây

Nó sử dụng danh sách miễn phí gấp đôi để đạt được các lần chèn và xóa liên tục với một danh sách miễn phí cho các khối miễn phí đã sẵn sàng để được chèn vào (các khối không đầy đủ) và danh sách không có phụ trong khối cho các chỉ mục trong khối đó sẵn sàng để được khai hoang khi chèn.

Tôi sẽ đề cập đến những ưu và nhược điểm của cấu trúc này. Hãy bắt đầu với một số khuyết điểm vì có một số trong số họ:

Nhược điểm

  1. Mất khoảng 4 lần để chèn một vài trăm triệu phần tử vào cấu trúc này hơn std::vector (một cấu trúc hoàn toàn liền kề). Và tôi khá giỏi trong việc tối ưu hóa vi mô nhưng về mặt khái niệm còn nhiều việc phải làm vì trường hợp phổ biến trước tiên phải kiểm tra khối miễn phí ở đầu danh sách miễn phí khối, sau đó truy cập vào khối và bật chỉ mục miễn phí từ khối danh sách miễn phí, viết phần tử ở vị trí miễn phí, sau đó kiểm tra xem khối đã đầy chưa và bật khối từ danh sách miễn phí khối nếu có. Đây vẫn là một hoạt động liên tục nhưng với hằng số lớn hơn nhiều so với đẩy lùi std::vector.
  2. Mất khoảng gấp đôi thời gian khi truy cập các phần tử bằng cách sử dụng mẫu truy cập ngẫu nhiên được cung cấp thêm số học để lập chỉ mục và lớp bổ sung thêm.
  3. Truy cập tuần tự không ánh xạ hiệu quả đến thiết kế của trình vòng lặp vì trình vòng lặp phải thực hiện phân nhánh bổ sung mỗi khi nó tăng lên.
  4. Nó có một chút chi phí bộ nhớ, thường là khoảng 1 bit cho mỗi phần tử. 1 bit cho mỗi phần tử nghe có vẻ không nhiều, nhưng nếu bạn đang sử dụng phần tử này để lưu trữ một triệu số nguyên 16 bit, thì đó là sử dụng bộ nhớ nhiều hơn 6,25% so với một mảng nhỏ gọn hoàn hảo. Tuy nhiên, trong thực tế, điều này có xu hướng sử dụng ít bộ nhớ hơn std::vectortrừ khi bạn đang nén vectorđể loại bỏ dung lượng dư thừa mà nó dự trữ. Ngoài ra tôi thường không sử dụng nó để lưu trữ các yếu tố tuổi teen như vậy.

Ưu

  1. Truy cập tuần tự bằng cách sử dụng một for_eachhàm có phạm vi xử lý gọi lại của các phần tử trong một khối gần như cạnh tranh với tốc độ truy cập tuần tự với std::vector(chỉ giống như chênh lệch 10%), do đó, nó không hiệu quả hơn trong các trường hợp sử dụng hiệu suất quan trọng nhất đối với tôi ( hầu hết thời gian dành cho động cơ ECS là truy cập tuần tự).
  2. Nó cho phép loại bỏ thời gian liên tục từ giữa với các khối sắp xếp cấu trúc khi chúng trở nên hoàn toàn trống rỗng. Kết quả là nó thường khá tốt trong việc đảm bảo cấu trúc dữ liệu không bao giờ sử dụng nhiều bộ nhớ hơn mức cần thiết.
  3. Nó không làm mất hiệu lực các chỉ số đối với các phần tử không được loại bỏ trực tiếp khỏi vùng chứa vì nó chỉ để lại các lỗ hổng bằng cách sử dụng cách tiếp cận danh sách miễn phí để lấy lại các lỗ đó sau khi chèn tiếp theo.
  4. Bạn không phải lo lắng quá nhiều về việc hết bộ nhớ ngay cả khi cấu trúc này chứa một số lượng lớn các yếu tố, vì nó chỉ yêu cầu các khối liền kề nhỏ không gây ra thách thức cho HĐH để tìm ra một số lượng lớn không sử dụng liền kề trang.
  5. Nó cho vay chính nó để đồng thời và an toàn luồng mà không khóa toàn bộ cấu trúc, vì các hoạt động thường được tập trung vào các khối riêng lẻ.

Bây giờ một trong những ưu điểm lớn nhất đối với tôi là nó trở nên tầm thường khi tạo ra một phiên bản bất biến của cấu trúc dữ liệu này, như thế này:

nhập mô tả hình ảnh ở đây

Kể từ đó, điều đó đã mở ra tất cả các loại cửa để viết thêm các chức năng mà không có tác dụng phụ giúp dễ dàng đạt được sự an toàn ngoại lệ, an toàn luồng, v.v. cấu trúc dữ liệu này trong nhận thức muộn và tình cờ, nhưng có thể nói là một trong những lợi ích tốt nhất mà nó đã có được vì nó giúp duy trì codebase dễ dàng hơn nhiều.

Các mảng không liên tục không có bộ nhớ cache dẫn đến hiệu năng kém. Tuy nhiên, ở kích thước khối 4M, có vẻ như sẽ có đủ địa phương để lưu trữ tốt.

Địa phương của tài liệu tham khảo không phải là điều mà bạn quan tâm ở các khối có kích thước đó, chứ đừng nói đến các khối 4 kilobyte. Một dòng bộ đệm chỉ là 64 byte thông thường. Nếu bạn muốn giảm các lỗi bộ nhớ cache, thì chỉ cần tập trung vào việc sắp xếp các khối đó đúng cách và ưu tiên các mẫu truy cập tuần tự hơn khi có thể.

Một cách rất nhanh để biến một mẫu bộ nhớ truy cập ngẫu nhiên thành một mẫu tuần tự là sử dụng một bitet. Giả sử bạn có một số lượng lớn các chỉ số và chúng theo thứ tự ngẫu nhiên. Bạn chỉ có thể cày qua chúng và đánh dấu các bit trong bitet. Sau đó, bạn có thể lặp qua bitet của mình và kiểm tra byte nào khác không, kiểm tra, giả sử, 64 bit mỗi lần. Khi bạn gặp một tập hợp 64 bit trong đó ít nhất một bit được đặt, bạn có thể sử dụng các hướng dẫn FFS để nhanh chóng xác định các bit nào được đặt. Các bit cho bạn biết những chỉ số nào bạn nên truy cập, ngoại trừ bây giờ bạn nhận được các chỉ mục được sắp xếp theo thứ tự.

Điều này có một số chi phí nhưng có thể là một trao đổi đáng giá trong một số trường hợp, đặc biệt là nếu bạn sẽ lặp đi lặp lại các chỉ số này nhiều lần.

Truy cập vào một mục không hoàn toàn đơn giản, có thêm một mức độ gián tiếp. Điều này sẽ được tối ưu hóa đi? Nó sẽ gây ra vấn đề bộ nhớ cache?

Không, nó không thể được tối ưu hóa đi. Truy cập ngẫu nhiên, ít nhất, sẽ luôn có giá cao hơn với cấu trúc này. Nó thường không làm tăng bộ nhớ cache của bạn nhiều như vậy mặc dù bạn sẽ có xu hướng có được tính cục bộ cao theo thời gian với các mảng con trỏ tới các khối, đặc biệt nếu các đường dẫn thực thi trường hợp thông thường của bạn sử dụng các mẫu truy cập tuần tự.

Vì có sự tăng trưởng tuyến tính sau khi đạt đến giới hạn 4M, bạn có thể có nhiều phân bổ hơn so với thông thường (giả sử, tối đa 250 phân bổ cho 1GB bộ nhớ). Không có bộ nhớ bổ sung được sao chép sau 4M, tuy nhiên tôi không chắc việc phân bổ thêm có đắt hơn so với sao chép bộ nhớ lớn hay không.

Trong thực tế, việc sao chép thường nhanh hơn vì đây là trường hợp hiếm gặp, chỉ xảy ra một log(N)/log(2)số lần như tổng số lần trong khi đồng thời đơn giản hóa trường hợp phổ biến bẩn mà bạn có thể viết một phần tử vào mảng nhiều lần trước khi nó trở nên đầy đủ và cần được phân bổ lại. Vì vậy, thông thường, bạn sẽ không nhận được các phần chèn nhanh hơn với loại cấu trúc này bởi vì công việc thông thường đắt hơn ngay cả khi nó không phải xử lý trường hợp hiếm hoi đắt đỏ đó của việc phân bổ lại các mảng lớn.

Sức hấp dẫn chính của cấu trúc này đối với tôi bất chấp tất cả các nhược điểm là giảm sử dụng bộ nhớ, không phải lo lắng về OOM, có thể lưu trữ các chỉ số và con trỏ không bị vô hiệu, đồng thời và không thay đổi. Thật tuyệt khi có một cấu trúc dữ liệu nơi bạn có thể chèn và xóa mọi thứ trong thời gian liên tục trong khi nó tự làm sạch cho bạn và không làm mất hiệu lực các con trỏ và chỉ mục vào cấu trúc.

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.