Chiến lược tối ưu hóa hiệu suất của giải pháp cuối cùng [đóng cửa]


609

Có rất nhiều câu hỏi về hiệu suất trên trang web này, nhưng đối với tôi, hầu hết tất cả đều rất cụ thể và khá hẹp. Và hầu như tất cả lặp lại lời khuyên để tránh tối ưu hóa sớm.

Giả sử:

  • mã đã hoạt động chính xác
  • các thuật toán được chọn đã tối ưu cho các tình huống của vấn đề
  • mã đã được đo và các thói quen vi phạm đã bị cô lập
  • tất cả các nỗ lực để tối ưu hóa cũng sẽ được đo lường để đảm bảo chúng không làm cho vấn đề tồi tệ hơn

Những gì tôi đang tìm kiếm ở đây là các chiến lược và thủ thuật để đạt được vài phần trăm cuối cùng trong một thuật toán quan trọng khi không còn gì để làm ngoài bất cứ điều gì cần thiết.

Tốt nhất, hãy cố gắng đưa ra câu trả lời bất khả tri về ngôn ngữ và chỉ ra bất kỳ mặt trái nào đối với các chiến lược được đề xuất khi áp dụng.

Tôi sẽ thêm một câu trả lời với các đề xuất ban đầu của riêng tôi và mong muốn bất cứ điều gì khác mà cộng đồng Stack Overflow có thể nghĩ ra.

Câu trả lời:


427

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 mallocmiễ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 ++ihoặ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.


3
Tôi rất thích đọc một số chi tiết của các bước bạn phác thảo ở trên. Có thể bao gồm một số mảnh tối ưu hóa cho hương vị? (mà không làm bài đăng quá dài?)
jerryjvl

8
... Tôi cũng đã viết một cuốn sách sắp in, vì vậy nó sẽ có giá vô lý trên Amazon - "Xây dựng ứng dụng tốt hơn" ISBN 0442017405. Về cơ bản, cùng một tài liệu nằm trong chương đầu tiên.
Mike Dunlavey

3
@Mike Dunlavey, tôi khuyên bạn nên nói với Google rằng bạn đã quét nó rồi. Họ có thể đã có một thỏa thuận với bất cứ ai đã mua nhà xuất bản của bạn.
Thorbjørn Ravn Andersen

19
@ Thorbjørn: Chỉ để theo dõi, tôi đã kết nối với GoogleBooks, điền vào tất cả các biểu mẫu và gửi cho họ một bản sao cứng. Tôi nhận được email hỏi tôi có thực sự sở hữu bản quyền không. Nhà xuất bản Van Nostrand Reinhold, được mua bởi International Thompson, được mua bởi Reuters, và khi tôi cố gắng gọi hoặc gửi email cho họ, nó giống như một lỗ đen. Vì vậy, nó ở trong tình trạng lấp lửng - Tôi chưa có năng lượng để thực sự đuổi theo nó.
Mike Dunlavey


188

Gợi ý:

  • Tính toán trước thay vì tính toán lại : bất kỳ vòng lặp hoặc cuộc gọi lặp lại nào có chứa các phép tính có phạm vi đầu vào tương đối hạn chế, hãy xem xét thực hiện tra cứu (mảng hoặc từ điển) có chứa kết quả của phép tính đó cho tất cả các giá trị trong phạm vi hợp lệ của đầu vào. Sau đó sử dụng một tra cứu đơn giản bên trong thuật toán thay thế.
    Mặt trái : nếu một vài trong số các giá trị được tính toán trước thực sự được sử dụng, điều này có thể làm cho vấn đề tồi tệ hơn, việc tra cứu có thể mất bộ nhớ đáng kể.
  • Không sử dụng các phương thức thư viện : hầu hết các thư viện cần được viết để hoạt động chính xác trong một loạt các kịch bản và thực hiện kiểm tra null trên các tham số, v.v. Bằng cách triển khai lại một phương thức, bạn có thể loại bỏ rất nhiều logic mà không áp dụng trong trường hợp chính xác bạn đang sử dụng nó.
    Xuống-bên : viết thêm phương tiện mã vùng bề mặt nhiều hơn cho các lỗi.
  • Đừng sử dụng các phương pháp thư viện : để mâu thuẫn với chính tôi, các thư viện ngôn ngữ được viết bởi những người thông minh hơn bạn hoặc tôi rất nhiều; tỷ lệ cược là họ đã làm nó tốt hơn và nhanh hơn. Không tự thực hiện trừ khi bạn thực sự có thể làm cho nó nhanh hơn (nghĩa là: luôn luôn đo lường!)
  • Cheat : trong một số trường hợp mặc dù tính toán chính xác có thể tồn tại cho vấn đề của bạn, bạn có thể không cần 'chính xác', đôi khi một phép tính gần đúng có thể 'đủ tốt' và nhanh hơn rất nhiều trong thỏa thuận. Hãy tự hỏi mình, nó có thực sự quan trọng nếu câu trả lời là 1% không? 5%? thậm chí 10%?
    Mặt trái : Chà ... câu trả lời sẽ không chính xác.

32
Tính toán trước không phải lúc nào cũng có ích, và đôi khi nó có thể làm tổn thương - nếu bảng tra cứu của bạn quá lớn, nó có thể giết chết hiệu suất bộ nhớ cache của bạn.
Adam Rosenfield

37
Gian lận thường có thể là chiến thắng. Tôi đã có một quá trình hiệu chỉnh màu sắc mà ở lõi là một vectơ 3 chấm với ma trận 3x3. CPU có một ma trận nhân lên trong phần cứng, bỏ qua một số thuật ngữ chéo và thực sự nhanh so với tất cả các cách khác để làm điều đó, nhưng chỉ hỗ trợ ma trận 4 x 4 và 4 vectơ nổi. Thay đổi mã để mang theo khoảng trống bổ sung và chuyển đổi phép tính thành điểm nổi từ điểm cố định cho phép kết quả kém chính xác hơn một chút nhưng nhanh hơn nhiều .
RBerteig

6
Gian lận là trong việc sử dụng một ma trận nhân mà bỏ đi một số sản phẩm bên trong, cho phép thực hiện bằng microcode cho một lệnh CPU duy nhất hoàn thành nhanh hơn cả chuỗi lệnh tương đương có thể. Đó là một mánh gian lận vì nó không nhận được câu trả lời "đúng", chỉ là một câu trả lời "đủ chính xác".
RBerteig

6
@RBerteig: chỉ "đủ chính xác" là cơ hội tối ưu hóa mà hầu hết mọi người bỏ lỡ trong trải nghiệm của tôi.
Martin Thompson

5
Bạn không thể luôn cho rằng mọi người đều thông minh hơn bạn. Cuối cùng, tất cả chúng ta đều là những người chuyên nghiệp. Tuy nhiên, bạn có thể giả sử rằng một thư viện cụ thể mà bạn sử dụng tồn tại và đã tiếp cận với môi trường của bạn vì chất lượng của nó, do đó việc viết thư viện này phải rất kỹ lưỡng, bạn không thể làm điều đó chỉ vì bạn không chuyên về điều đó và bạn không đầu tư cùng loại thời gian vào đó. Không phải vì bạn kém thông minh. nào.
v.oddou

164

