OK, bạn đang xác định vấn đề ở nơi dường như không có nhiều chỗ để cải thiện. Đó là khá hiếm, theo kinh nghiệm của tôi. Tôi đã cố gắng giải thích điều này trong một bài báo của Tiến sĩ Dobbs vào tháng 11 năm 1993, bằng cách bắt đầu từ một chương trình không tầm thường được thiết kế tốt, không có sự lãng phí rõ ràng và đưa nó qua một loạt các tối ưu hóa cho đến khi thời gian đồng hồ treo tường giảm xuống từ 48 giây còn 1,1 giây và kích thước mã nguồn đã giảm đi 4 lần . Công cụ chẩn đoán của tôi là thế này . Trình tự thay đổi là thế này:
Vấn đề đầu tiên được tìm thấy là việc sử dụng các cụm danh sách (bây giờ được gọi là "iterators" và "lớp container") chiếm hơn một nửa thời gian. Chúng được thay thế bằng mã khá đơn giản, giảm thời gian xuống còn 20 giây.
Bây giờ người làm thời gian lớn nhất là xây dựng danh sách nhiều hơn. Theo tỷ lệ phần trăm, trước đây nó không quá lớn, nhưng bây giờ là do vấn đề lớn hơn đã được loại bỏ. Tôi tìm cách tăng tốc nó và thời gian giảm xuống còn 17 giây.
Bây giờ khó tìm ra thủ phạm rõ ràng hơn, nhưng có một vài cái nhỏ hơn mà tôi có thể làm gì đó và thời gian giảm xuống còn 13 giây.
Bây giờ tôi dường như đã va vào một bức tường. Các mẫu đang cho tôi biết chính xác những gì nó đang làm, nhưng tôi dường như không thể tìm thấy bất cứ điều gì tôi có thể cải thiện. Sau đó, tôi suy nghĩ về thiết kế cơ bản của chương trình, về cấu trúc điều khiển giao dịch của nó và hỏi xem tất cả các tìm kiếm danh sách mà nó đang thực sự có bắt buộc bởi các yêu cầu của vấn đề không.
Sau đó, tôi bắt đầu thiết kế lại, trong đó mã chương trình thực sự được tạo ra (thông qua các macro tiền xử lý) từ một nguồn nhỏ hơn và trong đó chương trình không liên tục tìm ra những thứ mà lập trình viên biết là có thể dự đoán được. Nói cách khác, đừng "diễn giải" chuỗi việc cần làm, "biên dịch" nó.
- Việc thiết kế lại được thực hiện, thu nhỏ mã nguồn theo hệ số 4 và thời gian giảm xuống còn 10 giây.
Bây giờ, vì quá nhanh, rất khó để lấy mẫu, vì vậy tôi cho nó gấp 10 lần công việc phải làm, nhưng những lần sau dựa trên khối lượng công việc ban đầu.
Chẩn đoán nhiều hơn cho thấy rằng nó đang dành thời gian trong quản lý hàng đợi. Việc lót này giúp giảm thời gian xuống còn 7 giây.
Bây giờ một người làm thời gian lớn là in ấn chẩn đoán tôi đã và đang làm. Flush mà - 4 giây.
Bây giờ những người làm thời gian lớn nhất là các cuộc gọi đến malloc và miễn phí . Đối tượng tái chế - 2,6 giây.
Tiếp tục lấy mẫu, tôi vẫn thấy các thao tác không thực sự cần thiết - 1,1 giây.
Tổng hệ số tăng tốc: 43,6
Bây giờ không có hai chương trình giống nhau, nhưng trong phần mềm không phải đồ chơi, tôi luôn thấy một sự tiến bộ như thế này. Đầu tiên bạn có được những thứ dễ dàng, và sau đó càng khó khăn hơn, cho đến khi bạn đạt đến điểm giảm lợi nhuận. Sau đó, cái nhìn sâu sắc mà bạn đạt được cũng có thể dẫn đến việc thiết kế lại, bắt đầu một vòng tăng tốc mới, cho đến khi bạn một lần nữa đạt được lợi nhuận giảm dần. Bây giờ đây là điểm mà tại đó nó có thể làm cho tinh thần để tự hỏi liệu ++i
hoặc i++
hoặc for(;;)
hoặcwhile(1)
là nhanh hơn: các loại câu hỏi tôi thấy như vậy thường trên Stack Overflow.
PS Có thể tự hỏi tại sao tôi không sử dụng một hồ sơ. Câu trả lời là hầu hết mọi "vấn đề" này là một trang gọi hàm, trong đó ngăn xếp các mẫu xác định chính xác. Profiler, ngay cả ngày nay, hầu như không có ý tưởng rằng các câu lệnh và hướng dẫn cuộc gọi quan trọng hơn để xác định vị trí và dễ sửa chữa hơn so với toàn bộ các chức năng.
Tôi thực sự đã xây dựng một trình lược tả để làm điều này, nhưng đối với một sự thân mật thực sự bẩn thỉu với những gì mã đang làm, không có sự thay thế nào cho việc lấy ngón tay của bạn ngay trong đó. Nó không phải là một vấn đề mà số lượng mẫu là nhỏ, bởi vì không có vấn đề nào được tìm thấy là rất nhỏ mà chúng dễ bị bỏ qua.
THÊM: jerryjvl yêu cầu một số ví dụ. Đây là vấn đề đầu tiên. Nó bao gồm một số lượng nhỏ các dòng mã riêng biệt, cùng nhau chiếm hơn một nửa thời gian:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
Chúng được sử dụng cụm danh sách ILST (tương tự như một lớp danh sách). Chúng được thực hiện theo cách thông thường, với "ẩn thông tin" có nghĩa là người dùng của lớp không cần phải quan tâm đến cách chúng được thực hiện. Khi những dòng này được viết (trong số khoảng 800 dòng mã), ý nghĩ không được đưa ra cho ý tưởng rằng đây có thể là một "nút cổ chai" (tôi ghét từ đó). Họ chỉ đơn giản là cách được đề nghị để làm việc. Thật dễ dàng để nói trong nhận thức rằng những điều này nên được tránh, nhưng theo kinh nghiệm của tôi, tất cả các vấn đề hiệu suất là như vậy. Nói chung, thật tốt khi cố gắng tránh tạo ra các vấn đề về hiệu suất. Thậm chí tốt hơn là tìm và sửa những cái được tạo ra, mặc dù chúng "nên tránh" (trong nhận thức muộn màng).
Đây là vấn đề thứ hai, trong hai dòng riêng biệt:
/* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)
Đây là danh sách xây dựng bằng cách nối các mục vào cuối của chúng. (Cách khắc phục là thu thập các mục trong mảng và xây dựng tất cả các danh sách cùng một lúc.) Điều thú vị là các câu lệnh này chỉ có giá (tức là trên ngăn xếp cuộc gọi) 3/48 của thời điểm ban đầu, vì vậy chúng không ở trong Thực tế là một vấn đề lớn lúc ban đầu . Tuy nhiên, sau khi loại bỏ vấn đề đầu tiên, chúng có giá 3/20 thời gian và giờ đây là một "con cá lớn hơn". Nói chung, đó là cách nó đi.
Tôi có thể thêm rằng dự án này được chắt lọc từ một dự án thực tế mà tôi đã giúp đỡ. Trong dự án đó, các vấn đề về hiệu năng còn kịch tính hơn nhiều (cũng như việc tăng tốc), chẳng hạn như gọi một thói quen truy cập cơ sở dữ liệu trong một vòng lặp bên trong để xem liệu một nhiệm vụ đã kết thúc.
THAM KHẢO THÊM: Mã nguồn, cả bản gốc và bản thiết kế lại, có thể được tìm thấy trong www.ddj.com , cho năm 1993, trong tệp 9311.zip, tệp slug.asc và slug.zip.
EDIT 2011/11/26: Hiện tại có một dự án SourceForge chứa mã nguồn trong Visual C ++ và một mô tả từng bước về cách nó được điều chỉnh. Nó chỉ trải qua nửa đầu của kịch bản được mô tả ở trên và nó không tuân theo chính xác cùng một chuỗi, nhưng vẫn có được tốc độ tăng tốc 2-3 bậc.