Khi nào thì tối ưu hóa quá sớm?


82

Như Knuth đã nói,

Chúng ta nên quên đi những hiệu quả nhỏ, nói khoảng 97% thời gian: tối ưu hóa quá sớm là gốc rễ của mọi điều ác.

Đây là điều thường xuất hiện trong câu trả lời của Stack Overflow cho các câu hỏi như "cơ chế lặp hiệu quả nhất", "kỹ thuật tối ưu hóa SQL?" ( và như vậy ). Câu trả lời tiêu chuẩn cho các câu hỏi về mẹo tối ưu hóa này là lập hồ sơ mã của bạn và xem liệu đó có phải là vấn đề trước không, và nếu không, thì kỹ thuật mới của bạn là không cần thiết.

Câu hỏi của tôi là, nếu một kỹ thuật cụ thể khác nhưng không đặc biệt tối nghĩa hoặc khó hiểu, thì đó có thể thực sự được coi là một tối ưu hóa quá sớm không?

Đây là một bài viết có liên quan của Randall Hyde có tên The Fallacy of Premature Optimization .


41
Thật là mỉa mai khi nhiều người hét lên "Tối ưu hóa sớm là gốc rễ của mọi điều xấu xa" chính họ đã tối ưu hóa quá sớm câu trích dẫn: (tt)
some

21
"Chúng ta nên quên đi những hiệu quả nhỏ, nói khoảng 97% thời gian: tối ưu hóa quá sớm là gốc rễ của mọi điều xấu. Tuy nhiên, chúng ta không nên bỏ qua cơ hội của mình trong 3% quan trọng đó" (Donald Knuth)
một số

2
Tôi tin rằng chính CA Hoare đã nói điều này. Ngay cả Knuth cũng nói như vậy.
jamesh

1
vâng, lần đầu tiên Tony Hoare nói rằng "tối ưu hóa quá sớm là gốc rễ của tất cả các phần xấu xa", nhưng Knuth đã trích dẫn / diễn giải anh ấy thêm phần còn lại, tôi tin
nickf 22/12/08

2
Mặc dù tôi đồng ý rằng câu trích dẫn là câu hỏi thường bị lạm dụng nhất và đưa ra khỏi ngữ cảnh, nhưng theo định nghĩa thì nó luôn đúng vì "quá non" (Tuy nhiên, nó thường được sử dụng không chính xác để biện minh cho thiết kế và mã cẩu thả). Theo định nghĩa, nếu việc tối ưu hóa xảy ra ở thời điểm thuận lợi nhất trong quá trình phát triển, có thể là trong quá trình thiết kế hoặc bất kỳ thời điểm nào khác, thì nó không phải là "quá sớm".
Lawrence Dol

Câu trả lời:


103

Don Knuth bắt đầu phong trào lập trình biết chữ vì ông tin rằng chức năng quan trọng nhất của mã máy tính là truyền đạt ý định của lập trình viên tới người đọc . Bất kỳ thực hành mã hóa nào khiến mã của bạn khó hiểu hơn nhân danh hiệu suất đều là tối ưu hóa quá sớm.

Một số thành ngữ được giới thiệu dưới danh nghĩa tối ưu hóa đã trở nên phổ biến đến mức mọi người đều hiểu chúng và chúng đã trở nên được mong đợi , không hề sớm. Những ví dụ bao gồm

  • Sử dụng số học con trỏ thay vì ký hiệu mảng trong C, bao gồm cả việc sử dụng các thành ngữ như

    for (p = q; p < lim; p++)
    
  • Liên kết các biến toàn cục với các biến cục bộ trong Lua, như trong

    local table, io, string, math
        = table, io, string, math
    

Ngoài những thành ngữ như vậy, hãy đi tắt đón đầu khi gặp nguy hiểm .

Tất cả tối ưu hóa đều quá sớm trừ khi

  • Một chương trình quá chậm (nhiều người quên phần này).

  • Bạn có một phép đo (hồ sơ hoặc tương tự) cho thấy rằng việc tối ưu hóa có thể cải thiện mọi thứ .

(Nó cũng được phép tối ưu hóa cho bộ nhớ.)

Câu trả lời trực tiếp cho câu hỏi:

  • Nếu kỹ thuật "khác biệt" của bạn khiến chương trình khó hiểu hơn , thì đó là một sự tối ưu hóa quá sớm .

CHỈNH SỬA : Để trả lời các bình luận, sử dụng quicksort thay vì một thuật toán đơn giản hơn như sắp xếp chèn là một ví dụ khác về một thành ngữ mà mọi người đều hiểu và mong đợi . (Mặc dù nếu bạn viết thường xuyên loại riêng của bạn thay vì sử dụng thường xuyên loại thư viện, một hy vọng bạn có một rất lý do chính đáng.)


13
Theo định nghĩa của bạn; nếu triển khai quicksort khó đọc và khó hiểu hơn so với bubbleort, thì đó là một sự tối ưu hóa quá sớm. Bạn không thể tối ưu hóa cho bộ nhớ? Hãy thử tìm kiếm các ví dụ tương tự cho các ma trận thưa thớt lớn. IMHO, hầu hết tối ưu hóa sẽ xảy ra ở giai đoạn thiết kế. tôi, e, từ rất sớm.
SmacL 22/12/08