Khi bạn không thể cải thiện hiệu suất nữa - hãy xem liệu bạn có thể cải thiện hiệu suất nhận thức thay thế không.

Bạn có thể không thể làm cho thuật toán fooCalc của mình nhanh hơn, nhưng thường có nhiều cách để làm cho ứng dụng của bạn có vẻ phản ứng nhanh hơn với người dùng.

Một vài ví dụ:

  • dự đoán những gì người dùng sẽ yêu cầu và bắt đầu làm việc trước đó
  • hiển thị kết quả khi chúng đến, thay vì tất cả cùng một lúc ở cuối
  • Máy đo tiến độ chính xác

Những thứ này sẽ không làm cho chương trình của bạn nhanh hơn, nhưng nó có thể khiến người dùng của bạn hài lòng hơn với tốc độ bạn có.


27
Một thanh tiến trình tăng tốc ở cuối có thể được coi là nhanh hơn một thanh hoàn toàn chính xác. Trong "Xem xét lại thanh tiến trình" (2007) Harrison, Amento, Kuznetsov và Bell kiểm tra nhiều loại thanh trên một nhóm người dùng cũng như thảo luận về một số cách sắp xếp lại các hoạt động để tiến trình có thể được coi là nhanh hơn.
Emil Vikström

9
naxa, hầu hết các thanh tiến trình là giả vì dự đoán nhiều bước khác nhau rộng rãi của một luồng thành một tỷ lệ phần trăm là khó hoặc đôi khi không thể. Chỉ cần nhìn vào tất cả các thanh bị kẹt ở 99% :-(
Emil Vikström

138

Tôi dành phần lớn cuộc đời của tôi chỉ ở nơi này. Các nét rộng là để chạy hồ sơ của bạn và làm cho nó ghi lại:

  • Bộ nhớ cache bị mất . Bộ nhớ cache dữ liệu là nguồn số 1 của các quầy hàng trong hầu hết các chương trình. Cải thiện tỷ lệ truy cập bộ đệm bằng cách sắp xếp lại các cấu trúc dữ liệu vi phạm để có địa phương tốt hơn; đóng gói các cấu trúc và các kiểu số để loại bỏ các byte bị lãng phí (và do đó bị lãng phí bộ đệm cache); tìm nạp dữ liệu bất cứ nơi nào có thể để giảm quầy hàng.
  • Tải-hit-store . Các giả định của trình biên dịch về bí danh con trỏ và các trường hợp dữ liệu được di chuyển giữa các bộ thanh ghi bị ngắt kết nối qua bộ nhớ, có thể gây ra một hành vi bệnh lý nhất định làm cho toàn bộ đường ống CPU bị xóa trên op tải. Tìm những nơi mà các float, vectơ và int đang được truyền cho nhau và loại bỏ chúng. Sử dụng __restricttự do để hứa với trình biên dịch về răng cưa.
  • Hoạt động vi mã . Hầu hết các bộ xử lý có một số hoạt động không thể được dẫn đường, nhưng thay vào đó chạy một chương trình con nhỏ được lưu trữ trong ROM. Các ví dụ trên PowerPC là nhân số nguyên, chia và thay đổi theo số lượng. Vấn đề là toàn bộ đường ống ngừng chết trong khi thao tác này đang thực thi. Cố gắng loại bỏ việc sử dụng các hoạt động này hoặc ít nhất là chia chúng thành các ops đường ống cấu thành của chúng để bạn có thể nhận được lợi ích của việc gửi siêu khối trên bất cứ điều gì phần còn lại của chương trình của bạn đang làm.
  • Chi nhánh sai . Những cái này quá trống rỗng đường ống. Tìm các trường hợp CPU đang dành nhiều thời gian để nạp đầy đường ống sau một nhánh và sử dụng gợi ý nhánh nếu có để có được nó để dự đoán chính xác thường xuyên hơn. Hoặc tốt hơn nữa, thay thế các nhánh bằng các chuyển động có điều kiện bất cứ khi nào có thể, đặc biệt là sau các hoạt động của dấu phẩy động vì đường ống của chúng thường sâu hơn và đọc các cờ điều kiện sau fcmp có thể gây ra tình trạng treo.
  • Các ops dấu phẩy động tuần tự . Tạo các SIMD này.

Và một điều nữa tôi muốn làm:

  • Đặt trình biên dịch của bạn thành danh sách lắp ráp đầu ra và xem những gì nó phát ra cho các hàm hotspot trong mã của bạn. Tất cả những tối ưu hóa thông minh rằng "một trình biên dịch tốt sẽ có thể tự động làm cho bạn"? Rất có thể trình biên dịch thực tế của bạn không làm chúng. Tôi đã thấy GCC phát ra mã WTF thực sự.

8
Tôi chủ yếu sử dụng Intel VTune và PIX. Không biết họ có thể thích ứng với C # hay không, nhưng thực sự một khi bạn đã có lớp trừu tượng JIT đó, hầu hết các tối ưu hóa này nằm ngoài tầm với của bạn, ngoại trừ việc cải thiện cục bộ bộ đệm và có thể tránh một số nhánh.
Crashworks

6
Mặc dù vậy, kiểm tra đầu ra sau JIT có thể giúp tìm ra liệu có bất kỳ cấu trúc nào không tối ưu hóa tốt qua giai đoạn JIT ... điều tra không bao giờ có thể bị tổn thương, ngay cả khi hóa ra ngõ cụt.
jerryjvl

5
Tôi nghĩ rằng nhiều người, bao gồm cả bản thân tôi, sẽ quan tâm đến "lắp ráp wtf" này được sản xuất bởi gcc. Nghe có vẻ như là một công việc rất thú vị :)
BlueRaja - Daniel Pflughoeft

1
Examples on the PowerPC ...<- Đó là, một số triển khai của PowerPC. PowerPC là một ISA, không phải CPU.
Billy ONeal

1
@BillyONeal Ngay cả trên phần cứng x86 hiện đại, imul có thể làm tắc nghẽn đường ống; xem "Hướng dẫn tham khảo tối ưu hóa kiến ​​trúc Intel® 64 và IA-32" §13.3.2.3: "Hướng dẫn nhân số nguyên phải mất vài chu kỳ để thực thi. Chúng được đặt theo đường ống sao cho một lệnh nhân số nguyên và một lệnh có độ trễ dài khác có thể tiến triển về phía trước trong Tuy nhiên, các lệnh thực hiện số nguyên sẽ chặn các lệnh số nguyên đơn chu kỳ khác phát hành do yêu cầu của thứ tự chương trình. " Đó là lý do tại sao thường tốt hơn để sử dụng kích thước mảng liên kết từ và lea.
Crashworks

78

Ném thêm phần cứng vào nó!


30
phần cứng nhiều hơn không phải lúc nào cũng là một lựa chọn khi bạn có phần mềm dự kiến ​​sẽ chạy trên phần cứng đã có trong lĩnh vực này.
Doug T.

76
Không phải là một câu trả lời rất hữu ích cho ai đó làm phần mềm tiêu dùng: khách hàng sẽ không muốn nghe bạn nói, "mua một máy tính nhanh hơn." Đặc biệt là nếu bạn đang viết phần mềm để nhắm mục tiêu một cái gì đó như bảng điều khiển trò chơi video.
Crashworks

