Điều này khiến tôi tự hỏi rằng Multithreading quan trọng như thế nào trong kịch bản ngành hiện tại?
Trong các lĩnh vực quan trọng về hiệu năng, nơi hiệu suất không đến từ mã của bên thứ ba thực hiện công việc nặng nhọc, mà là của chúng tôi, sau đó tôi có xu hướng xem xét mọi thứ theo thứ tự quan trọng này từ góc độ CPU (GPU là ký tự đại diện tôi đã giành được đi vào):
- Hiệu quả bộ nhớ (ví dụ: địa phương của tài liệu tham khảo).
- Thuật toán
- Đa luồng
- SIMD
- Tối ưu hóa khác (gợi ý dự đoán nhánh tĩnh, ví dụ)
Lưu ý rằng đây là danh sách không chỉ dựa trên tầm quan trọng mà còn rất nhiều động lực khác như tác động của chúng đối với việc bảo trì, chúng đơn giản như thế nào (nếu không, đáng để xem xét trước), tương tác của chúng với những người khác trong danh sách, v.v.
Hiệu quả bộ nhớ
Hầu hết có thể ngạc nhiên về sự lựa chọn của tôi về hiệu quả bộ nhớ so với thuật toán. Đó là vì hiệu quả bộ nhớ tương tác với tất cả 4 mục khác trong danh sách này và đó là vì việc xem xét nó thường rất nhiều trong danh mục "thiết kế" thay vì danh mục "triển khai". Phải thừa nhận rằng có một chút vấn đề về con gà hoặc trứng ở đây vì hiểu được hiệu quả bộ nhớ thường yêu cầu xem xét tất cả 4 mục trong danh sách, trong khi cả 4 mục khác cũng yêu cầu xem xét hiệu quả bộ nhớ. Tuy nhiên, đó là trung tâm của mọi thứ.
Ví dụ: nếu chúng ta có nhu cầu về cấu trúc dữ liệu cung cấp truy cập tuần tự theo thời gian tuyến tính và chèn vào thời gian liên tục ở phía sau và không có gì khác cho các phần tử nhỏ, thì lựa chọn ngây thơ ở đây sẽ là một danh sách được liên kết. Đó là coi thường hiệu quả bộ nhớ. Khi chúng ta xem xét hiệu quả bộ nhớ trong hỗn hợp, cuối cùng chúng ta sẽ chọn các cấu trúc liền kề hơn trong kịch bản này, như các cấu trúc dựa trên mảng có thể phát triển hoặc các nút liền kề hơn (ví dụ: lưu trữ 128 phần tử trong một nút) được liên kết với nhau hoặc ít nhất là một danh sách liên kết được hỗ trợ bởi một phân bổ hồ bơi. Chúng có một lợi thế đáng kể mặc dù có cùng độ phức tạp thuật toán. Tương tự như vậy, chúng ta thường chọn quicksort của một mảng trên sắp xếp hợp nhất mặc dù độ phức tạp thuật toán kém hơn đơn giản chỉ vì hiệu quả bộ nhớ.
Tương tự như vậy, chúng ta không thể có đa luồng hiệu quả nếu các mẫu truy cập bộ nhớ của chúng ta quá nhỏ và phân tán trong tự nhiên đến mức cuối cùng chúng ta tối đa hóa lượng chia sẻ sai trong khi khóa ở mức độ chi tiết nhất trong mã. Vì vậy, hiệu quả bộ nhớ nhân lên hiệu suất đa luồng. Đó là một điều kiện tiên quyết để có được hầu hết các chủ đề.
Mỗi một mục ở trên trong danh sách đều có sự tương tác phức tạp với dữ liệu và tập trung vào cách thức dữ liệu được trình bày cuối cùng nằm trong tĩnh mạch của hiệu quả bộ nhớ. Mỗi một trong những điều trên có thể bị tắc nghẽn với cách thể hiện hoặc truy cập dữ liệu không phù hợp.
Một lý do khác khiến hiệu quả bộ nhớ rất quan trọng là nó có thể áp dụng trong toàn bộ cơ sở mã. Nói chung khi mọi người tưởng tượng rằng sự thiếu hiệu quả tích lũy từ các phần nhỏ của công việc ở đây và đó, đó là một dấu hiệu cho thấy họ cần phải lấy một hồ sơ. Tuy nhiên, các trường có độ trễ thấp hoặc những trường xử lý phần cứng rất hạn chế sẽ thực sự tìm thấy, ngay cả sau khi định hình, các phiên chỉ ra không có điểm nóng rõ ràng (chỉ phân tán khắp nơi) trong một cơ sở mã không hiệu quả với cách phân bổ, sao chép và truy cập bộ nhớ. Thông thường, đây là khoảng thời gian duy nhất toàn bộ một cơ sở mã có thể dễ bị ảnh hưởng bởi hiệu suất có thể dẫn đến một bộ tiêu chuẩn hoàn toàn mới được áp dụng trong toàn bộ cơ sở mã và hiệu quả bộ nhớ thường là trọng tâm của nó.
Thuật toán
Điều này khá giống nhau, vì sự lựa chọn trong thuật toán sắp xếp có thể tạo ra sự khác biệt giữa một đầu vào lớn mất hàng tháng để sắp xếp so với giây để sắp xếp. Nó tạo ra tác động lớn nhất của tất cả nếu sự lựa chọn nằm giữa các thuật toán bậc hai hoặc bậc ba thực sự và một thuật toán tuyến tính, hoặc giữa tuyến tính và logarit hoặc hằng số, ít nhất là cho đến khi chúng ta có 1.000.000 máy lõi (trong trường hợp đó là bộ nhớ hiệu quả sẽ trở nên quan trọng hơn nữa).
Tuy nhiên, nó không đứng đầu danh sách cá nhân của tôi, vì bất kỳ ai có năng lực trong lĩnh vực của họ đều biết sử dụng cấu trúc gia tốc để loại bỏ sự bực bội, ví dụ: Chúng tôi bị bão hòa bởi kiến thức thuật toán và biết những thứ như sử dụng biến thể của một trie như một cây cơ số cho các tìm kiếm dựa trên tiền tố là công cụ bé. Thiếu loại kiến thức cơ bản về lĩnh vực chúng tôi đang làm việc, thì hiệu quả thuật toán chắc chắn sẽ tăng lên hàng đầu, nhưng thường thì hiệu quả thuật toán là không đáng kể.
Ngoài ra, việc phát minh ra các thuật toán mới có thể là một điều cần thiết trong một số lĩnh vực (ví dụ: trong xử lý lưới tôi đã phải phát minh ra hàng trăm vì chúng không tồn tại trước đó hoặc việc triển khai các tính năng tương tự trong các sản phẩm khác là bí mật độc quyền, không được công bố trong một bài báo ). Tuy nhiên, một khi chúng ta vượt qua phần giải quyết vấn đề và tìm cách có được kết quả chính xác và một khi hiệu quả trở thành mục tiêu, cách duy nhất để thực sự đạt được nó là xem xét cách chúng ta tương tác với dữ liệu (bộ nhớ). Nếu không hiểu hiệu quả bộ nhớ, thuật toán mới có thể trở nên phức tạp không cần thiết với những nỗ lực vô ích để làm cho nó nhanh hơn, khi điều duy nhất nó cần là xem xét thêm một chút về hiệu quả bộ nhớ để mang lại một thuật toán đơn giản hơn, thanh lịch hơn.
Cuối cùng, các thuật toán có xu hướng nằm trong danh mục "triển khai" hơn là hiệu quả bộ nhớ. Chúng thường dễ dàng cải thiện hơn trong nhận thức ngay cả với thuật toán tối ưu phụ được sử dụng ban đầu. Ví dụ, thuật toán xử lý ảnh kém hơn thường chỉ được thực hiện ở một vị trí cục bộ trong cơ sở mã. Nó có thể được trao đổi với một cái tốt hơn sau này. Tuy nhiên, nếu tất cả các thuật toán xử lý hình ảnh được gắn với một Pixel
giao diện có biểu diễn bộ nhớ tối ưu phụ, nhưng cách duy nhất để sửa nó là thay đổi cách thể hiện nhiều pixel (chứ không phải một pixel), thì chúng ta thường SOL và sẽ phải viết lại hoàn toàn codebase theo hướngImage
giao diện. Một loại điều tương tự dành cho việc thay thế một thuật toán sắp xếp - nó thường là một chi tiết triển khai, trong khi một sự thay đổi hoàn toàn đối với biểu diễn cơ bản của dữ liệu được sắp xếp hoặc cách nó truyền qua các thông điệp có thể yêu cầu các giao diện được thiết kế lại.
Đa luồng
Đa luồng là một khó khăn trong bối cảnh hiệu suất vì nó là tối ưu hóa ở cấp độ vi mô theo các đặc điểm phần cứng, nhưng phần cứng của chúng tôi thực sự mở rộng theo hướng đó. Tôi đã có các đồng nghiệp có 32 lõi (tôi chỉ có 4).
Tuy nhiên, mulithreading là một trong những tối ưu hóa vi mô nguy hiểm nhất có thể được một chuyên gia biết đến nếu mục đích được sử dụng để tăng tốc phần mềm. Điều kiện cuộc đua gần như là lỗi nghiêm trọng nhất có thể xảy ra, vì bản chất nó không rõ ràng (có thể chỉ xuất hiện một vài tháng một lần trên máy của nhà phát triển vào thời điểm bất tiện nhất bên ngoài bối cảnh gỡ lỗi). Vì vậy, nó được cho là sự xuống cấp tiêu cực nhất về khả năng bảo trì và tính chính xác tiềm năng của mã trong số này, đặc biệt là do các lỗi liên quan đến đa luồng có thể dễ dàng bay theo radar ngay cả khi thử nghiệm cẩn thận nhất.
Tuy nhiên, nó trở nên rất quan trọng. Mặc dù có thể vẫn không phải lúc nào cũng chiếm ưu thế như hiệu quả bộ nhớ (đôi khi có thể khiến mọi thứ nhanh hơn gấp trăm lần) với số lượng lõi chúng ta có bây giờ, chúng ta đang thấy ngày càng nhiều lõi. Tất nhiên, ngay cả với các máy 100 lõi, tôi vẫn đặt hiệu quả bộ nhớ lên đầu danh sách, vì hiệu quả của luồng nói chung là không thể nếu không có nó. Một chương trình có thể sử dụng hàng trăm luồng trên một máy như vậy và vẫn chậm khi thiếu các mẫu truy cập và biểu diễn bộ nhớ hiệu quả (sẽ liên kết với các mẫu khóa).
SIMD
SIMD cũng hơi khó xử vì các thanh ghi thực sự ngày càng rộng hơn, với các kế hoạch sẽ còn rộng hơn nữa. Ban đầu chúng tôi đã thấy các thanh ghi MMX 64 bit, sau đó là các thanh ghi XMM 128 bit có khả năng hoạt động song song 4 SPFP. Bây giờ chúng ta đang thấy các thanh ghi YMM 256 bit có khả năng 8 song song. Và đã có kế hoạch thay thế cho các thanh ghi 512 bit cho phép 16 thanh ghi song song.
Chúng sẽ tương tác và nhân lên với hiệu quả của đa luồng. Tuy nhiên, SIMD có thể làm giảm khả năng bảo trì cũng giống như đa luồng. Mặc dù các lỗi liên quan đến chúng không nhất thiết phải khó tái tạo và sửa chữa như tình trạng bế tắc hoặc cuộc đua, tính di động rất khó xử và đảm bảo rằng mã có thể chạy trên máy của mọi người (và sử dụng các hướng dẫn phù hợp dựa trên khả năng phần cứng của họ) vụng về.
Một điều nữa là trong khi các trình biên dịch ngày nay thường không đánh bại mã SIMD được viết một cách thành thạo, thì chúng dễ dàng đánh bại các nỗ lực ngây thơ. Chúng có thể cải thiện đến mức chúng ta không còn phải thực hiện thủ công hoặc ít nhất là không cần thủ công để viết nội tại hoặc mã lắp ráp thẳng (có lẽ chỉ là một chút hướng dẫn của con người).
Mặc dù vậy, một lần nữa, không có bố cục bộ nhớ hiệu quả cho xử lý véc tơ, SIMD là vô dụng. Chúng tôi cuối cùng chỉ tải một trường vô hướng vào một thanh ghi rộng chỉ để thực hiện một thao tác trên nó. Trọng tâm của tất cả các mục này là sự phụ thuộc vào bố trí bộ nhớ để thực sự hiệu quả.
Tối ưu hóa khác
Đây thường là những gì tôi muốn đề nghị chúng ta bắt đầu gọi "vi mô" ngày nay nếu từ này gợi ý không chỉ vượt ra ngoài trọng tâm thuật toán mà còn hướng tới những thay đổi có tác động rất nhỏ đến hiệu suất.
Thường cố gắng tối ưu hóa cho dự đoán nhánh đòi hỏi phải thay đổi thuật toán hoặc hiệu quả bộ nhớ, ví dụ: Nếu điều này chỉ được cố gắng thông qua gợi ý và sắp xếp lại mã để dự đoán tĩnh, điều đó chỉ có xu hướng cải thiện việc thực thi mã lần đầu tiên, khiến các hiệu ứng trở nên nghi ngờ nếu không thường xuyên hoàn toàn không đáng kể
Quay lại đa luồng cho hiệu suất
Vì vậy, dù sao, đa luồng quan trọng như thế nào từ bối cảnh hiệu suất? Trên máy 4 lõi của tôi, lý tưởng nhất có thể khiến mọi thứ nhanh hơn khoảng 5 lần (những gì tôi có thể nhận được với siêu phân luồng). Nó sẽ quan trọng hơn đáng kể đối với đồng nghiệp của tôi, người có 32 lõi. Và nó sẽ ngày càng trở nên quan trọng trong những năm tới.
Vì vậy, nó khá quan trọng. Nhưng thật vô ích khi chỉ ném một loạt các luồng vào vấn đề nếu hiệu quả bộ nhớ không có để cho phép các khóa được sử dụng một cách tiết kiệm, để giảm chia sẻ sai, v.v.
Đa luồng ngoài hiệu suất
Đa luồng không phải lúc nào cũng nói về hiệu suất tuyệt đối theo kiểu thông lượng đơn giản. Đôi khi, nó được sử dụng để cân bằng tải ngay cả với chi phí thông lượng có thể có để cải thiện khả năng phản hồi cho người dùng hoặc cho phép người dùng thực hiện đa nhiệm nhiều hơn mà không cần chờ mọi thứ kết thúc (ví dụ: tiếp tục duyệt trong khi tải xuống tệp).
Trong những trường hợp đó, tôi đề nghị rằng đa luồng tăng cao hơn nữa về phía trên (thậm chí có thể cao hơn hiệu quả bộ nhớ), vì đó là về thiết kế cuối của người dùng thay vì tận dụng tối đa phần cứng. Nó sẽ thường thống trị các thiết kế giao diện và cách chúng ta cấu trúc toàn bộ cơ sở mã của chúng ta trong các tình huống như vậy.
Khi chúng ta không chỉ đơn giản là song song một vòng lặp chặt chẽ truy cập vào một cấu trúc dữ liệu khổng lồ, đa luồng sẽ chuyển sang danh mục "thiết kế" thực sự khó khăn và thiết kế luôn vượt qua việc thực hiện.
Vì vậy, trong những trường hợp đó, tôi muốn nói rằng việc xem trước đa luồng là hoàn toàn quan trọng, thậm chí còn hơn cả việc truy cập và thể hiện bộ nhớ.