1
@frankodwyer: nhưng tăng con trỏ có thể nhanh hơn tăng bộ đếm và sử dụng ký hiệu mảng, và nó sẽ là tối ưu hóa quá sớm.
Joachim Sauer

5
@Norman: Mặc dù hiện nay quicksort đã phổ biến, nhưng nó không phải là khi nó được phát minh lần đầu tiên, và do đó QED là một sự tối ưu hóa quá sớm mà tác giả không có vấn đề gì, phải không?
Lawrence Dol,

5
@Software Monkey: Hoàn toàn có thể. Tất cả các nghiên cứu về CS là một sự lãng phí tiền của người đóng thuế và cần phải dừng lại ngay lập tức.
Norman Ramsey

11
Bất kỳ thuật toán sắp xếp nào, kể cả thuật toán bạn phát minh, đều rõ ràng và ngắn gọn nếu được viết dưới dạng hàm riêng biệt được gọi là sortQuickly (...) với các chú thích phù hợp.
ilya n.

40

IMHO, 90% việc tối ưu hóa của bạn sẽ xảy ra ở giai đoạn thiết kế, dựa trên các yêu cầu hiện tại đã đạt được và quan trọng hơn là các yêu cầu trong tương lai. Nếu bạn phải lấy ra một hồ sơ vì ứng dụng của bạn không mở rộng đến mức tải cần thiết, bạn đã để nó quá muộn và IMO sẽ lãng phí rất nhiều thời gian và công sức trong khi không khắc phục được sự cố.

Thông thường, các tối ưu hóa duy nhất đáng giá là những tối ưu hóa giúp bạn có được thứ tự cải thiện hiệu suất đáng kể về tốc độ hoặc cấp số nhân về lưu trữ hoặc băng thông. Những loại tối ưu hóa này thường liên quan đến việc lựa chọn thuật toán và chiến lược lưu trữ, và cực kỳ khó để đảo ngược mã hiện có. Chúng có thể ảnh hưởng sâu sắc đến quyết định về ngôn ngữ mà bạn triển khai hệ thống của mình.

Vì vậy, lời khuyên của tôi, hãy tối ưu hóa sớm, dựa trên yêu cầu của bạn, không phải mã của bạn và xem xét tuổi thọ có thể kéo dài của ứng dụng của bạn.


6
Tôi không đồng ý về kết luận "để nó quá muộn" của bạn. Về cơ bản, việc lập hồ sơ là cần thiết khi một giả định không phù hợp và cần thiết lập hồ sơ để cho bạn biết giả định GÌ đã phá vỡ. Ví dụ: tôi thấy rằng "xóa ký tự ở vị trí 0" cho StringBuffers trong Java hoạt động tốt cho các bài kiểm tra junit, nhưng RẤT chậm đối với các chuỗi lớn. Tôi đã không nghi ngờ mã đó cho đến khi trình biên dịch xác định chính xác nó là thủ phạm!
Thorbjørn Ravn Andersen

7
Tôi đồng ý với câu "khi bạn cần hồ sơ, thì đã muộn rồi", dựa trên kinh nghiệm của tôi - phần lớn các vấn đề về hiệu suất của tôi không phải là tắc nghẽn đơn lẻ, mà là lan rộng ra trên nhiều người đóng góp. Nhưng sau đó, tôi có một nền tảng vững chắc về mã và chi phí cấp thấp, và theo bản năng, tôi sẽ tránh xa bất kỳ thứ gì dựa vào việc loại bỏ ký tự chuỗi đầu tiên (lặp đi lặp lại đáng kể). +1 cho "tối ưu hóa trong quá trình thiết kế".
peterchen

@peterchen chỉ vì tò mò, bạn sẽ làm gì để "xóa ký tự chuỗi đầu tiên".
Ghos3t

1
@ user258365: Brute force sẽ là sử dụng biểu diễn chuỗi không cần tạo bản sao cho chuỗi con. Điều đó "gần như tầm thường" đối với các chuỗi được đếm tham chiếu bất biến. Ngoài ra, các thay đổi về thuật toán, chẳng hạn như thay thế (mã giả) while (s[0]==' ') s = s.substring(1) for(i=0; i<s.len && s[i]==' '; ++i); s=s.substring(i)--- nhưng điều này đòi hỏi bạn phải biết các vấn đề về hiệu suất tiềm ẩn (trình định cấu hình là công cụ có giá trị để học liên tục ở đây).
peterchen

@ ThorbjørnRavnAndersen, tôi đã làm tư vấn để giúp một nhóm hoàn thành dự án, nhưng điều đó không thể thực hiện được vì các vấn đề nghiêm trọng về hiệu suất không được lên kế hoạch (ngoài mã spaghetti). Nó được cho là hiển thị một biểu đồ thời gian với tất cả bệnh sử của bệnh nhân. Một yêu cầu duy nhất đã được thực hiện cho toàn bộ dữ liệu, chẳng hạn như Google Maps tìm nạp toàn bộ thế giới. Việc phát triển mã xấu, mong đợi việc lập hồ sơ sau đó đã khiến dự án thất bại.
Pedro Amaral Couto

30

Nếu bạn chưa lập hồ sơ, thì đó là quá sớm.