19
@Crashworks, hoặc cho vấn đề đó, một hệ thống nhúng. Khi tính năng cuối cùng xuất hiện và lô bo mạch đầu tiên đã xuất hiện không phải là lúc để phát hiện ra rằng bạn nên sử dụng CPU nhanh hơn ở nơi đầu tiên ...
RBerteig

71
Tôi đã từng phải gỡ lỗi một chương trình bị rò rỉ bộ nhớ lớn - kích thước VM của nó tăng khoảng 1Mb mỗi giờ. Một đồng nghiệp đã nói đùa rằng tất cả những gì tôi cần làm là thêm bộ nhớ với tốc độ không đổi . :)
j_random_hacker

9
Thêm phần cứng: ah vâng, huyết mạch của nhà phát triển tầm thường. Tôi không biết bao nhiêu lần tôi đã nghe "thêm một máy khác và tăng gấp đôi công suất!"
Olof Forshell

58

Nhiều gợi ý hơn:

  • Tránh I / O : Bất kỳ I / O nào (đĩa, mạng, cổng, v.v.) sẽ luôn chậm hơn bất kỳ mã nào đang thực hiện tính toán, vì vậy hãy loại bỏ bất kỳ I / O nào mà bạn không thực sự cần.

  • Di chuyển I / O lên phía trước : Tải lên tất cả dữ liệu bạn sẽ cần để tính toán trước, để bạn không lặp lại I / O chờ trong lõi của thuật toán quan trọng (và có thể là kết quả được lặp lại đĩa tìm kiếm, khi tải tất cả dữ liệu trong một lần nhấn có thể tránh tìm kiếm).

  • Trì hoãn I / O : Không viết kết quả của bạn cho đến khi tính toán kết thúc, lưu trữ chúng trong cấu trúc dữ liệu và sau đó kết xuất nó trong một lần khi kết thúc công việc khó khăn.

  • I / O có luồng : Đối với những người đủ táo bạo, hãy kết hợp 'I / O lên trước' hoặc 'Trì hoãn I / O' với tính toán thực tế bằng cách di chuyển tải vào một luồng song song, để trong khi bạn tải nhiều dữ liệu hơn, bạn có thể làm việc trên một tính toán trên dữ liệu bạn đã có hoặc trong khi bạn tính lô dữ liệu tiếp theo, bạn có thể đồng thời viết ra kết quả từ đợt trước.


3
Lưu ý rằng "di chuyển IO sang luồng song song" phải được thực hiện dưới dạng IO không đồng bộ trên nhiều nền tảng (ví dụ: Windows NT).
Billy ONeal

2
I / O thực sự là một điểm quan trọng, bởi vì nó chậm và có độ trễ lớn, và bạn có thể nhanh hơn với lời khuyên này, nhưng về cơ bản vẫn còn thiếu sót: Các điểm là độ trễ (phải được ẩn) và trên cao của tòa nhà ( phải giảm bằng cách giảm số lượng cuộc gọi I / O). Lời khuyên tốt nhất là: sử dụng mmap()cho đầu vào, thực hiện các madvise()cuộc gọi phù hợp và sử dụng aio_write()để viết các khối đầu ra lớn (= một vài MiB).
cmaster - phục hồi monica

1
Tùy chọn cuối cùng này khá dễ thực hiện trong Java, đặc biệt. Nó đã tăng hiệu suất HUGE cho các ứng dụng tôi đã viết. Một điểm quan trọng khác (hơn cả việc di chuyển I / O lên phía trước) là làm cho nó trở thành I / O TUYỆT VỜI và khối lớn. Rất nhiều lượt đọc nhỏ đắt hơn nhiều so với 1 lần đọc lớn, do thời gian tìm kiếm đĩa.
BobMcGee

Tại một thời điểm, tôi đã gian lận trong việc tránh I / O, bằng cách tạm thời di chuyển tất cả các tệp vào đĩa RAM trước khi tính toán và di chuyển chúng trở lại sau đó. Điều này là bẩn, nhưng có thể hữu ích trong trường hợp bạn không kiểm soát logic thực hiện các cuộc gọi I / O.
MD

48

Vì nhiều vấn đề về hiệu suất liên quan đến các vấn đề cơ sở dữ liệu, tôi sẽ cung cấp cho bạn một số điều cụ thể cần xem xét khi điều chỉnh các truy vấn và quy trình được lưu trữ.

Tránh các con trỏ trong hầu hết các cơ sở dữ liệu. Tránh vòng lặp là tốt. Hầu hết thời gian, truy cập dữ liệu nên được thiết lập dựa trên, không ghi lại bằng cách xử lý hồ sơ. Điều này bao gồm không sử dụng lại một quy trình được lưu trữ khi bạn muốn chèn 1.000.000 bản ghi cùng một lúc.

Không bao giờ sử dụng select *, chỉ trả về các trường bạn thực sự cần. Điều này đặc biệt đúng nếu có bất kỳ phép nối nào vì các trường tham gia sẽ được lặp lại và do đó gây ra tải không cần thiết trên cả máy chủ và mạng.

Tránh sử dụng các truy vấn con tương quan. Sử dụng các phép nối (bao gồm các phép nối với các bảng dẫn xuất nếu có thể) (Tôi biết điều này đúng với Microsoft SQL Server, nhưng kiểm tra lời khuyên khi sử dụng một phụ trợ khác nhau).

Chỉ số, chỉ số, chỉ số. Và nhận được các số liệu thống kê cập nhật nếu áp dụng cho cơ sở dữ liệu của bạn.

Hãy truy vấn sargable . Có nghĩa là tránh những thứ khiến không thể sử dụng các chỉ mục, chẳng hạn như sử dụng ký tự đại diện trong ký tự đầu tiên của mệnh đề like hoặc hàm trong phép nối hoặc là phần bên trái của câu lệnh where.

Sử dụng các loại dữ liệu chính xác. Làm toán ngày trên một trường ngày nhanh hơn là phải cố gắng chuyển đổi một kiểu dữ liệu chuỗi thành kiểu dữ liệu ngày, sau đó thực hiện phép tính.

Không bao giờ đặt một vòng lặp của bất kỳ loại nào vào một kích hoạt!

Hầu hết các cơ sở dữ liệu có một cách để kiểm tra việc thực hiện truy vấn sẽ được thực hiện như thế nào. Trong Microsoft SQL Server, đây được gọi là kế hoạch thực hiện. Kiểm tra những người đầu tiên để xem khu vực có vấn đề nằm.

Xem xét tần suất truy vấn cũng như thời gian chạy khi xác định những gì cần được tối ưu hóa. Đôi khi, bạn có thể đạt được nhiều sự hoàn hảo hơn từ một tinh chỉnh nhỏ đến một truy vấn chạy hàng triệu lần một ngày so với việc bạn có thể xóa sạch thời gian khỏi một truy vấn dài chỉ chạy mỗi tháng một lần.

Sử dụng một số loại công cụ trình hồ sơ để tìm hiểu những gì thực sự được gửi đến và từ cơ sở dữ liệu. Tôi có thể nhớ một lần trong quá khứ, nơi chúng tôi không thể tìm ra lý do tại sao trang tải quá chậm khi thủ tục được lưu trữ nhanh và phát hiện ra thông tin rằng trang web đã yêu cầu truy vấn nhiều lần thay vì một lần.

