Việc dùng chuẩn liên kết danh sách định dạng thư mục có thể trở nên chậm khi mà số lượng tập tin bắt đầu tăng. Để tăng hiệu quả cho một hệ thống như vậy, một mảng băm được tạo ra cho phép nhanh chóng định vị tập tin cụ thể được tìm kiếm.
Bit EXT2_INDEX_FL trong cờ hiệu điều khiển thao tác (behaviour control flags) được thiết lập nếu indexed directory format được sử dụng.
Cấu trúc Index
Gốc của cây Index nằm ở block 0 của tập tin. Khoảng trống được dữ trữ cho cấp độ 2 của cây index trong block 1 đến 511 (block hệ thống tập tin 4KB). Block thư mục lá đươc gắn vào bắt đầu tại block 512, vì vậy đoạn cuối của thư mục trông giống như một thư mục Ext2 thông thường và có thể được xử lý một cách trực tiếp bởi trường ext2_readdir. Đối với các thư mục ít hơn 90KB có lỗ chạy từ block 1 đến 511, vì vậy một thư mục rỗng có hai block trong nó, mặc dù kích thước của nó xuất hiện khoảng 2MB trong một danh sách thư mục. Một thư mục trông như sau:
0: Root index block 1: Index block/0 2: Index block/0 ... 511: Index block/0 512: Dirent block 513: Dirent block ...
Mỗi index block bao gồm 512 index entry: hash, block
trong đó hash là một hash 32 bit với một cờ hiệu xung đột trong bit có ý nghĩa ít nhất của nó, và block là số block luận lý của một index của block lá, phụ thuộc vào cấp độ của cây.
Giá trị hash của index entry 0 không cần thiết bởi vì cấp độ của cây sẽ tạo ra nó, do đó nó được dùng để ghi việc tính các index entry trong một index block.
Block index gốc có định dạng giống như các block index khác, với 8 byte đầu tiên của nó dữ trữ một tiêu đề nhỏ:
1 byte header length (default: 8) 1 byte index type (default: 0) 1 byte hash version (default:0) 1 byte tree depth (default: 1)
Cách xử lý của header khác với bản đắp vá kèm theo một cách không đáng kể. Nói riêng, chỉ một cấp độ đơn của cây index (root) được bổ sung ở đây. Điều này trở nên đủ để điều khiển nhiều hơn 90,000 entry, vì vậy hiện nay nó cũng đủ. Khi một cấp độ 2 được thêm vào cây, khả năng này sẽ tăng lên đến khoảng 50 triệu entry, và không có gì ngăn ngừa việc dùng cấp độ thứ n.
Thuật toán tìm kiếm (Lookup Algorithm)
- Tính toán một hash của tên - Đọc index gốc
- Dùng tìm kiếm nhị phân để tìm index đầu tiên hoặc block lá có thể chứa hash đích (trong trật tự cây)
- Lập lại bước trên cho đến khi đạt được cấp độ thấp nhất của cây.
- Đọc block entry của thư mục lá và thực hiện tìm kiếm Ext2 thông thường trên nó. - Nếu tên được tìm thấy, trả về buffer và directory-entry của nó.
- Ngược lại, nếu bit xung đột của directory-entry tiếp theo được thiết lập, tiếp tục tìm kiếm trên block đã thành công.
Thuật toán chèn (Insert Algorithm)
Chèn vào các entry mới vào trong thư mục phức tạp hơn là tìm kiếm, vì cần phải tách các block lá khi chúng đầy, và thỏa mãm điều kiện là cho phép những xung đột khóa hash được điều khiển một cách chắc chắn và có hiệu quả. Tôi xin tóm tắt như sau:
- Dò index như là tìm kiếm.
- Nếu block lá đích đầy, chia nhỏ nó và ghi nhớ block sẽ nhận entry mới.
- Chèn thêm entry mới trong block lá sử dụng mã chèn directiry-entry Ext2 thông thường.
Tách (Splitting)
Tóm lại, khi một nút lá đầy và chúng ta muốn đặt một entry mới vào đó thì lá sẽ bị tách ra, và một phần khoảng trống hash của nó được chia thành nhiều phần. Cách dễ nhất để làm được điều này là sắp xếp các entry theo giá trị hash và cắt ở phần giữa của danh sách đã được sắp xếp. Thao tác này là log(number_of_entries_in_leaf) và không mất nhiều chi phí với điều kiện sử dụng thuật toán sắp xếp có hiệu quả. Tôi dùng CombSort cho việc này, mặc dù QuickSort cũng tốt cho trường hợp này bởi vì sự thực thi trường hợp trung bình quan trọng hơn trường hợp tệ nhất.
Một phương pháp khác là dự đoán một giá trị trung bình cho khóa hash, và việc chia phần có thể được làm theo thời gian tuyến tình, nhưng kết quả việc phân chia kém hơn khoảng trống khóa hash có giá trị hơn thuận lợi ít ỏi của thuật toán phân chia tuyến tính. Trong trường hợp bất kỳ, số entry cần sắp xếp được giới hạn bởi số lượng vừa khít trong một lá.
Các xung đột khóa (Key Collisions)
Việc điều khiển những chuỗi xung đột khóa hash có một vài phức tạp. Thật là tuyệt nếu tránh việc tách các chuỗi như vậy giữa các block, vì vậy điểm cắt của một block được điều chỉnh với cái này. Nhưng khả năng vẫn còn nếu block lắp đầy bởi các entry được băm một cách xác định, chuỗi có thể vẫn phải bị tách. Tình huống này được đánh dấu bằng cách đặt một số 1 vào bit bên dưới của entry index trỏ vào block kế thừa, cái mà được làm sáng tỏ một cách tự nhiên bởi sự thăm dò index như là một giá trị trung gian mà không có bất kỳ sự mã hóa đặc biệt nào. Vì vậy, việc điều khiển vấn đề xung đột bắt buộc không có xử lý thực sự, chỉ có mã mở rộng và và sự giảm bớt không đáng kể khoảng trống của khóa băm. Khoảng trống của khóa băm vẫn còn đủ cho số directory-entry có thể tưởng tượng được, lên đến hàng tỷ.
Hàm băm (Hash Function)
Các đặc tính chính xác của hàm Hash rất ảnh hưởng đến việc thực thi chiến lược index này. Một hàm hash dở sẽ dẫn đến nhiều sự xung đột hoặc sự phân chia khoảng trống hash dở. Để minh họa tại sao cái sau lại là một vấn đề, xét xem cái gì sẽ xảy ra khi một block được tách ra để mà nó bao phủ một vài giá trị hash nhất định. Xác suất các entry index sau đó cũng được băm tương tự, khoảng cách càng nhỏ thì
giá trị này càng nhỏ. Thực tế, khi một block được tách ra, nếu khoảng trống hash của nó quá nhỏ đến nỗi chỉ luôn đầy có một nửa, đó là một kết quả mà tôi quan sát ở thực tế.
Sau một số thử nghiệm, tôi đã có một hàm Hash cho ra một sự phân tán hợp lý các khóa hash dọc theo toàn bộ khoảng trống khóa 31 bit. Điều này làm tăng sự đầy trung bình của các block lá, đạt gần tới giá trị trung bình theo lý thuyết là đầy ¾.
Nhưng hàm hash hiện tại chỉ để dùng tạm, chờ một phiên bản tốt hơn dựa trên lý thuyết chắc chắn.
Sự thực thi (Performance)
Tóm lại, sự cải tiến khả năng thực thi trên Ext2 thông thường đã gây một sự bất ngờ. Với việc thực thi các thư mục rất nhỏ giống với Ext2 chuẩn, nhưng vì kích thước thư mục tăng theo Ext2 chuẩn một cách nhanh chóng làm xuất hiện bậc hai, trong khi htree-enhanced Ext2 tiếp tục lấy tỉ lệ một cách tuyến tính.
Uli Luckas chạy điểm chuẩn cho việc tạo tập tin theo các kích thước khác nhau của thư mục nằm trong dãy từ 10,000 đến 90,000 tập tin. Kết quả này rất hài lòng: toàn bộ thời gian tạo file gần như tuyến tính, chống lại việc tăng bậc hai của Ext2 thường. Thời gian được tạo như sau:
Figure 2-3. Việc thực thi Indexed Directories
Indexed Normal ======= ====== 10000 Files: 0m1.350s 0m23.670s 20000 Files: 0m2.720s 1m20.470s 30000 Files: 0m4.330s 3m9.320s 40000 Files: 0m5.890s 5m48.750s 50000 Files: 0m7.040s 9m31.270s 60000 Files: 0m8.610s 13m52.250s 70000 Files: 0m9.980s 19m24.070s 80000 Files: 0m12.060s 25m36.730s 90000 Files: 0m13.400s 33m18.550s
Các thư mục dễ dàng vừa khít bộ nhớ đệm, và thừa số giới hạn trong trường hợp Ext2 chuẩn là việc tìm các block directory trong bộ nhớ đệm buffer, và việc quét cấp độ thấp các entry directory. Trong trường hợp lập chỉ mục htree, thì số chi phí cần được xem xét, tất cả chúng hầu như được giới hạn. Có một vài cách tối ưu cần phải thực hiện:
- Dùng tìm kiếm bằng nhị phân thay vì tìm kiếm tuyến tính trong các nút chỉ thị ở phía trong. - Nếu chỉ có một block lá trong một thư mục, thì bỏ qua việc dò chỉ mục, đi thẳng đến block luôn. - Ánh xạ thư mục vào trang cache thay vì vào cache bộ nhớ đệm trung gian.
Mỗi sự tối ưu này sẽ tạo ra một hiệu quả được cải thiện, nhưng không có gì giống
như bước nhảy lớn từ N*2 đến Log512(N), ~=N. Trong lúc những tối ưu đó được áp
Đó là một hiệu quả không đáng kể khi mà thư mục đủ lớn để cần một level cấp 2 vì bộ lưu trữ sẽ rất nhỏ. Việc đi ngang qua các block dữ liệu của thư mục sẽ là một chi phí rất lớn, và một lần nữa, giá trị này có thể tăng lên bởi việc di chuyển các block vào trang cache.
Điển hình chúng ta sẽ đi qua 3 block để đọc hay viết một entry thư mục, và số đó tăng đến khoảng 4-5 với những thư mục lớn. Nhưng thực sự thì không có gì so sánh với Ext2 thông thường cái mà đi qua vài trăm block trong tình huống tương tự.