3
Tôi đồng ý với ý kiến ​​đằng sau nó, nhưng cũng có thể: trừ khi việc triển khai bị ràng buộc hoàn toàn bởi các chu kỳ CPU, việc có được một phép đo vừa có thể tái tạo lại vừa có thể tổng quát hóa là điều khó - và nó càng ổn định thì nó càng kém thực tế.
peterchen

1
Vấn đề tôi gặp phải với câu trả lời ở trên là nó ngụ ý rằng bạn không thể tối ưu hóa một thuật toán trước khi bạn viết mã nó. Cách làm việc của tôi có xu hướng thiết kế thuật toán để đáp ứng các yêu cầu chức năng. Xem xét liệu nó có khả năng không đạt yêu cầu về hiệu suất hay không (ví dụ: độ phức tạp cao và có khả năng đạt được các tập dữ liệu lớn) và tối ưu hóa thuật toán trước khi bắt đầu viết mã. Tối ưu hóa chỉ đơn giản là sàng lọc để đạt được giải pháp tối ưu, nó thường được thực hiện hiệu quả nhất ở giai đoạn thiết kế.
SmacL

2
Tôi không đồng ý. Knuth đã nói về những hiệu quả nhỏ. Tối ưu hóa thường xảy ra ở giai đoạn thiết kế. Nó liên quan đến việc lựa chọn cấu trúc dữ liệu và thuật toán thích hợp, thường có tác động lớn đến hiệu suất và không nhất thiết phải trao đổi sau này.
haslersn

@haslersn: "Knuth đang nói về hiệu quả nhỏ" Donald Knuth: "Sự khôn ngoan thông thường được chia sẻ bởi nhiều kỹ sư phần mềm ngày nay kêu gọi bỏ qua hiệu quả trong phạm vi nhỏ; nhưng tôi tin rằng đây chỉ đơn giản là một phản ứng thái quá đối với việc lạm dụng (...) Trong ngành kỹ thuật thành lập một sự cải thiện 12%, dễ dàng có được, không bao giờ được coi là biên (...)"
Pedro Couto Amaral

27

Câu hỏi của tôi là, nếu một kỹ thuật cụ thể khác nhưng không đặc biệt tối nghĩa hoặc khó hiểu, thì đó có thể thực sự được coi là một tối ưu hóa quá sớm không?

Ừm ... Vậy là bạn đã có sẵn hai kỹ thuật, giống nhau về chi phí (cùng nỗ lực sử dụng, đọc, sửa đổi) và một kỹ thuật hiệu quả hơn. Không, sử dụng cái hiệu quả hơn, trong trường hợp đó, sẽ không quá sớm.

Làm gián đoạn quá trình viết mã của bạn để tìm kiếm các lựa chọn thay thế cho các cấu trúc lập trình / quy trình thư viện thông thường nếu không may có một phiên bản hiệu quả hơn đang ở đâu đó mặc dù bạn biết tốc độ tương đối của những gì bạn đang viết sẽ không bao giờ thực sự quan trọng. .. Đó là quá sớm.


3
Đồng ý, nếu bạn biết một thuật toán hiệu quả hơn cho trường hợp sử dụng của bạn, bằng mọi cách, hãy sử dụng thuật toán hiệu quả hơn. Nếu bạn không biết thuật toán hiệu quả nhất, hãy sử dụng những gì bạn có và hồ sơ sau để xem nó có phải là vấn đề hay không.
grepsedawk

10

Đây là vấn đề tôi thấy với toàn bộ khái niệm về việc tránh tối ưu hóa quá sớm.

Có một sự ngắt kết nối giữa nói và làm.

Tôi đã thực hiện rất nhiều điều chỉnh hiệu suất, loại bỏ các yếu tố lớn ra khỏi mã được thiết kế tốt, dường như được thực hiện mà không cần tối ưu hóa sớm. Đây là một ví dụ.

Trong hầu hết mọi trường hợp, lý do cho hiệu suất dưới mức tối ưu là cái mà tôi gọi là tính tổng quát phi mã , đó là việc sử dụng các lớp nhiều lớp trừu tượng và thiết kế hướng đối tượng kỹ lưỡng, trong đó các khái niệm đơn giản sẽ kém thanh lịch hơn nhưng hoàn toàn đủ.

Và trong tài liệu giảng dạy nơi các khái niệm thiết kế trừu tượng này được dạy, chẳng hạn như kiến ​​trúc theo hướng thông báo và ẩn thông tin, nơi chỉ cần thiết lập thuộc tính boolean của một đối tượng có thể tạo ra hiệu ứng gợn sóng không giới hạn của các hoạt động, lý do được đưa ra là gì? Hiệu quả .

Vì vậy, đó có phải là tối ưu hóa quá sớm hay không?


Tôi thích câu trả lời này, vì nó minh họa một trong những vấn đề lớn về trừu tượng và khái quát hóa. Khi bạn tổng quát hóa một hệ thống phân cấp lớp để hỗ trợ nhiều trường hợp sử dụng hơn, thì việc làm giảm nghiêm trọng hiệu suất đối với các trường hợp sử dụng điển hình nhất là quá dễ dàng. Cũng dễ dàng bám vào một lớp cung cấp một phần chức năng nhất định mà không cần kiểm tra xem chức năng đó có được cung cấp ở mức hiệu suất có thể chấp nhận được đối với quy mô sử dụng dự định hay không.
SmacL