Trình hồ sơ cũng sẽ giúp bạn tìm ra ai đang chặn ai. Một số truy vấn thực thi nhanh trong khi chạy một mình có thể trở nên rất chậm do khóa từ các truy vấn khác.


29

Yếu tố giới hạn quan trọng nhất hiện nay là bandwitdh bộ nhớ hạn chế . Đa lõi chỉ làm cho điều này trở nên tồi tệ hơn, vì băng thông được chia sẻ các lõi betwen. Ngoài ra, khu vực chip giới hạn dành cho việc thực hiện bộ nhớ cache cũng được phân chia giữa các lõi và luồng, làm cho vấn đề này trở nên tồi tệ hơn nữa. Cuối cùng, tín hiệu liên chip cần thiết để giữ các bộ đệm khác nhau kết hợp cũng tăng lên với số lượng lõi tăng lên. Điều này cũng thêm một hình phạt.

Đây là những hiệu ứng mà bạn cần quản lý. Đôi khi thông qua vi quản lý mã của bạn, nhưng đôi khi thông qua việc xem xét và tái cấu trúc cẩn thận.

Rất nhiều ý kiến ​​đã đề cập đến mã thân thiện bộ đệm. Có ít nhất hai hương vị riêng biệt này:

  • Tránh thời gian trễ lấy bộ nhớ.
  • Áp suất bus bộ nhớ thấp hơn (băng thông).

Vấn đề đầu tiên đặc biệt liên quan đến việc làm cho các mẫu truy cập dữ liệu của bạn thường xuyên hơn, cho phép trình tải trước phần cứng hoạt động hiệu quả. Tránh phân bổ bộ nhớ động làm lây lan các đối tượng dữ liệu của bạn xung quanh trong bộ nhớ. Sử dụng các thùng chứa tuyến tính thay vì danh sách liên kết, băm và cây.

Vấn đề thứ hai liên quan đến việc cải thiện việc tái sử dụng dữ liệu. Thay đổi thuật toán của bạn để làm việc trên các tập hợp con của dữ liệu phù hợp với bộ đệm có sẵn và sử dụng lại dữ liệu đó càng nhiều càng tốt trong khi nó vẫn còn trong bộ đệm.

Đóng gói dữ liệu chặt chẽ hơn và đảm bảo bạn sử dụng tất cả dữ liệu trong các dòng bộ đệm trong các vòng lặp nóng, sẽ giúp tránh các hiệu ứng khác này và cho phép lắp dữ liệu hữu ích hơn vào bộ đệm.


25
  • Bạn đang chạy trên phần cứng nào? Bạn có thể sử dụng tối ưu hóa nền tảng cụ thể (như vector hóa) không?
  • Bạn có thể có được một trình biên dịch tốt hơn? Ví dụ: chuyển từ GCC sang Intel?
  • Bạn có thể làm cho thuật toán của bạn chạy song song?
  • Bạn có thể giảm nhớ cache bằng cách sắp xếp lại dữ liệu?
  • Bạn có thể vô hiệu hóa khẳng định?
  • Micro-tối ưu hóa cho trình biên dịch và nền tảng của bạn. Theo kiểu, "tại một if / khác, đặt câu lệnh phổ biến nhất lên đầu tiên"

4
Nên là "chuyển từ GCC sang LLVM" :)
Zifre

4
Bạn có thể làm cho thuật toán của bạn chạy song song? - điều ngược lại cũng được áp dụng
justin

4
Đúng như vậy, việc giảm số lượng chủ đề có thể là một tối ưu hóa tốt không kém
Johan Kotlinski

re: tối ưu hóa vi mô: nếu bạn kiểm tra đầu ra asm của trình biên dịch, bạn có thể thường xuyên điều chỉnh nguồn để giữ bằng tay để tạo ra asm tốt hơn. Xem Tại sao mã C ++ này nhanh hơn tập hợp viết tay của tôi để kiểm tra phỏng đoán Collatz? để biết thêm về việc giúp đỡ hoặc đánh bại trình biên dịch trên x86 hiện đại.
Peter Cordes

17

Mặc dù tôi thích câu trả lời của Mike Dunlavey, nhưng thực tế đó là một câu trả lời tuyệt vời thực sự với ví dụ hỗ trợ, tôi nghĩ nó có thể được diễn đạt rất đơn giản như vậy:

Tìm hiểu những gì mất thời gian lớn nhất đầu tiên, và hiểu lý do tại sao.

Đây là quá trình xác định các con lợn thời gian giúp bạn hiểu nơi bạn phải tinh chỉnh thuật toán của mình. Đây là câu trả lời bất khả tri về ngôn ngữ bao gồm tất cả những gì tôi có thể tìm thấy cho một vấn đề đã được tối ưu hóa hoàn toàn. Cũng giả sử bạn muốn độc lập kiến ​​trúc trong cuộc tìm kiếm tốc độ của bạn.

Vì vậy, trong khi thuật toán có thể được tối ưu hóa, việc thực hiện nó có thể không được. Việc xác định cho phép bạn biết phần nào là: thuật toán hoặc triển khai. Vì vậy, bất cứ lúc nào nhiều thời gian nhất là ứng cử viên chính của bạn để xem xét. Nhưng vì bạn nói rằng bạn muốn loại bỏ vài% cuối cùng, nên bạn cũng có thể muốn kiểm tra các phần nhỏ hơn, những phần mà bạn chưa kiểm tra kỹ lúc đầu.

Cuối cùng, một chút thử nghiệm và lỗi với các số liệu hiệu suất về các cách khác nhau để thực hiện cùng một giải pháp hoặc các thuật toán có khả năng khác nhau, có thể mang lại những hiểu biết giúp xác định lãng phí thời gian và tiết kiệm thời gian.

HPH, asoudmove.


16

Có lẽ bạn nên xem xét "phối cảnh Google", nghĩa là xác định cách ứng dụng của bạn có thể trở nên song song và đồng thời, điều này chắc chắn cũng có nghĩa là đôi khi sẽ xem xét phân phối ứng dụng của bạn trên các máy và mạng khác nhau, để nó có thể mở rộng gần như tuyến tính với phần cứng mà bạn ném vào nó.

Mặt khác, dân Google cũng được biết đến với việc ném rất nhiều nhân lực và tài nguyên để giải quyết một số vấn đề trong các dự án, công cụ và cơ sở hạ tầng họ đang sử dụng, ví dụ như tối ưu hóa toàn bộ chương trình cho gcc bằng cách có một đội ngũ kỹ sư chuyên dụng hack nội bộ gcc để chuẩn bị cho các tình huống sử dụng điển hình của Google.

Tương tự, cấu hình một ứng dụng không còn có nghĩa là chỉ đơn giản là cấu hình mã chương trình, mà còn tất cả các hệ thống và cơ sở hạ tầng xung quanh của nó (nghĩ mạng, chuyển mạch, máy chủ, mảng RAID) để xác định dự phòng và tiềm năng tối ưu hóa theo quan điểm của hệ thống.


