Trễ trong bộ nhớ 2016

Một phần của tài liệu (LUẬN văn THẠC sĩ) tối ưu hóa truy vấn tìm đường ngắn nhất trên đồ thị động quy mô lớn (Trang 35 - 47)

Thi gian (ns)

Ghi chú

L1 cache 0.5 >2 ALU instruction latency

Branch mispredict 3

L2 cache reference 4 8 x L1 cache

Mutex lock/unlock 17

Main memory reference 100 25x L2 cache, 200x L1 cache

Trong đồ thị, cách đơn giản và thuận tiện nhất để biểu diễn các đỉnh của đồ thị là định nghĩa nó dưới dạng một số nguyên dương. Do vậy, nếu đồ thị có |V| đỉnh thì các đỉnh của nó sẽ có định danh từ 0 đến |V| - 1. Để biểu diễn các đỉnh trong đồ thị trên máy tính, như đã trình bày ở mục 1.1.3, có 4 phương pháp chính

sau: danh sách cạnh, danh sách kề, ma trận liên thuộc và ma trận kề. Bởi vì quy mô đồ thị của chúng ta rất lớn, số lượng cạnh và đỉnh lên tới vài triệu, cùng với ràng buộc về tài nguyên hệ thống nên cách biểu diễn bởi ma trận liên thuộc hay ma trận kề không còn phù hợp (không gian lưu trữ cần thiết là Θ(|V| x |E|) và Θ(|V|2) tương ướng). Biểu diễn bằng danh sách cạnh tốn ít không gian lưu trữ nhất Θ(|E|) và đơn giản nhất, tuy nhiên, với cách biểu diễn quá trình tìm đường đi ngắn nhất cần duyệt tất cả các cạnh liền kề của một đỉnh sẽ mất thời gian khá lớn (O(m)). Do vậy, danh sách kề chính là cách thích hợp nhất để biểu diễn đồ thị quy mô lớn có nhiều thay đổi bởi vì không gian lưu trữ tương đối nhỏ (Θ(|V| + |E|) và thuận tiện để thêm hoặc xóa đỉnh, cạnh.

Trong đơn đồ thị có hướng, không trọng số, đồ thị có thể được biểu diễn theo hai danh sách kề như sau:

Danh sách k các đỉnh vào ca đồ th

Đồ thị sẽ được biểu diễn bởi một danh sách các đỉnh vào của các nốt liên tiếp nhau (incoming_edges) và một mảng chỉ số vào (incoming_index) để có thể lấy được danh sách các đỉnh vào của một nốt trong truy vấn tìm đường ngắn nhất. Vị trí đỉnh vào đầu tiên của nốt N sẽ được lưu trữ tại vị trí N trong mảng chỉ số vào (inconming_index[N]). Thêm vào đó, giá trị số lượng đỉnh vào của một đỉnh cũng được lưu trữ để thuận tiện cho việc duyệt sau này. Để tăng tỉ lệ cache hit, giá trị số lượng đỉnh vào này cần được lưu trữ liền kề với vị trí đỉnh vào đầu tiên của một nốt. Cụ thể, một cặp (vị trí đỉnh vào đầu tiên, tổng số đỉnh vào) được lưu trữ trong mảng chỉ số vào incoming_index. Khi đó, chúng ta có thể lấy giá trị đỉnh vào đầu tiên và số lượng đỉnh vào của đỉnh N tại vị trí incoming_index[Nx2] và incoming_index[Nx2 + 1] tương ứng.

Ví dụ: đồ thị ở Hình 2.1 được biểu diễn theo danh sách kề các đỉnh vào như sau:

Incoming_edges: [3, 4, 1, 2, 2]

Incoming_index: [(0,0), (0,2), (2,1), (3,1), (4,1)]

Danh sách k các đỉnh ra ca đồ th

Tương tự như danh sách kề các đỉnh vào của đồ thị, đồ thị sẽ được biểu diễn bởi một danh sách các đỉnh ra của các nốt liên tiếp nhau (outgoing_edges) và một mảng chỉ số ra (outgoing_index) để có thể lấy được danh sách các đỉnh ra của một nốt trong truy vấn tìm đường đi ngắn nhất. Vị trí đỉnh ra đầu tiên của nốt N sẽ được lưu trữ tại vị trí N trong mảng chỉ số ra (outgoing_index[N]). Thêm vào đó, giá trị số lượng đỉnh ra của một đỉnh cũng được lưu trữ để thuận tiện cho việc

duyệt sau này. Để tăng tỉ lệ cache hit, giá trị số lượng đỉnh ra này cần được lưu trữ liền kề với vị trí đỉnh ra đầu tiên của một nốt. Cụ thể, một cặp (vị trí đỉnh ra đầu tiên, tổng số đỉnh ra) được lưu trữ trong mảng chỉ số outgoing_index. Khi đó, chúng ta có thể lấy giá trị đỉnh ra đầu tiên và số lượng đỉnh ra của đỉnh N tại vị trí outgoing_index[Nx2] và outgoing_index[Nx2 + 1] tương ứng.

Ví dụ: đồ thị ở Hình 2.1 được biểu diễn theo danh sách kề các đỉnh ra như sau:

Outgoing_edges: [2, 3, 4, 1, 1]

Outgoing_index: [(0,0), (0,1), (1,2), (3,1), (4,1)]

Từ những ý tưởng nêu trên, đồ thị được mô hình hóa dữ liệu dưới dạng theo cấu trúc danh sách kề, được miêu tả cụ thể như sau:

- Mỗi đỉnh được biểu diễn bởi một số nguyên dương.

- Tất cả các đỉnh vào và đỉnh ra được tổ chức dưới dạng danh sách kề. - Một mảng lưu vị trí bắt đầu và số lượng đỉnh vào/đỉnh ra của mỗi nốt.

Hình 2.6: Cấu trúc dữ liệu của đồ thị

Cụ thể, đồ thị G sẽ được biểu diễn bởi các mảng như sau:

- incoming_edges, incoming_index là mảng gồm các phần tử nguyên 4 byte để lưu trữ thông tin liên quan đến các đỉnh vào của các nốt. incoming_edges lưu tất cả các đỉnh vào của các nốt, incoming_index lưu trữ vị trí bắt đầu và số lượng đỉnh vào của các nốt.

- outgoing_edges, outgoing_index là mảng gồm các phần tử nguyên 4 byte để lưu trữ thông tin liên quan đến các đỉnh ra của các nốt. outgoing_edges lưu tất cả các đỉnh ra của các nốt, outgoing_index lưu trữ vị trí bắt đầu và số lượng đỉnh ra của các nốt.

Ví dụ ở Hình 2.6 mô tả đầu vào (input) bên trái sẽ cho cấu trúc dữ liệu theo danh sách kề các đỉnh vào ở bên phải.

Nhờ vào việc sử dụng danh sách các đỉnh vào và đỉnh ra của từng nốt, chúng ta có thể duyệt đồ thị theo chiều rộng từ cả hai phía. Nó sẽ cho phép tính toán khoảng cách ngắn nhất giữa hai điểm dựa trên thuật toán duyệt đồ thị theo từ rộng từ cả hai phía (bi-directional BFS) [19]

2.5. Tối ưu quá trình thêm và xóa cạnh của đồ thị

2.5.1. Thêm mi mt cnh

Vấn đề đặt ra là làm thế nào một cạnh có thể được thêm vào đồ thị với thời gian ít nhất. Khi sử dụng danh sách kề truyền thống để biểu diễn đồ thị, các chiến lược có thể được dùng để thêm mới một cạnh như sau:

Chèn vào gia mng

Phương pháp này rất đơn giản, trước khi thêm cạnh mới vào đồ thị, vị trí các phần tử của đỉnh vào/ra của một nốt được xác định. Tất cả các phần tử đỉnh vào/ra của các nốt kế tiếp sẽ được dịch sang phải một ví trị để dành vị trí trống cho đỉnh vào/ra của cạnh mới được thêm vào. Quá trình này được miêu tả trong Hình 2.7 (Thêm cạnh (2, 1) vào đồ thị).

Hình 2.7: Thêm một cạnh bằng cách chèn vào giữa mảng

Thực tế cho thấy rằng, phương pháp này chỉ thích hợp với những đồ thị có kích thước nhỏ khoảng vài nghìn cạnh. Vì khi dữ liệu đồ thị rất lớn, số cạnh lên đến hàng triệu, số lượng đỉnh ra và đỉnh vào rất nhiều, chi phí để di chuyển tất cả các phần tử của các nốt kế tiếp sang phải một vị trí là không nhỏ. Do đó, phương pháp thứ hai dưới đây được áp dụng.

Cp phát trước mt khong trng

Để giảm bớt chi phí di chuyển tất các phần tử các nốt kế tiếp sang phải một vị trí mỗi khi thêm một đỉnh vào/ra, một khoảng trống giữa các đỉnh vào/ra của các nốt được cấp phát trước. Điều này cho phép tối ưu quá trình thêm cạnh. Quá trình này được miêu tả trong Hình 2.8 và Hình 2.9.

Hình 2.8: Cấu trúc dữ liệu danh sách kề cấp phát trước khoảng trống

Hình 2.9: Thêm một cạnh bằng cách cấp trước khoảng trống

Tuy nhiên, dựa trên phân tích ở mục 2.4 có thể thấy, phương pháp này không thực sự hiệu quả đối với đồ thị dữ liệu lớn. Vì khi đó, mảng danh sách cạnh liền kề biểu diễn đồ thị sẽ bị hổng rất nhiều, điều này dẫn đến tăng tỉ lệ cache miss trong khi duyệt đồ thị.

Để cải thiện tỉ lệ cache miss này, chúng ta có thể giảm số lượng khoảng trống bằng cách giảm bớt không gian cấp phát trước. Ví dụ như sau mỗi khoảng đỉnh vào/ra của 32 nốt sẽ có không gian dành cho 8 nốt thêm mới được cấp phát trước. Điều đó có nghĩa là chúng ta có thể thêm tối đa 8 nốt trong mỗi khoảng đỉnh vào/ra của 32 nốt. Phương án này đã giảm được hạn chế rất nhiều, tuy nhiên, một phương án khác cho hiệu quả tốt hơn được trình bày tiếp theo sau đây.

Di chuyn phn t xung cui mng

Trước khi thêm vào một đỉnh vào/ra của một nốt, tất cả các phần tử của nốt đó được chuyển xuống cuối mảng, sau đó mới thêm đỉnh mới (Hình 2.10).

Hình 2.10: Quá trình thêm một cạnh

Đối với bài toán trong luận văn, phương án 3 "Di chuyển phần tử xuống cuối mảng" cho kết quả tốt hơn phương án 2 "Cấp phát trước một khoảng trống". Bởi đối với cấu trúc dữ liệu các đỉnh được sắp xếp liền kề, phương án 3 cho phép các đỉnh nằm ở vị trí liên tiếp nhau nhiều hơn, trong khi đó phương án 2 sẽ cho nhiều khoảng trống hơn giữa các đỉnh. Dựa vào phân tích tối ưu ở phần trên, phương án 3 sẽ cho tỉ lệ cache miss ít hơn, do đó tăng được hiệu năng hệ thống. Và cuối cùng, phương án 3 được lựa chọn để sử dụng trong quá trình thêm mới một cạnh.

2.5.2. Xóa đi mt cnh

Dựa trên cấu trúc đồ thị trên, quá trình xóa một cạnh rất đơn giản. Chỉ cần thay đổi giá trị số lượng đỉnh vào/ra trong mảng chỉ số incoming_index /outgoing_index và xóa đi đỉnh nối trong mảng danh sách đỉnh vào/ra. Quá trình này được miêu tả trong Hình 2.11 (Xóa cạnh (3, 1)).

2.6. Tối ưu quá trình xử lý truy vấn tìm đường ngắn nhất

Trong quá trình xử lý truy vấn tìm đường đi ngắn nhất, thuật toán duyệt đồ thị theo chiều rộng được sử dụng. Thuật toán này được xem như là phương pháp tối ưu đối với đồ thị quy mô lớn, có hướng, không trọng số [3] [13]. Một điểm cần chú ý nữa là với thuật toán này, khả năng song song hóa chạy được trên nhiều luồng, nhiều CPU. Bởi vậy, chiến lược để giải quyết bài toán là xử lý song song các truy vấn liên tiếp. Chiến lược này được giải thích chi tiết dưới đây.

2.6.1. Ci thin thut toán tìm đường đi ngn nht t hai hướng

Thuật toán duyệt đồ thị theo chiều rộng được sử dụng với quá trình duyệt từ hai hướng (đỉnh đầu và đỉnh kết thúc) bởi sử dụng cả hai mảng đỉnh vào (incoming_array) và đỉnh ra (outgoing_array).

Dđoán hướng đi mi ln lp

Dự đoán hướng đi, số lượng các đỉnh ra/vào cần duyệt ở mức kế tiếp. Trong thuật toán duyệt đồ thị theo chiều rộng từ hai hướng bình thường, nhánh có số lượng đỉnh trong hàng đợi ít hơn sẽ được duyệt ưu tiên. Tuy nhiên, thông tin này chưa đủ. Ví dụ như Hình 2.12, chúng ta cần tìm đường đi ngắn nhất giữa đỉnh 1 và đỉnh 9. Khi sử dụng thuật toán duyệt đồ thị theo chiều rộng từ hai hướng bình thường:

+ Duyệt các đỉnh ra của nốt 1, được một phần tử đỉnh 3.

+ Duyệt các đỉnh vào của nốt 9, được hai phần tử đỉnh 8 và đỉnh 7.

+ So sánh số lượng các đỉnh ra của nốt 1 và số lượng đỉnh vào của nốt 9. Vì số lượng đỉnh ra của nốt 1 là 1 bé hơn số lượng đỉnh vào của nốt 9 là 2, vậy nên chúng ta tiếp tục đi theo nhánh đỉnh ra. Lúc này số lượng đỉnh ra của đỉnh 3 là 6, trong trường hợp này chúng ta phải duyệt qua tất cả 6 đỉnh mới tìm được phần tử đỉnh 8. Như vậy, tổng cộng 1 + 2 + 6 = 9 đỉnh phải cho vào danh sách hàng đợi duyệt.

Để giảm thiểu không gian duyệt (giảm thiểu số lượng các đỉnh cần duyệt), như ví dụ Hình 2.12, thay vì duyệt theo hướng đỉnh ra, chúng ta duyệt theo hướng đỉnh vào. Khi đó, số lượng đỉnh cần phải đưa vào danh sách hàng đợi duyệt chỉ còn lại 1 + 2 = 3. Do vậy, chúng ta không chỉ so sánh tổng số đỉnh vào và đỉnh ra tại một mức, mà so sánh với cả mức thứ 2. Quay trở lại với ví dụ ở Hình 2.12, trước khi duyệt chúng ta sẽ ước lượng tổng số đỉnh ra/vào sẽ được cho vào hàng đợi ở mức 1 và mức 2. Khi đó, đối với đỉnh 1, tổng số đỉnh là 1 + 6 = 7, còn đối với đỉnh 9 là 2 + 1 = 3. Suy ra, đỉnh 9 sẽ được duyệt trước. Điều này sẽ giảm thiểu thời gian tìm kiếm đường đi ngắn nhất trong bài toán này.

2.6.2. Song song hóa truy vn tìm đường đi ngn nht

Quá trình xử lý các truy vấn liên tiếp được mô tả chi tiết dưới đây:

- Cilk Plus được sử dụng cho quá trình song song hóa. Trong quá trình thực hiện song song hóa, OpenMP và Pthread cũng được tiến hành thử nghiệm. Tuy nhiên, đối với bài toán cụ thể được trình bày trong luận văn này, Cilk Plus cho kết quả tốt nhất. Cilk Plus thực hiện quá trình song song hóa theo Hình 2.5.

- Ngoài ra, hai mảng toàn cục dùng để đánh dấu các đỉnh đã được duyệt, một dùng cho các đỉnh ra, một dùng cho các đỉnh vào được sử dụng cho mỗi lần tìm kiếm đường đi ngắn nhất giữa hai đỉnh.

- Sau đó, mỗi một luồng sẽ sử dụng một mảng vào/ra phù hợp, được phân chia bởi mảng toàn cục. Sau khi kết thúc quá trình tìm kiếm, mảng vào/ra tương ứng với từng luồng sẽ tự động được giải phóng để chuẩn bị cho quá trình tìm kiếm tiếp theo.

2.7. Tổng kết chương

Chương 2 đã trình bày chi tiết về bài toán cần giải quyết cũng như các vấn đề liên quan đến bài toán. Phần tiếp theo luận văn trình bày cách tiếp cận giải quyết bài toán và đưa ra phương pháp giải quyết bài toán dựa trên cấu trúc dữ liệu, quá trình thêm, xóa cạnh và quá trình truy vấn tìm đường ngắn nhất. Dựa vào các phân tích ở chương 2, chương tiếp theo sẽ trình bày cụ thể về phương pháp cài đặt và kết quả thực nghiệm của giải thuật.

Chương 3: Thc nghim và đánh giá

Để kiểm nghiệm phương pháp tìm đường đi ngắn nhất trong đồ thị lớn động, không trọng số, thuật toán đã được sử dụng để tham gia cuộc thi lập trình ACM Sigmod năm 2016 (ACM Sigmod Programming Contest 2016) và được chọn là một trong năm đội xuất sắc nhất giải. Bên cạnh đó, các bộ dữ liệu lớn từ SNAP [5] cũng được dùng để đánh giá phương pháp này.

3.1. Cài đặt

Như đã phân tích ở chương 2, 3 sự kiện mà chính cần cài đặt là: tìm đường ngắn nhất giữa hai đỉnh, thêm một cạnh và xóa một cạnh. Ba sự kiện này sẽ được giới thiệu trong các hàm exec_queries, add_edge, del_edge. Trong quá trình xử lý sự kiện, mỗi khi chương trình đọc vào một dòng, chữ cái đầu tiên sẽ được dùng để phân loại sự kiện và thi hành các thủ tục tương ứng với sự kiện đó. Chi tiết cài đặt được miêu tả trong Thủ tục 1 dưới đây.

Thủ tục 1: Xử lý tổng hợp các sự kiện

Để tích hợp quá trình song song hóa trong truy vấn tìm đường đi ngắn nhất, trước khi thêm mới một cạnh, tất cả truy vấn tìm đường đi ngắn nhất trong danh sách trước đó sẽ được thực hiện. Thủ tục để thêm mới một cạnh (u, v) vào đồ thị G được mô tả dưới đây.

Thủ tục 2: Thêm mới một cạnh (u, v) vào đồ thị G

Để tích hợp quá trình song song hóa trong tìm khoảng cách ngắn nhất, trước khi thực sự xóa cạnh, tất cả truy vấn tìm đường đi ngắn nhất trong danh sách trước đó sẽ được thực hiện. Thủ tục xóa cạnh (u, v) của đồ thị G được mô tả dưới đây.

Thủ tục 3: Xóa cạnh (u, v) của đồ thị G

Thủ tục quan trọng nhất là tìm đường đi ngắn nhất giữa hai đỉnh (u, v) dựa trên thuật toán duyệt đồ thị theo chiều rộng từ hai hướng cải tiến bởi sử dụng cả hai mảng đỉnh vào (incoming_array) và đỉnh ra (outgoing_array). Quá trình này được mô tả cụ thể trong Thủ tục 4 dưới đây.

Thủ tục 4: Tìm đường đi ngắn nhất giữa hai đỉnh (u, v) theo phương pháp duyệt đồ thị theo chiều rộng từ hai hướng cải tiến.

Trong thuật toán duyệt đồ thị theo chiều rộng (mục 1.1.4), chúng ta có bước đánh dấu các đỉnh đã được duyệt. Để đánh dấu các đỉnh này, thuật toán được cài đặt theo mã giả đã được giới thiệu ở (mục 1.1.4). Khi đó, để có thể đánh dấu được các đỉnh đã xét/chưa xét chúng ta cần khởi tạo một mảng đúng bằng số lượng đỉnh của đồ thị. Ví dụ đối với đồ thị một triệu cạnh, chúng ta cần có mảng một triệu

Một phần của tài liệu (LUẬN văn THẠC sĩ) tối ưu hóa truy vấn tìm đường ngắn nhất trên đồ thị động quy mô lớn (Trang 35 - 47)

Tải bản đầy đủ (PDF)

(58 trang)