1
"nơi mà các khái niệm đơn giản sẽ kém thanh lịch hơn nhưng hoàn toàn đủ" Mã phức tạp hiếm khi thanh lịch hơn mã đơn giản khi mã đơn giản đáp ứng các yêu cầu. (Mặc dù, tôi sẽ cho rằng bạn phải đảm bảo mã đơn giản của bạn thực sự bùng nổ với một dấu hiệu rõ ràng của tình trạng không được hỗ trợ / input nếu cố gắng một người nào đó để thực hiện nó trên một trường hợp phức tạp hơn.)
jpmc26

8

Đầu tiên, làm cho mã hoạt động. Thứ hai, xác minh rằng mã là chính xác. Thứ ba, làm cho nó nhanh chóng.

Bất kỳ thay đổi mã nào được thực hiện trước giai đoạn 3 chắc chắn là quá sớm. Tôi không hoàn toàn chắc chắn làm thế nào để phân loại các lựa chọn thiết kế được thực hiện trước đó (như sử dụng cấu trúc dữ liệu phù hợp), nhưng tôi thích chuyển sang sử dụng các yếu tố trừu tượng mà taht dễ lập trình hơn là những người hoạt động tốt, cho đến khi tôi ở một giai đoạn mà tôi có thể bắt đầu sử dụng cấu hình và triển khai tham chiếu chính xác (mặc dù thường chậm) để so sánh kết quả với.


7

Những gì bạn có vẻ đang nói đến là tối ưu hóa như sử dụng một vùng chứa tra cứu dựa trên băm so với một vùng chứa được lập chỉ mục như một mảng khi nhiều tìm kiếm chính sẽ được thực hiện. Đây không phải là tối ưu hóa quá sớm mà là thứ bạn nên quyết định trong giai đoạn thiết kế.

Loại tối ưu hóa mà quy tắc Knuth hướng tới là giảm thiểu độ dài của các đường dẫn phổ biến nhất, tối ưu hóa mã được chạy nhiều nhất bằng cách viết lại trong assembly hoặc đơn giản hóa mã, làm cho nó ít chung chung hơn. Nhưng làm điều này không có ích lợi gì cho đến khi bạn chắc chắn phần nào của mã cần loại tối ưu hóa này và việc tối ưu hóa sẽ (có thể?) Làm cho mã khó hiểu hoặc khó bảo trì hơn, do đó "tối ưu hóa quá sớm là gốc rễ của mọi điều ác".

Knuth cũng nói rằng tốt hơn hết là thay vì tối ưu hóa, thay đổi các thuật toán mà chương trình của bạn sử dụng, cách tiếp cận một vấn đề. Ví dụ: trong khi một chút tinh chỉnh có thể giúp bạn tăng 10% tốc độ với tối ưu hóa, việc thay đổi về cơ bản cách thức hoạt động của chương trình có thể làm cho nó nhanh hơn 10 lần.

Để phản ứng với rất nhiều nhận xét khác được đăng về câu hỏi này: lựa chọn thuật toán! = Tối ưu hóa


7

Từ góc độ cơ sở dữ liệu, việc không xem xét thiết kế tối ưu ở giai đoạn thiết kế là điều tối kỵ. Cơ sở dữ liệu không cấu trúc lại dễ dàng. Một khi chúng được thiết kế kém (đây là thiết kế không coi là tối ưu hóa, bất kể bạn có cố gắng che giấu điều vô nghĩa của việc tối ưu hóa quá sớm như thế nào), hầu như không bao giờ có thể khôi phục từ đó do cơ sở dữ liệu quá cơ bản hoạt động của toàn hệ thống. Sẽ ít tốn kém hơn nhiều để thiết kế chính xác mã tối ưu cho tình huống bạn mong đợi hơn là đợi cho đến khi có một triệu người dùng và mọi người đang la hét vì bạn đã sử dụng con trỏ trong suốt ứng dụng. Các tối ưu hóa khác như sử dụng mã có thể kiểm tra được, chọn giao diện là chỉ mục tốt nhất có thể, v.v. chỉ có ý nghĩa khi thực hiện tại thời điểm thiết kế. Có một lý do tại sao nhanh và bẩn được gọi như vậy. Bởi vì nó không thể hoạt động tốt bao giờ, vì vậy đừng sử dụng tính nhanh để thay thế cho mã tốt. Cũng thành thật mà nói khi bạn hiểu điều chỉnh hiệu suất trong cơ sở dữ liệu, bạn có thể viết mã có nhiều khả năng hoạt động tốt trong cùng một thời gian hoặc ít hơn so với việc viết mã không hoạt động tốt. Không dành thời gian để tìm hiểu thiết kế cơ sở dữ liệu hoạt động tốt là gì là sự lười biếng của nhà phát triển, không phải là phương pháp hay nhất.


6

Thông thường , điểm của câu châm ngôn là tối ưu hóa phức tạp và phức tạp. Và thông thường , bạn là kiến ​​trúc sư / nhà thiết kế / lập trình viên / người bảo trì cần mã rõ ràng và ngắn gọn để hiểu những gì đang xảy ra.