15
  • Thói quen nội tuyến (loại bỏ cuộc gọi / trả lại và đẩy tham số)
  • Hãy thử loại bỏ các kiểm tra / chuyển đổi với tra cứu bảng (nếu chúng nhanh hơn)
  • Bỏ các vòng lặp (thiết bị của Duff) đến điểm chúng vừa với bộ đệm CPU
  • Bản địa hóa truy cập bộ nhớ để không làm hỏng bộ nhớ cache của bạn
  • Bản địa hóa các tính toán liên quan nếu trình tối ưu hóa chưa thực hiện điều đó
  • Loại bỏ các bất biến vòng lặp nếu trình tối ưu hóa chưa làm điều đó

2
Thiết bị của IIRC Duff rất hiếm khi nhanh hơn. Chỉ khi op rất ngắn (như một biểu thức toán học nhỏ)
BCS

12
  • Khi bạn đạt đến điểm bạn đang sử dụng các thuật toán hiệu quả, đó là câu hỏi về những gì bạn cần thêm tốc độ hoặc bộ nhớ . Sử dụng bộ nhớ đệm để "thanh toán" trong bộ nhớ để có thêm tốc độ hoặc sử dụng các phép tính để giảm dung lượng bộ nhớ.
  • Nếu có thể (và hiệu quả hơn về chi phí), hãy ném phần cứng vào vấn đề - CPU nhanh hơn, nhiều bộ nhớ hơn hoặc HD có thể giải quyết vấn đề nhanh hơn sau đó thử mã hóa nó.
  • Sử dụng song song nếu có thể - chạy một phần mã trên nhiều luồng.
  • Sử dụng công cụ phù hợp cho công việc . một số ngôn ngữ lập trình tạo mã hiệu quả hơn, sử dụng mã được quản lý (ví dụ Java / .NET) tăng tốc độ phát triển nhưng ngôn ngữ lập trình gốc tạo mã chạy nhanh hơn.
  • Micro tối ưu hóa . Chỉ có thể áp dụng, bạn có thể sử dụng lắp ráp được tối ưu hóa để tăng tốc các đoạn mã nhỏ, sử dụng tối ưu hóa SSE / vector ở đúng nơi có thể giúp tăng hiệu suất rất nhiều.

12

Phân chia và chinh phục

Nếu tập dữ liệu đang được xử lý quá lớn, hãy lặp qua các đoạn của nó. Nếu bạn đã thực hiện đúng mã của mình, việc triển khai sẽ dễ dàng. Nếu bạn có một chương trình nguyên khối, bây giờ bạn biết rõ hơn.


9
+1 cho âm thanh "smack" flyswatter tôi nghe được khi đọc câu cuối cùng.
Bryan Boettcher

11

Trước hết, như đã đề cập trong một số câu trả lời trước, hãy tìm hiểu những gì cắn hiệu suất của bạn - đó là bộ nhớ hoặc bộ xử lý hoặc mạng hoặc cơ sở dữ liệu hoặc một cái gì đó khác. Tùy vào đó ...

  • ... Nếu đó là bộ nhớ - hãy tìm một trong những cuốn sách được viết từ lâu bởi Knuth, một trong bộ "Nghệ thuật lập trình máy tính". Rất có thể đó là về cách sắp xếp và tìm kiếm - nếu trí nhớ của tôi sai thì bạn sẽ phải tìm hiểu xem anh ấy nói về cách xử lý lưu trữ dữ liệu băng chậm. Tinh thần biến đổi vắt của mình lên đến vài phần trăm cuối cùng phải không? Nếu thực sự ít có khả năng bạn sẽ thắng. bộ nhớ / băng cặp thành cặp bộ nhớ cache / bộ nhớ chính (hoặc theo cặp bộ đệm L1 / L2) tương ứng. Nghiên cứu tất cả các mẹo mà anh ấy mô tả - nếu bạn không tìm thấy thứ gì đó giải quyết vấn đề của mình, thì hãy thuê nhà khoa học máy tính chuyên nghiệp để thực hiện một nghiên cứu chuyên nghiệp. Nếu vấn đề về bộ nhớ của bạn là do FFT (bộ nhớ cache bị mất ở các chỉ số đảo ngược bit khi thực hiện bướm radix-2) thì đừng thuê một nhà khoa học - thay vào đó, hãy tối ưu hóa thủ công từng bước một cho đến khi bạn '

  • ... nếu đó là bộ xử lý - chuyển sang ngôn ngữ lắp ráp. Nghiên cứu đặc tả bộ xử lý - những gì cần đánh dấu , VLIW, SIMD. Các cuộc gọi chức năng rất có thể thay thế người ăn tick. Tìm hiểu biến đổi vòng lặp - đường ống, unroll. Nhiều và các bộ phận có thể được thay thế / nội suy với các thay đổi bit (nhân với số nguyên nhỏ có thể được thay thế bằng các bổ sung). Hãy thử các thủ thuật với dữ liệu ngắn hơn - nếu bạn may mắn, một hướng dẫn với 64 bit có thể thay thế bằng hai trên 32 hoặc thậm chí 4 trên 16 hoặc 8 trên 8 bit. Cũng cố gắng lâu hơndữ liệu - ví dụ: tính toán float của bạn có thể bật chậm hơn so với tính toán kép tại bộ xử lý cụ thể. Nếu bạn có công cụ lượng giác, hãy chiến đấu với các bảng được tính toán trước; cũng nên nhớ rằng sin của giá trị nhỏ có thể được thay thế bằng giá trị đó nếu mất độ chính xác nằm trong giới hạn cho phép.

  • ... Nếu đó là mạng - hãy nghĩ đến việc nén dữ liệu mà bạn vượt qua. Thay thế chuyển XML bằng nhị phân. Đề cương nghiên cứu. Hãy thử UDP thay vì TCP nếu bạn có thể xử lý mất dữ liệu bằng cách nào đó.

  • ... Nếu đó là cơ sở dữ liệu, tốt, hãy đến bất kỳ diễn đàn cơ sở dữ liệu nào và xin lời khuyên. Lưới dữ liệu trong bộ nhớ, tối ưu hóa kế hoạch truy vấn, v.v.

HTH :)


9

Bộ nhớ đệm! Một cách rẻ tiền (trong nỗ lực lập trình viên) để làm cho hầu hết mọi thứ nhanh hơn là thêm một lớp trừu tượng bộ đệm vào bất kỳ khu vực di chuyển dữ liệu nào trong chương trình của bạn. Có thể là I / O hoặc chỉ cần truyền / tạo các đối tượng hoặc cấu trúc. Thông thường, thật dễ dàng để thêm bộ nhớ cache vào các lớp xuất xưởng và trình đọc / ghi.

Đôi khi bộ đệm sẽ không mang lại cho bạn nhiều, nhưng đó là một phương pháp dễ dàng để chỉ cần thêm bộ đệm vào và sau đó vô hiệu hóa nó ở nơi không giúp ích. Tôi thường thấy điều này để đạt được hiệu suất lớn mà không cần phải phân tích mã.


8

Tôi nghĩ điều này đã được nói theo một cách khác. Nhưng khi bạn đang xử lý một thuật toán chuyên sâu về bộ xử lý, bạn nên đơn giản hóa mọi thứ bên trong vòng lặp bên trong nhất với chi phí của mọi thứ khác.

Điều đó có vẻ rõ ràng đối với một số người, nhưng đó là điều tôi cố gắng tập trung vào bất kể ngôn ngữ tôi đang làm việc với. Ví dụ, nếu bạn đang xử lý các vòng lặp lồng nhau và bạn tìm thấy cơ hội để giảm một số mã xuống, thì trong một số trường hợp, bạn có thể tăng tốc mã của mình một cách mạnh mẽ. Một ví dụ khác, có những điều nhỏ để suy nghĩ như làm việc với các số nguyên thay vì các biến dấu phẩy động bất cứ khi nào bạn có thể, và sử dụng phép nhân thay vì chia bất cứ khi nào bạn có thể. Một lần nữa, đây là những điều nên được xem xét cho vòng lặp bên trong nhất của bạn.

Đôi khi bạn có thể tìm thấy lợi ích của việc thực hiện các phép toán của mình trên một số nguyên bên trong vòng lặp bên trong, và sau đó thu nhỏ nó xuống một biến số dấu phẩy động mà bạn có thể làm việc sau đó. Đó là một ví dụ về việc hy sinh tốc độ trong một phần để cải thiện tốc độ ở phần khác, nhưng trong một số trường hợp, việc thanh toán có thể rất xứng đáng.


8

Tôi đã dành thời gian để tối ưu hóa các hệ thống kinh doanh máy khách / máy chủ hoạt động trên các mạng băng thông thấp và độ trễ dài (ví dụ: vệ tinh, từ xa, ngoài khơi) và có thể đạt được một số cải tiến hiệu suất đáng kinh ngạc với quy trình khá lặp lại.

  • Đo lường : Bắt đầu bằng cách hiểu cấu trúc và cấu trúc liên kết cơ bản của mạng. Nói chuyện với những người có liên quan trong doanh nghiệp và sử dụng các công cụ cơ bản như ping và traceroute để thiết lập (tối thiểu) độ trễ mạng từ mỗi vị trí máy khách, trong các giai đoạn hoạt động thông thường. Tiếp theo, thực hiện các phép đo thời gian chính xác của các chức năng người dùng cuối cụ thể hiển thị các triệu chứng có vấn đề. Ghi lại tất cả các phép đo này, cùng với vị trí, ngày và thời gian của chúng. Xem xét xây dựng chức năng "kiểm tra hiệu suất mạng" của người dùng cuối vào ứng dụng khách của bạn, cho phép người dùng quyền lực của bạn tham gia vào quá trình cải tiến; Trao quyền cho họ như thế này có thể có tác động tâm lý rất lớn khi bạn giao dịch với người dùng bị thất vọng bởi một hệ thống hoạt động kém.

  • Phân tích : Sử dụng bất kỳ và tất cả các phương pháp ghi nhật ký có sẵn để thiết lập chính xác dữ liệu nào được truyền và nhận trong quá trình thực hiện các hoạt động bị ảnh hưởng. Lý tưởng nhất, ứng dụng của bạn có thể thu thập dữ liệu được truyền và nhận bởi cả máy khách và máy chủ. Nếu chúng bao gồm cả dấu thời gian là tốt, thậm chí tốt hơn. Nếu đăng nhập đủ không có sẵn (ví dụ: hệ thống đóng hoặc không thể triển khai các sửa đổi trong môi trường sản xuất), hãy sử dụng trình thám thính mạng và đảm bảo bạn thực sự hiểu những gì đang diễn ra ở cấp độ mạng.

  • Bộ nhớ cache : Tìm kiếm các trường hợp dữ liệu thay đổi tĩnh hoặc không thường xuyên được truyền đi lặp lại và xem xét một chiến lược bộ đệm phù hợp. Các ví dụ điển hình bao gồm các giá trị "danh sách chọn" hoặc các "thực thể tham chiếu" khác, có thể lớn đến mức đáng ngạc nhiên trong một số ứng dụng kinh doanh. Trong nhiều trường hợp, người dùng có thể chấp nhận rằng họ phải khởi động lại hoặc làm mới ứng dụng để cập nhật dữ liệu được cập nhật không thường xuyên, đặc biệt là nếu nó có thể cạo thời gian đáng kể khỏi màn hình của các thành phần giao diện người dùng thường sử dụng. Hãy chắc chắn rằng bạn hiểu hành vi thực sự của các yếu tố bộ đệm đã được triển khai - nhiều phương thức lưu trữ phổ biến (ví dụ HTTP ETag) vẫn yêu cầu một chuyến đi khứ hồi mạng để đảm bảo tính nhất quán và khi độ trễ mạng đắt, bạn có thể tránh hoàn toàn một cách tiếp cận bộ nhớ đệm khác nhau.

  • Song song : Tìm kiếm các giao dịch tuần tự không cần phải được ban hành một cách nghiêm ngặt theo tuần tự và làm lại hệ thống để phát hành chúng song song. Tôi đã xử lý một trường hợp trong đó yêu cầu kết thúc có độ trễ mạng cố định là ~ 2 giây, đó không phải là vấn đề đối với một giao dịch, nhưng khi 6 chuyến đi vòng 2 tuần tự được yêu cầu trước khi người dùng lấy lại quyền kiểm soát ứng dụng khách , nó trở thành một nguồn thất vọng lớn. Khám phá ra rằng các giao dịch này trên thực tế độc lập cho phép chúng được thực hiện song song, giảm độ trễ của người dùng cuối xuống rất gần với chi phí của một chuyến đi khứ hồi.

  • Kết hợp : Trường hợp các yêu cầu tuần tự phải được thực hiện tuần tự, hãy tìm cơ hội để kết hợp chúng thành một yêu cầu toàn diện hơn. Các ví dụ điển hình bao gồm việc tạo ra các thực thể mới, theo sau là các yêu cầu liên quan các thực thể đó với các thực thể hiện có khác.

  • Nén : Tìm kiếm cơ hội để tận dụng việc nén tải trọng, bằng cách thay thế một dạng văn bản bằng dạng nhị phân hoặc sử dụng công nghệ nén thực tế. Nhiều ngăn xếp công nghệ hiện đại (tức là trong vòng một thập kỷ) hỗ trợ điều này gần như minh bạch, vì vậy hãy chắc chắn rằng nó được cấu hình. Tôi thường bị bất ngờ bởi tác động đáng kể của việc nén khi có vẻ như rõ ràng rằng vấn đề là độ trễ cơ bản thay vì băng thông, phát hiện ra sau khi thực tế là nó cho phép giao dịch nằm gọn trong một gói hoặc tránh bị mất gói và do đó có sự mất giá tác động đến hiệu suất.

  • Lặp lại : Quay trở lại từ đầu và đo lại các hoạt động của bạn (tại cùng địa điểm và thời gian) với các cải tiến tại chỗ, ghi lại và báo cáo kết quả của bạn. Như với tất cả tối ưu hóa, một số vấn đề có thể đã được giải quyết phơi bày những vấn đề khác hiện đang thống trị.