Nếu một tối ưu hóa cụ thể rõ ràng và ngắn gọn, hãy thoải mái thử nghiệm với nó (nhưng hãy quay lại và kiểm tra xem liệu tối ưu hóa đó có hiệu quả hay không). Điểm mấu chốt là giữ cho mã rõ ràng và ngắn gọn trong suốt quá trình phát triển, cho đến khi lợi ích của hiệu suất lớn hơn chi phí viết và duy trì tối ưu hóa.


2
Trên thực tế, một chút "tối ưu hóa" hợp lý nằm ở việc chọn thuật toán thích hợp cho công việc; đó là hoạt động cấp cao với kết quả cấp cao - khác xa so với "hiệu quả nhỏ" trong trích dẫn của Knuth.
Shog9

4

Tôi cố gắng chỉ tối ưu hóa khi vấn đề hiệu suất được xác nhận.

Định nghĩa của tôi về tối ưu hóa sớm là 'nỗ lực bị lãng phí vào mã không được coi là vấn đề về hiệu suất.' Chắc chắn là có thời gian và địa điểm để tối ưu hóa. Tuy nhiên, mẹo ở đây là chi phí bổ sung chỉ được tính vào hiệu suất của ứng dụng và khi chi phí bổ sung lớn hơn hiệu suất đạt được.

Khi viết mã (hoặc một truy vấn DB), tôi cố gắng viết mã 'hiệu quả' (tức là mã thực hiện chức năng dự kiến ​​của nó, nhanh chóng và hoàn chỉnh với logic đơn giản nhất.) Lưu ý rằng mã 'hiệu quả' không nhất thiết phải giống với mã 'tối ưu hóa' mã. Tính tối ưu thường đưa thêm độ phức tạp vào mã làm tăng cả chi phí phát triển và bảo trì của mã đó.

Lời khuyên của tôi: Cố gắng chỉ trả chi phí tối ưu hóa khi bạn có thể định lượng được lợi ích.


4

Khi lập trình, một số tham số rất quan trọng. Trong số này có:

  • Dễ đọc
  • Khả năng bảo trì
  • Phức tạp
  • Mạnh mẽ
  • Tính đúng đắn
  • Hiệu suất
  • Thời gian phát triển

Tối ưu hóa (để đạt được hiệu suất) thường đi kèm với chi phí của các thông số khác, và phải được cân bằng với sự "mất mát" trong các lĩnh vực này.

Khi bạn có tùy chọn chọn các thuật toán nổi tiếng hoạt động tốt, chi phí "tối ưu hóa" trước thường có thể chấp nhận được.


1
Bạn đang thiếu một tham số QA quan trọng nhất trong danh sách ở trên; Đáp ứng yêu cầu. Nếu một phần mềm không đáp ứng được yêu cầu của đối tượng dự kiến, tất cả các thông số khác đều vô nghĩa. Nếu hiệu suất không được chấp nhận, các yêu cầu chưa được đáp ứng.
SmacL 22/12/08

3
Điều đó có thể nói là được bao phủ bởi tính đúng đắn. Bên cạnh đó, 'hiệu suất' theo nghĩa 'càng nhanh càng tốt' rất hiếm khi nằm trong số các yêu cầu, và thậm chí quan điểm của Ola về việc nó là sự cân bằng với các nhu cầu khác vẫn đúng.
Frankodwyer 22/12/08

4

Việc tối ưu hóa có thể xảy ra ở các cấp độ chi tiết khác nhau, từ cấp rất cao đến cấp rất thấp:

  1. Bắt đầu với một kiến ​​trúc tốt, khớp nối lỏng lẻo, mô-đun, v.v.

  2. Chọn cấu trúc dữ liệu và thuật toán phù hợp cho vấn đề.

  3. Tối ưu hóa cho bộ nhớ, cố gắng khớp nhiều mã / dữ liệu hơn trong bộ đệm. Hệ thống con bộ nhớ chậm hơn CPU từ 10 đến 100 lần và nếu dữ liệu của bạn được phân trang vào đĩa, nó sẽ chậm hơn 1000 đến 10.000 lần. Thận trọng về mức tiêu thụ bộ nhớ có nhiều khả năng mang lại lợi ích lớn hơn là tối ưu hóa các hướng dẫn riêng lẻ.

  4. Trong mỗi chức năng, hãy sử dụng thích hợp các câu lệnh điều khiển luồng. (Di chuyển các biểu thức bất biến ra bên ngoài thân vòng lặp. Đặt giá trị phổ biến nhất trước trong một switch / case, v.v.)

  5. Trong mỗi câu lệnh, hãy sử dụng các biểu thức hiệu quả nhất để mang lại kết quả chính xác. (Nhân so với ca, v.v.)

Việc phân tích xem nên sử dụng biểu thức chia hay biểu thức shift không nhất thiết tối ưu hóa quá sớm. Sẽ chỉ là quá sớm nếu bạn làm như vậy mà không tối ưu hóa kiến ​​trúc, cấu trúc dữ liệu, thuật toán, dấu chân bộ nhớ và kiểm soát luồng trước.

Và tất nhiên, bất kỳ tối ưu hóa nào cũng sớm nếu bạn không xác định ngưỡng hiệu suất mục tiêu.

Trong hầu hết các trường hợp, một trong hai:

A) Bạn có thể đạt đến ngưỡng hiệu suất mục tiêu bằng cách thực hiện tối ưu hóa cấp cao, do đó, bạn không cần phải loay hoay với các biểu thức.

hoặc là

B) Ngay cả sau khi thực hiện tất cả các tối ưu hóa có thể, bạn sẽ không đạt được ngưỡng hiệu suất mục tiêu của mình và các tối ưu hóa cấp thấp không tạo ra đủ khác biệt về hiệu suất để biện minh cho việc mất khả năng đọc.

Theo kinh nghiệm của tôi, hầu hết các vấn đề tối ưu hóa có thể được giải quyết ở cấp độ kiến ​​trúc / thiết kế hoặc cấu trúc dữ liệu / thuật toán. Tối ưu hóa cho dấu chân bộ nhớ thường được yêu cầu (mặc dù không phải luôn luôn). Nhưng hiếm khi cần tối ưu hóa logic biểu thức và điều khiển luồng. Và trong những trường hợp thực sự cần thiết, nó hiếm khi đủ.


3

Sự cần thiết phải sử dụng một hồ sơ nên được để lại cho những trường hợp cực đoan. Các kỹ sư của dự án nên nhận thức được vị trí tắc nghẽn hiệu suất.

Tôi nghĩ rằng "tối ưu hóa sớm" là vô cùng chủ quan.

Nếu tôi đang viết một số mã và tôi biết rằng tôi nên sử dụng Hashtable thì tôi sẽ làm điều đó. Tôi sẽ không triển khai nó theo một cách nào đó thiếu sót và sau đó đợi báo cáo lỗi đến một tháng hoặc một năm sau khi ai đó gặp sự cố với nó.

Thiết kế lại tốn kém hơn so với tối ưu hóa thiết kế theo những cách rõ ràng ngay từ đầu.

Rõ ràng là một số điều nhỏ sẽ bị bỏ qua trong lần đầu tiên nhưng đây hiếm khi là những quyết định thiết kế quan trọng.

Do đó: KHÔNG tối ưu hóa thiết kế là IMO có mùi mã trong và của chính nó.


Vấn đề là các nút thắt cổ chai thường xuất hiện trong các đoạn mã mà bạn chưa bao giờ nghĩ là có vấn đề. Lập hồ sơ phân phối giả mạo và hiển thị các trung tâm chi phí thực tế của chương trình. Tốt nhất bạn nên làm những việc rõ ràng ngay từ đầu, nhưng đối với mọi thứ khác thì cần phải có hồ sơ.
Chris Smith

2

Câu trả lời của Norman là tuyệt vời. Bằng cách nào đó, bạn thường xuyên thực hiện một số "tối ưu hóa sớm", thực ra là các phương pháp hay nhất, bởi vì làm theo cách khác được biết là hoàn toàn không hiệu quả.