Trong các bước trên, tôi tập trung vào quá trình tối ưu hóa liên quan đến ứng dụng, nhưng tất nhiên bạn phải đảm bảo rằng mạng bên dưới được cấu hình theo cách hiệu quả nhất để hỗ trợ ứng dụng của bạn. Thu hút các chuyên gia mạng trong doanh nghiệp và xác định xem họ có thể áp dụng các cải tiến về dung lượng, QoS, nén mạng hoặc các kỹ thuật khác để giải quyết vấn đề hay không. Thông thường, họ sẽ không hiểu nhu cầu của ứng dụng của bạn, vì vậy điều quan trọng là bạn phải trang bị (sau bước Phân tích) để thảo luận với họ và cũng để đưa ra trường hợp kinh doanh cho bất kỳ chi phí nào bạn sẽ yêu cầu họ phải chịu . Tôi đã gặp phải trường hợp cấu hình mạng bị lỗi khiến dữ liệu ứng dụng được truyền qua liên kết vệ tinh chậm chứ không phải liên kết đường bộ, đơn giản là vì nó đang sử dụng một cổng TCP không được các chuyên gia mạng "nổi tiếng"; rõ ràng việc khắc phục một vấn đề như thế này có thể có tác động lớn đến hiệu suất, không có thay đổi cấu hình hoặc mã phần mềm nào cả.


7

Rất khó để đưa ra một câu trả lời chung chung cho câu hỏi này. Nó thực sự phụ thuộc vào miền vấn đề của bạn và thực hiện kỹ thuật. Một kỹ thuật chung khá trung lập về ngôn ngữ: Xác định các điểm nóng mã không thể loại bỏ và tối ưu hóa mã trình biên dịch mã.


7

Vài% cuối cùng là một thứ rất phụ thuộc vào CPU và ứng dụng ....

  • Kiến trúc bộ đệm khác nhau, một số chip có RAM trên chip bạn có thể ánh xạ trực tiếp, đôi khi (ARM) có đơn vị vectơ, SH4 là một ma trận mã hóa hữu ích. Có GPU không - có lẽ một shader là con đường để đi. Các TMS320 rất nhạy cảm với các nhánh trong các vòng lặp (vì vậy hãy tách các vòng lặp và di chuyển các điều kiện bên ngoài nếu có thể).

Danh sách này tiếp tục .... Nhưng những thứ này thực sự là giải pháp cuối cùng ...

Xây dựng cho x86 và chạy Valgrind / Cachegrind dựa trên mã để lập hồ sơ hiệu suất phù hợp. Hoặc CCStudio của Texas Cụ có một hồ sơ ngọt ngào. Sau đó, bạn sẽ thực sự biết nơi tập trung ...


7

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

Đối với bất kỳ dự án ngoại tuyến nào, trong khi có phần mềm tốt nhất và phần cứng tốt nhất, nếu thông lượng của bạn yếu, thì dòng mỏng đó sẽ siết chặt dữ liệu và khiến bạn chậm trễ, mặc dù tính bằng mili giây ... nhưng nếu bạn đang nói về những giọt cuối cùng , đó là một số giọt tăng, 24/7 cho bất kỳ gói nào được gửi hoặc nhận.


7

Không gần như sâu hoặc phức tạp như các câu trả lời trước, nhưng ở đây đi: (đây là những cấp độ mới bắt đầu / trung cấp)

  • hiển nhiên: khô
  • chạy các vòng lặp ngược để bạn luôn so sánh với 0 thay vì một biến
  • sử dụng toán tử bitwise bất cứ khi nào bạn có thể
  • phá mã lặp đi lặp lại thành các mô-đun / chức năng
  • đối tượng bộ nhớ cache
  • biến cục bộ có lợi thế hiệu suất nhẹ
  • hạn chế thao tác chuỗi càng nhiều càng tốt

4
Về vòng lặp ngược: có, việc so sánh kết thúc vòng lặp sẽ nhanh hơn. Thông thường, bạn sử dụng biến để lập chỉ mục vào bộ nhớ và việc truy cập nó bị đảo ngược có thể phản tác dụng do lỗi bộ nhớ cache thường xuyên (không tìm nạp trước).
Andreas Reiff

1
AFAIK, trong hầu hết các trường hợp, bất kỳ trình tối ưu hóa hợp lý nào cũng sẽ hoạt động tốt với các vòng lặp, mà không cần lập trình viên phải chạy ngược lại. Trình tối ưu hóa sẽ tự đảo ngược vòng lặp hoặc có một cách khác cũng tốt như vậy. Tôi đã lưu ý đầu ra ASM giống hệt nhau cho các vòng lặp (tương đối đơn giản) được viết cả tăng dần so với tối đa và giảm dần so với 0. Chắc chắn, những ngày Z80 của tôi có thói quen viết các vòng lặp ngược, nhưng tôi nghi ngờ việc nhắc đến nó cho người mới thường là một tối ưu hóa cá trích / sớm, khi mã có thể đọc và học các thực hành quan trọng hơn nên được ưu tiên.
gạch dưới

Ngược lại, chạy một vòng lặp ngược sẽ chậm hơn trong các ngôn ngữ cấp thấp hơn bởi vì trong một cuộc chiến giữa so sánh với 0 cộng với phép trừ bổ sung so với so sánh số nguyên đơn, so sánh số nguyên đơn sẽ nhanh hơn. Thay vì giảm dần, bạn có thể có một con trỏ đến địa chỉ bắt đầu trong bộ nhớ và một con trỏ đến địa chỉ kết thúc trong bộ nhớ. Sau đó, tăng con trỏ bắt đầu cho đến khi nó bằng con trỏ kết thúc. Điều này sẽ loại bỏ hoạt động bù bộ nhớ bổ sung trong mã lắp ráp, do đó chứng tỏ hiệu năng cao hơn nhiều.
Jack Giffin

5

Không thể nói. Nó phụ thuộc vào những gì mã trông như thế nào. Nếu chúng ta có thể giả định rằng mã đã tồn tại, thì chúng ta chỉ cần nhìn vào nó và tìm ra từ đó, làm thế nào để tối ưu hóa nó.

Địa phương bộ đệm tốt hơn, không kiểm soát vòng lặp, Cố gắng loại bỏ các chuỗi phụ thuộc dài, để có được sự song song ở mức hướng dẫn tốt hơn. Thích di chuyển có điều kiện hơn các chi nhánh khi có thể. Khai thác hướng dẫn SIMD khi có thể.

Hiểu mã của bạn đang làm gì và hiểu phần cứng mà nó đang chạy. Sau đó, nó trở nên khá đơn giản để xác định những gì bạn cần làm để cải thiện hiệu suất mã của bạn. Đó thực sự là lời khuyên thực sự chung duy nhất tôi có thể nghĩ ra.

Vâng, đó và "Hiển thị mã trên SO và yêu cầu tư vấn tối ưu hóa cho đoạn mã cụ thể đó".


5

Nếu phần cứng tốt hơn là một lựa chọn thì chắc chắn đi cho điều đó. Nếu không thì

  • Kiểm tra bạn đang sử dụng các tùy chọn trình biên dịch và liên kết tốt nhất.
  • Nếu thường xuyên điểm nóng trong thư viện khác với người gọi thường xuyên, hãy xem xét di chuyển hoặc nhân bản nó sang mô-đun người gọi. Loại bỏ một số chi phí cuộc gọi và có thể cải thiện các lần truy cập bộ đệm (cf cách AIX liên kết strcpy () tĩnh vào các đối tượng chia sẻ được liên kết riêng). Điều này tất nhiên có thể làm giảm lượt truy cập bộ đệm, đó là lý do tại sao một biện pháp.
  • Xem nếu có bất kỳ khả năng sử dụng một phiên bản chuyên biệt của thói quen hotspot. Nhược điểm là nhiều hơn một phiên bản để duy trì.
  • Nhìn vào trình biên dịch. Nếu bạn nghĩ rằng nó có thể tốt hơn, hãy xem xét tại sao trình biên dịch không tìm ra điều này và làm thế nào bạn có thể giúp trình biên dịch.
  • Hãy xem xét: bạn có thực sự sử dụng thuật toán tốt nhất? Đây có phải là thuật toán tốt nhất cho kích thước đầu vào của bạn?