Ví dụ, để thêm vào danh sách của Norman:

  • Sử dụng nối StringBuilder trong Java (hoặc C #, v.v.) thay vì String + String (trong một vòng lặp);
  • Tránh lặp lại trong C như: for (i = 0; i < strlen(str); i++)(vì strlen ở đây là một hàm gọi đi bộ chuỗi mỗi lần, được gọi trên mỗi vòng lặp);
  • Có vẻ như trong hầu hết các triển khai JavaScript, nó cũng nhanh hơn for (i = 0 l = str.length; i < l; i++)và nó vẫn có thể đọc được, vì vậy OK.

Và như thế. Nhưng những tối ưu hóa vi mô như vậy không bao giờ phải trả giá bằng khả năng đọc được của mã.


2

Điều đáng chú ý là trích dẫn ban đầu của Knuth đến từ một bài báo mà anh ấy viết về việc quảng bá việc sử dụng gotoở các khu vực được đo lường và lựa chọn cẩn thận như một cách để loại bỏ các điểm nóng. Trích dẫn của anh ấy là một lời cảnh báo mà anh ấy thêm vào để biện minh cho lý do anh ấy sử dụng gotođể tăng tốc các vòng lặp quan trọng đó.

[...] một lần nữa, đây là một tiết kiệm đáng chú ý trong tốc độ chạy tổng thể, giả sử, giá trị trung bình của n là khoảng 20 và nếu quy trình tìm kiếm được thực hiện khoảng một triệu lần trong chương trình. Các cách tối ưu hóa vòng lặp như vậy [sử dụng gotos] không khó học và như tôi đã nói, chúng chỉ thích hợp trong một phần nhỏ của chương trình, nhưng chúng thường mang lại tiết kiệm đáng kể. [...]

Và tiếp tục:

Sự khôn ngoan thông thường được chia sẻ bởi nhiều kỹ sư phần mềm ngày nay kêu gọi bỏ qua hiệu quả trong phạm vi nhỏ; nhưng tôi tin rằng đây chỉ đơn giản là một phản ứng thái quá đối với những hành vi lạm dụng mà họ thấy đang được thực hiện bởi các lập trình viên ngu ngốc, những người không thể gỡ lỗi hoặc duy trì các chương trình "được tối ưu hóa" của họ. Trong các lĩnh vực kỹ thuật đã được thiết lập, một cải tiến 12%, dễ dàng đạt được, không bao giờ được coi là biên; và tôi tin rằng quan điểm tương tự sẽ phổ biến trong kỹ thuật phần mềm. Tất nhiên tôi sẽ không bận tâm đến việc tối ưu hóa như vậy cho một công việc oneshot, nhưng khi đặt câu hỏi về việc chuẩn bị các chương trình chất lượng, tôi không muốn giới hạn bản thân mình trong các công cụ phủ nhận tính hiệu quả của tôi [tức là các goto tuyên bố trong bối cảnh này].

Hãy ghi nhớ cách anh ấy sử dụng "tối ưu hóa" trong dấu ngoặc kép (phần mềm có thể không thực sự hiệu quả). Cũng lưu ý rằng anh ấy không chỉ chỉ trích những lập trình viên "ngu xuẩn và ngu ngốc" này mà còn cả những người phản ứng bằng cách gợi ý bạn nên luôn bỏ qua những điểm kém hiệu quả nhỏ. Cuối cùng, đến phần thường được trích dẫn:

Không có nghi ngờ rằng chén hiệu quả dẫn đến lạm dụng. Các lập trình viên lãng phí một lượng lớn thời gian để suy nghĩ hoặc lo lắng về tốc độ của các phần không quan trọng trong chương trình của họ, và những nỗ lực về hiệu quả này thực sự có tác động tiêu cực mạnh khi xem xét việc gỡ lỗi và bảo trì. Chúng ta nên quên đi những hiệu quả nhỏ, ví dụ như 97% thời gian; tối ưu hóa quá sớm là gốc rễ của mọi điều ác.

... và sau đó là một số thông tin khác về tầm quan trọng của các công cụ lập hồ sơ:

Thường là sai lầm khi đưa ra các phán đoán trước về những phần nào của chương trình thực sự quan trọng, vì kinh nghiệm phổ biến của các lập trình viên đã sử dụng các công cụ đo lường là các dự đoán trực quan của họ không thành công. Sau khi làm việc với các công cụ như vậy trong bảy năm, tôi tin rằng tất cả các trình biên dịch được viết từ bây giờ trở đi nên được thiết kế để cung cấp cho tất cả các lập trình viên phản hồi cho biết phần nào trong chương trình của họ đang tốn kém nhất; thực sự, phản hồi này sẽ được cung cấp tự động trừ khi nó đã được tắt cụ thể.

Mọi người đã sử dụng sai trích dẫn của anh ấy ở khắp nơi, thường cho rằng các tối ưu hóa vi mô là quá sớm khi toàn bộ bài báo của anh ấy ủng hộ các tối ưu hóa vi mô! Một trong những nhóm người mà anh ấy đang chỉ trích, những người lặp lại "sự khôn ngoan thông thường" này vì anh ấy luôn bỏ qua những hiệu quả nhỏ thường lạm dụng trích dẫn của anh ấy vốn được hướng dẫn ban đầu, một phần, chống lại những người không khuyến khích tất cả các hình thức tối ưu hóa vi mô .

Tuy nhiên, đó là một trích dẫn ủng hộ các tối ưu hóa vi mô được áp dụng thích hợp khi được sử dụng bởi một tay có kinh nghiệm đang nắm giữ một trình biên dịch. Tương tự tương tự ngày nay có thể là, "Mọi người không nên quá mù quáng vào việc tối ưu hóa phần mềm của họ, nhưng trình phân bổ bộ nhớ tùy chỉnh có thể tạo ra sự khác biệt lớn khi được áp dụng trong các lĩnh vực chính để cải thiện vị trí tham chiếu" hoặc, " Mã SIMD viết tay sử dụng SoA thực sự rất khó để duy trì và bạn không nên sử dụng nó khắp nơi, nhưng nó có thể tiêu tốn bộ nhớ nhanh hơn nhiều khi được áp dụng một cách thích hợp bởi một tay có kinh nghiệm và hướng dẫn. "

Bất cứ khi nào bạn đang cố gắng quảng bá các tính năng tối ưu hóa vi mô được áp dụng cẩn thận như Knuth đã quảng cáo ở trên, bạn nên đưa ra tuyên bố từ chối trách nhiệm để ngăn cản những người mới sử dụng quá hào hứng và mù quáng tham gia vào việc tối ưu hóa, chẳng hạn như viết lại toàn bộ phần mềm của họ để sử dụng goto. Đó là một phần những gì anh ấy đang làm. Câu nói của anh ấy thực sự là một phần của một tuyên bố từ chối trách nhiệm lớn, giống như việc ai đó thực hiện một chiếc mô tô nhảy qua một hố lửa rực lửa có thể thêm một lời tuyên bố từ chối trách nhiệm rằng những người nghiệp dư không nên thử điều này ở nhà đồng thời chỉ trích những người cố gắng mà không có kiến ​​thức và thiết bị phù hợp và bị thương .

Những gì ông cho là "tối ưu hóa quá sớm" là những tối ưu hóa được áp dụng bởi những người không biết họ đang làm gì một cách hiệu quả: không biết liệu tối ưu hóa có thực sự cần thiết hay không, không đo lường bằng các công cụ thích hợp, có thể không hiểu bản chất của trình biên dịch hoặc kiến ​​trúc máy tính của họ, và hơn hết là "ngu xuẩn xuẩn xuẩn", nghĩa là họ đã bỏ qua những cơ hội lớn để tối ưu hóa (tiết kiệm hàng triệu đô la) bằng cách cố gắng kiếm từng xu, và tất cả trong khi tạo mã, họ không thể gỡ lỗi và duy trì hiệu quả hơn.

Nếu bạn không phù hợp với danh mục "xuẩn ngốc và xuẩn ngốc", thì bạn không sớm tối ưu hóa theo tiêu chuẩn của Knuth, ngay cả khi bạn đang sử dụng gotođể tăng tốc một vòng lặp quan trọng (điều khó xảy ra để giúp nhiều chống lại các trình tối ưu hóa ngày nay, nhưng nếu có và trong một lĩnh vực thực sự quan trọng, thì bạn sẽ không tối ưu hóa sớm). Nếu bạn thực sự áp dụng bất cứ điều gì bạn đang làm trong những lĩnh vực thực sự cần thiết và họ thực sự được hưởng lợi từ nó, thì bạn đang làm rất tốt trong mắt Knuth.


1

Đối với tôi, tối ưu hóa sớm có nghĩa là cố gắng cải thiện hiệu quả mã của bạn trước khi bạn có một hệ thống hoạt động và trước khi bạn thực sự cấu hình nó và biết điểm nghẽn ở đâu. Ngay cả sau đó, khả năng đọc và khả năng bảo trì nên đi trước khi tối ưu hóa trong nhiều trường hợp.


1

Tôi không nghĩ rằng các phương pháp hay nhất được công nhận là tối ưu hóa quá sớm. Đó là nhiều hơn về việc đốt cháy thời gian về những gì nếu là vấn đề hiệu suất tiềm ẩn tùy thuộc vào các tình huống sử dụng. Một ví dụ điển hình: Nếu bạn đốt cháy một tuần để cố gắng tối ưu hóa phản ánh qua một đối tượng trước khi bạn có bằng chứng rằng đó là nút thắt cổ chai thì bạn đang tối ưu hóa quá sớm.


1

Trừ khi bạn nhận thấy rằng bạn cần nhiều hiệu suất hơn từ ứng dụng của mình, do nhu cầu của người dùng hoặc doanh nghiệp, có rất ít lý do để lo lắng về việc tối ưu hóa. Ngay cả khi đó, đừng làm bất cứ điều gì cho đến khi bạn đã cấu hình mã của mình. Sau đó tấn công những phần mất nhiều thời gian nhất.


0

Theo cách tôi thấy, nếu bạn tối ưu hóa thứ gì đó mà không biết bạn có thể đạt được bao nhiêu hiệu suất trong các trường hợp khác thì đó là một sự tối ưu hóa quá sớm. Mục tiêu của mã phải thực sự giúp con người đọc dễ dàng nhất.


0

Như tôi đã đăng trên một câu hỏi tương tự, các quy tắc tối ưu hóa là:

1) Không tối ưu hóa