Tôi sẽ thêm vào mệnh giá đầu tiên của bạn: đừng quên tắt tất cả thông tin gỡ lỗi trong các tùy chọn trình biên dịch của bạn .
varnie

5

Cách google là một tùy chọn "Cache nó .. Bất cứ khi nào có thể không chạm vào đĩa"


5

Dưới đây là một số kỹ thuật tối ưu hóa nhanh và bẩn tôi sử dụng. Tôi coi đây là tối ưu hóa 'vượt qua đầu tiên'.

Tìm hiểu thời gian ở đâu Tìm hiểu chính xác những gì đang dành thời gian. Có phải tập tin IO không? Có phải là thời gian CPU? Có phải là mạng không? Có phải là cơ sở dữ liệu? Sẽ vô ích khi tối ưu hóa cho IO nếu đó không phải là nút cổ chai.

Biết môi trường của bạn Biết nơi tối ưu hóa thường phụ thuộc vào môi trường phát triển. Ví dụ, trong VB6, truyền bằng tham chiếu chậm hơn so với truyền theo giá trị, nhưng trong C và C ++, bằng tham chiếu nhanh hơn rất nhiều. Trong C, thật hợp lý khi thử một cái gì đó và làm một cái gì đó khác nếu mã trả về cho thấy lỗi, trong khi ở Dot Net, việc bắt ngoại lệ chậm hơn nhiều so với kiểm tra điều kiện hợp lệ trước khi thử.

Chỉ mục Xây dựng chỉ mục trên các trường cơ sở dữ liệu thường xuyên truy vấn. Bạn hầu như luôn có thể trao đổi không gian cho tốc độ.

Tránh tra cứu Bên trong vòng lặp để được tối ưu hóa, tôi tránh phải thực hiện bất kỳ tra cứu nào. Tìm phần bù và / hoặc chỉ mục bên ngoài vòng lặp và sử dụng lại dữ liệu bên trong.

Giảm thiểu IO cố gắng thiết kế theo cách giảm số lần bạn phải đọc hoặc viết đặc biệt qua kết nối được nối mạng

Giảm trừu tượng Càng nhiều lớp trừu tượng mà mã phải hoạt động, nó càng chậm. Trong vòng lặp quan trọng, giảm trừu tượng (ví dụ: tiết lộ các phương thức cấp thấp hơn để tránh mã bổ sung)

Spawn Themes cho các dự án có giao diện người dùng, tạo ra một luồng mới để tạo các tác vụ chậm hơn làm cho ứng dụng cảm thấy phản ứng nhanh hơn, mặc dù không.

Tiền xử lý Bạn thường có thể trao đổi không gian cho tốc độ. Nếu có tính toán hoặc các hoạt động mạnh khác, hãy xem liệu bạn có thể tính toán trước một số thông tin trước khi bạn vào vòng quan trọng không.


5

Nếu bạn có rất nhiều phép toán dấu phẩy động song song rất cao - đặc biệt là độ chính xác đơn - hãy thử giảm tải nó cho bộ xử lý đồ họa (nếu có) bằng cách sử dụng OpenCL hoặc (cho chip NVidia) CUDA. GPU có sức mạnh tính toán dấu phẩy động lớn trong các shader của chúng, lớn hơn nhiều so với CPU.


5

Thêm câu trả lời này vì tôi không thấy nó bao gồm trong tất cả những người khác.

Giảm thiểu chuyển đổi ngầm giữa các loại và dấu hiệu:

Điều này ít nhất áp dụng cho C / C ++, ngay cả khi bạn nghĩ rằng bạn không có chuyển đổi - đôi khi cũng tốt để kiểm tra thêm cảnh báo trình biên dịch xung quanh các chức năng yêu cầu hiệu suất, đặc biệt là coi chừng chuyển đổi trong các vòng lặp.

GCC spesific: Bạn có thể kiểm tra điều này bằng cách thêm một số pragma dài dòng xung quanh mã của bạn,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

Tôi đã thấy các trường hợp bạn có thể tăng tốc vài phần trăm bằng cách giảm các chuyển đổi được đưa ra bởi các cảnh báo như thế này.

Trong một số trường hợp, tôi có một tiêu đề với các cảnh báo nghiêm ngặt mà tôi luôn đưa vào để ngăn chặn các chuyển đổi ngẫu nhiên, tuy nhiên đây là một sự đánh đổi vì cuối cùng bạn có thể thêm rất nhiều diễn biến vào các chuyển đổi có chủ ý có thể làm cho mã trở nên lộn xộn hơn đạt được.


Đây là lý do tại sao tôi thích điều đó trong OCaml, việc truyền giữa các kiểu số phải là xplicit.
Gaius

@Gaius điểm công bằng - nhưng trong nhiều trường hợp, việc thay đổi ngôn ngữ không phải là một lựa chọn thực tế. Vì C / C ++ được sử dụng rộng rãi nên rất hữu ích để có thể làm cho chúng nghiêm ngặt hơn, ngay cả khi trình biên dịch cụ thể.
ideaman42

4

Đôi khi thay đổi cách bố trí dữ liệu của bạn có thể giúp đỡ. Trong C, bạn có thể chuyển từ một mảng hoặc cấu trúc sang cấu trúc của mảng hoặc ngược lại.


4

Tinh chỉnh hệ điều hành và khung.

Nghe có vẻ quá mức nhưng hãy nghĩ về nó như thế này: Hệ điều hành và Khung được thiết kế để làm nhiều việc. Ứng dụng của bạn chỉ làm những việc rất cụ thể. Nếu bạn có thể khiến HĐH thực hiện chính xác những gì ứng dụng của bạn cần và để ứng dụng của bạn hiểu cách khung (php, .net, java) hoạt động, bạn có thể tận dụng tốt hơn phần cứng của mình.

Ví dụ, Facebook đã thay đổi một số thứ cấp nhân trong Linux, thay đổi cách hoạt động của memcached (ví dụ: họ đã viết proxy memcached và sử dụng udp thay vì tcp ).

Một ví dụ khác cho điều này là Window2008. Win2K8 có một phiên bản, bạn chỉ có thể cài đặt hệ điều hành cơ bản cần thiết để chạy X applicaions (ví dụ: Ứng dụng web, Ứng dụng máy chủ). Điều này giúp giảm phần lớn chi phí mà HĐH có trong các quy trình đang chạy và mang lại cho bạn hiệu suất tốt hơn.

Tất nhiên, bạn nên luôn luôn sử dụng nhiều phần cứng hơn như bước đầu tiên ...


2
Đó sẽ là một cách tiếp cận hợp lệ sau khi tất cả các cách tiếp cận khác không thành công hoặc nếu một tính năng cụ thể của HĐH hoặc Khung chịu trách nhiệm cho hiệu suất giảm rõ rệt, nhưng mức độ chuyên môn và kiểm soát cần thiết để loại bỏ điều đó có thể không khả dụng cho mọi dự án.
Andrew Neely
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.