2) (chỉ dành cho chuyên gia) Tối ưu hóa sau

Khi nào thì tối ưu hóa quá sớm? Thông thường.

Ngoại lệ có lẽ là trong thiết kế của bạn hoặc trong mã được đóng gói tốt được sử dụng nhiều. Trong quá khứ, tôi đã làm việc trên một số mã quan trọng về thời gian (một triển khai RSA) trong đó việc xem xét trình hợp dịch mà trình biên dịch tạo ra và loại bỏ một lệnh không cần thiết duy nhất trong một vòng lặp bên trong giúp tăng tốc độ 30%. Tuy nhiên, tốc độ tăng tốc từ việc sử dụng các thuật toán phức tạp hơn có mức độ lớn hơn thế.

Một câu hỏi khác để tự hỏi bản thân khi tối ưu hóa là "tôi có đang làm tương đương với tối ưu hóa cho modem 300 baud ở đây không?" . Nói cách khác, liệu định luật Moore có làm cho việc tối ưu hóa của bạn trở nên không phù hợp trước khi quá lâu hay không. Nhiều vấn đề về tỷ lệ có thể được giải quyết chỉ bằng cách ném thêm phần cứng vào vấn đề.

Cuối cùng nhưng không kém phần quan trọng, đó là quá sớm để tối ưu hóa trước khi chương trình diễn ra quá chậm. Nếu đó là ứng dụng web mà bạn đang nói đến, bạn có thể chạy nó ở chế độ tải để xem các nút thắt ở đâu - nhưng khả năng là bạn sẽ gặp vấn đề mở rộng giống như hầu hết các trang web khác và các giải pháp tương tự sẽ được áp dụng.

chỉnh sửa: Tình cờ, liên quan đến bài báo được liên kết, tôi sẽ đặt câu hỏi về nhiều giả định được đưa ra. Thứ nhất, định luật Moore ngừng hoạt động vào những năm 90 là không đúng. Thứ hai, không rõ ràng rằng thời gian của người dùng quý hơn thời gian của lập trình viên. Dù sao đi nữa, hầu hết người dùng đều không điên cuồng sử dụng mọi chu kỳ CPU có sẵn, họ có thể đang chờ mạng thực hiện điều gì đó. Thêm vào đó, có một chi phí cơ hội khi thời gian của lập trình viên bị chuyển hướng từ việc thực hiện một thứ gì đó khác, để loại bỏ một vài mili giây thứ gì đó mà chương trình thực hiện trong khi người dùng đang sử dụng điện thoại. Bất cứ điều gì dài hơn thường không phải là tối ưu hóa, đó là sửa lỗi.

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.