Những kỹ thuật đơn giản nào bạn sử dụng để cải thiện hiệu suất?


21

Tôi đang nói về cách chúng ta viết các thói quen đơn giản để cải thiện hiệu suất mà không làm cho mã của bạn khó đọc hơn ... ví dụ, đây là điển hình cho chúng ta đã học:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

Nhưng, tôi thường làm điều này khi foreachkhông áp dụng:

for(int i = 0, j = collection.length(); i < j; i++ ){
   // stuff here
}

Tôi nghĩ rằng đây là một cách tiếp cận tốt hơn vì nó sẽ chỉ gọi lengthphương thức một lần thôi ... bạn gái tôi nói rằng đó là điều khó hiểu. Có bất kỳ thủ thuật đơn giản nào khác mà bạn sử dụng cho sự phát triển của chính mình không?


34
+1 chỉ để có bạn gái, người sẽ cho bạn biết khi nào mã của bạn không rõ ràng.
Kristo

76
Bạn chỉ đăng bài này để cho chúng tôi biết bạn có bạn gái.
Josh K

11
@Christian: Đừng quên rằng có những tối ưu hóa trình biên dịch có thể làm điều này cho bạn để bạn chỉ có thể ảnh hưởng đến khả năng đọc và không ảnh hưởng đến hiệu suất; tối ưu hóa sớm là gốc rễ của mọi tội lỗi ... Hãy cố gắng tránh nhiều hơn một tuyên bố hoặc bài tập trên cùng một dòng, đừng bắt mọi người đọc nó hai lần ... Bạn nên sử dụng cách thông thường (ví dụ đầu tiên của bạn) hoặc đặt khai báo thứ hai bên ngoài vòng lặp for (mặc dù điều đó cũng làm giảm khả năng đọc vì bạn sẽ cần đọc lại để xem ý nghĩa của j).
Tamara Wijsman

5
@TomWij: Trích dẫn chính xác (và đầy đủ): "Chúng ta nên quên đi những hiệu quả nhỏ, nói về 97% thời gian: tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Tuy nhiên, chúng ta không nên bỏ qua cơ hội của mình trong 3% quan trọng đó. "
Robert Harvey

3
@tomwij: Nếu bạn đang chi tiêu ba phần trăm, thì theo định nghĩa, bạn nên thực hiện theo mã quan trọng về thời gian và không lãng phí thời gian của mình cho 97% còn lại.
Robert Harvey

Câu trả lời:


28

chèn bài giảng sớm-thảo luận-là-gốc-của-tất cả-ác

Điều đó nói rằng, đây là một số thói quen tôi đã mắc phải để tránh hiệu quả không cần thiết, và trong một số trường hợp, làm cho mã của tôi đơn giản hơn và chính xác hơn.

Đây không phải là một cuộc thảo luận về các nguyên tắc chung, nhưng về một số điều cần lưu ý để tránh đưa sự thiếu hiệu quả không cần thiết vào mã.

Biết ông lớn của bạn

Điều này có lẽ nên được hợp nhất vào các cuộc thảo luận dài ở trên. Có một ý nghĩa khá phổ biến là một vòng lặp bên trong một vòng lặp, trong đó vòng lặp bên trong lặp lại một phép tính, sẽ chậm hơn. Ví dụ:

for (i = 0; i < strlen(str); i++) {
    ...
}

Điều này sẽ mất một lượng thời gian khủng khiếp nếu chuỗi thực sự dài, bởi vì độ dài đang được tính toán lại trên mỗi lần lặp của vòng lặp. Lưu ý rằng GCC thực sự tối ưu hóa trường hợp này vì strlen()được đánh dấu là một hàm thuần túy.

Khi sắp xếp một triệu số nguyên 32 bit, sắp xếp bong bóng sẽ là cách sai . Nói chung, việc sắp xếp có thể được thực hiện trong thời gian O (n * log n) (hoặc tốt hơn, trong trường hợp sắp xếp cơ số), vì vậy trừ khi bạn biết dữ liệu của mình sẽ nhỏ, hãy tìm một thuật toán ít nhất là O (n * đăng nhập n).

Tương tự như vậy, khi làm việc với cơ sở dữ liệu, hãy chú ý đến các chỉ mục. Nếu bạn SELECT * FROM people WHERE age = 20và bạn không có chỉ số về người (tuổi), thì nó sẽ yêu cầu quét tuần tự O (n) thay vì quét chỉ mục O (log n) nhanh hơn nhiều.

Phân cấp số nguyên

Khi lập trình bằng C, hãy nhớ rằng một số phép toán số học đắt hơn các phép toán khác. Đối với số nguyên, hệ thống phân cấp có dạng như thế này (ít tốn kém nhất trước tiên):

  • + - ~ & | ^
  • << >>
  • *
  • /

Cấp, trình biên dịch sẽ điều thường tối ưu hóa như n / 2để n >> 1tự động nếu bạn đang nhắm mục tiêu một máy tính chủ đạo, nhưng nếu bạn đang nhắm mục tiêu một thiết bị nhúng, bạn có thể không nhận được sang trọng.

Ngoài ra, % 2& 1có ngữ nghĩa khác nhau. Phân chia và mô đun thường làm tròn về 0, nhưng nó được xác định. Tốt ol ' >>&luôn luôn hướng về vô cực tiêu cực, mà (theo ý kiến ​​của tôi) có ý nghĩa hơn rất nhiều. Chẳng hạn, trên máy tính của tôi:

printf("%d\n", -1 % 2); // -1 (maybe)
printf("%d\n", -1 & 1); // 1

Do đó, sử dụng những gì có ý nghĩa. Đừng nghĩ rằng bạn là một cậu bé tốt bằng cách sử dụng % 2khi ban đầu bạn sẽ viết & 1.

Hoạt động điểm nổi tốn kém

Tránh các hoạt động dấu phẩy động nặng như pow()log()trong mã không thực sự cần chúng, đặc biệt là khi giao dịch với số nguyên. Lấy ví dụ, đọc một số:

int parseInt(const char *str)
{
    const char *p;
    int         digits;
    int         number;
    int         position;

    // Count the number of digits
    for (p = str; isdigit(*p); p++)
        {}
    digits = p - str;

    // Sum the digits, multiplying them by their respective power of 10.
    number = 0;
    position = digits - 1;
    for (p = str; isdigit(*p); p++, position--)
        number += (*p - '0') * pow(10, position);

    return number;
}

Việc sử dụng pow()(và int<-> doublechuyển đổi cần thiết để sử dụng nó) khá tốn kém, nhưng nó tạo ra cơ hội cho việc mất độ chính xác (tình cờ, mã ở trên không có vấn đề chính xác). Đó là lý do tại sao tôi nhăn nhó khi thấy loại hàm này được sử dụng trong bối cảnh phi toán học.

Ngoài ra, hãy chú ý cách thuật toán "thông minh" bên dưới, nhân với 10 trên mỗi lần lặp, thực sự ngắn gọn hơn mã ở trên:

int parseInt(const char *str)
{
    const char *p;
    int         number;

    number = 0;
    for (p = str; isdigit(*p); p++) {
        number *= 10;
        number += *p - '0';
    }

    return number;
}

Câu trả lời rất kỹ lưỡng.
Paddyslacker

1
Lưu ý, các cuộc thảo luận tối ưu hóa sớm không áp dụng cho mã rác. Bạn nên luôn luôn sử dụng một triển khai làm việc tốt ở nơi đầu tiên.

Lưu ý rằng GCC thực sự tối ưu hóa trường hợp này vì strlen () được đánh dấu là một hàm thuần túy. Tôi nghĩ bạn có nghĩa là nó là một hàm const, không thuần túy.
Andy Lester

@Andy Lester: Thật ra, tôi có nghĩa là thuần khiết. Trong tài liệu GCC , nó nói rằng const hơi nghiêm ngặt hơn thuần túy ở chỗ một hàm const không thể đọc bộ nhớ chung. strlen()kiểm tra chuỗi được trỏ đến bởi đối số con trỏ của nó, nghĩa là nó không thể là const. Ngoài ra, strlen()thực sự được đánh dấu là tinh khiết trong glibcstring.h
Joey Adams

Bạn nói đúng, lỗi của tôi và tôi nên kiểm tra lại. Tôi đã làm việc với các hàm chú thích của dự án Parrot dưới dạng purehoặc constthậm chí là ghi lại nó trong tệp tiêu đề vì sự khác biệt tinh tế giữa hai chức năng. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_fifts.pod.html
Andy Lester

13

Từ câu hỏi của bạn và chuỗi nhận xét, có vẻ như bạn "nghĩ" rằng thay đổi mã này sẽ cải thiện hiệu suất, nhưng bạn không thực sự biết liệu nó có hay không.

Tôi là người hâm mộ triết lý của Kent Beck :

"Làm cho nó hoạt động, làm cho nó đúng, làm cho nó nhanh."

Kỹ thuật của tôi để cải thiện hiệu suất mã, trước tiên là lấy mã vượt qua các bài kiểm tra đơn vị và được bao gồm nhiều thứ và sau đó (đặc biệt cho các hoạt động lặp) viết một bài kiểm tra đơn vị kiểm tra hiệu năng và sau đó cấu trúc lại mã hoặc nghĩ về một thuật toán khác nếu tôi đã chọn không làm việc như mong đợi.

Ví dụ: để kiểm tra tốc độ bằng mã .NET, tôi sử dụng thuộc tính Timeout của NUnit để viết các xác nhận rằng một cuộc gọi đến một phương thức cụ thể sẽ thực hiện trong một khoảng thời gian nhất định.

Sử dụng một cái gì đó như thuộc tính hết thời gian của NUnit với ví dụ mã bạn đã đưa ra (và một số lượng lớn các lần lặp cho vòng lặp), bạn thực sự có thể chứng minh liệu "cải thiện" mã của mình có thực sự giúp ích cho sự lặp lại của vòng lặp đó hay không.

Từ chối trách nhiệm: Mặc dù điều này có hiệu quả ở cấp độ "vi mô", nhưng chắc chắn đây không phải là cách duy nhất để kiểm tra hiệu suất và không tính đến các vấn đề có thể phát sinh ở cấp độ "vĩ mô" - nhưng đó là một khởi đầu tốt.


2
Mặc dù tôi là một người tin tưởng lớn vào hồ sơ, tôi cũng tin rằng thật thông minh khi giữ các loại lời khuyên mà Cristian đang tìm kiếm trong đầu. Tôi sẽ luôn chọn nhanh hơn trong hai phương pháp có thể đọc được như nhau. Bị buộc phải tối ưu hóa sau trưởng thành là không vui.
HỎI

Không nhất thiết phải có các bài kiểm tra đơn vị, nhưng luôn luôn đáng để dành 20 phút này để kiểm tra xem một số huyền thoại hiệu suất có đúng hay không, đặc biệt là vì câu trả lời thường phụ thuộc vào trình biên dịch và trạng thái của cờ -O và -g (hoặc Debug / Phát hành trong trường hợp của VS).
mbq

+1 Câu trả lời này bổ sung nhận xét liên quan của tôi cho chính câu hỏi.
Tamara Wijsman

1
@AShelly: nếu chúng ta đang nói về các cải cách đơn giản của cú pháp vòng lặp, việc thay đổi nó sau khi thực tế là rất dễ thực hiện. Ngoài ra, những gì bạn thấy có thể đọc được như nhau có thể không phải như vậy đối với các lập trình viên khác. Tốt nhất nên sử dụng cú pháp "chuẩn" càng nhiều càng tốt và chỉ thay đổi nó khi cần thiết.
Joeri Sebrechts

@AShelly chắc chắn nếu bạn có thể nghĩ ra hai phương pháp dễ đọc như nhau và bạn chọn phương pháp kém hiệu quả hơn mà bạn không làm việc của mình? Bất cứ ai sẽ thực sự làm điều đó?
glenatron

11

Hãy nhớ rằng trình biên dịch của bạn cũng có thể biến:

for(int i = 0; i < collection.length(); i++ ){
   // stuff here
}

vào:

int j = collection.length();
for(int i = 0; i < j; i++ ){
   // stuff here
}

hoặc một cái gì đó tương tự, nếu collectionkhông thay đổi trong vòng lặp.

Nếu mã này nằm trong phần quan trọng của ứng dụng, bạn nên tìm hiểu xem đây có phải là trường hợp hay không - hoặc thực sự liệu bạn có thể thay đổi các tùy chọn trình biên dịch để thực hiện việc này hay không.

Điều này sẽ duy trì khả năng đọc mã (vì trước đây là điều mà hầu hết mọi người sẽ thấy), đồng thời giúp bạn có thêm vài chu kỳ máy. Sau đó, bạn thoải mái tập trung vào các lĩnh vực khác mà trình biên dịch không thể giúp bạn.

Một lưu ý phụ: nếu bạn thay đổi collectionbên trong vòng lặp bằng cách thêm hoặc xóa các phần tử (vâng, tôi biết đó là một ý tưởng tồi, nhưng nó đã xảy ra) thì ví dụ thứ hai của bạn sẽ không lặp qua tất cả các phần tử hoặc sẽ cố gắng truy cập qua sự kết thúc của mảng.


1
Tại sao không làm điều đó một cách rõ ràng?

3
Trong một số ngôn ngữ giới hạn kiểm tra, bạn sẽ CHẬM mã của mình nếu bạn làm điều đó một cách rõ ràng. Với một vòng lặp để Collection.length trình biên dịch sẽ chuyển nó ra cho bạn và bỏ qua kiểm tra giới hạn. Với một vòng lặp đến một số hằng số từ các nơi khác trong ứng dụng của bạn, bạn sẽ có một kiểm tra giới hạn trên mỗi lần lặp. Đó là lý do tại sao nó quan trọng để đo lường - trực giác về hiệu suất gần như không bao giờ đúng.
Kate Gregory

1
Đó là lý do tại sao tôi nói "nó sẽ đáng để tìm hiểu".
ChrisF

Làm thế nào trình biên dịch C # có thể biết rằng bộ sưu tập.length () không sửa đổi bộ sưu tập, theo cách mà stack.pop () thực hiện? Tôi nghĩ rằng tốt nhất là kiểm tra IL thay vì giả sử trình biên dịch tối ưu hóa điều này. Trong C ++, bạn có thể đánh dấu một phương thức là const ('không thay đổi đối tượng'), vì vậy trình biên dịch có thể thực hiện tối ưu hóa này một cách an toàn.
JBRWilkinson

1
@JBRW Các trình tối ưu hóa thực hiện điều này cũng nhận thức được các phương thức ok-let-call-it-constness-thậm chí-dù-điều này không phải là C ++ của các bộ sưu tập. Sau tất cả, bạn chỉ có thể kiểm tra giới hạn nếu bạn có thể nhận thấy rằng một cái gì đó là một bộ sưu tập và biết làm thế nào để có được chiều dài của nó.
Kate Gregory

9

Loại tối ưu hóa này thường không được khuyến khích. Phần tối ưu hóa đó có thể dễ dàng được thực hiện bởi trình biên dịch, bạn đang làm việc với ngôn ngữ lập trình cấp cao hơn thay vì lắp ráp, vì vậy hãy suy nghĩ ở cùng cấp độ.


1
Đưa cho cô ấy một cuốn sách về lập trình;)
Joeri Sebrechts

1
+1, vì hầu hết các bạn gái của chúng tôi có thể quan tâm đến Lady Gaga hơn là sự rõ ràng về mã.
đơn bội

Bạn có thể giải thích tại sao nó không được khuyến khích?
Macneil

@macneil tốt ... mẹo đó làm cho các mã không phổ biến và hoàn toàn không hoạt động, phần tối ưu hóa đó được cho là do trình biên dịch thực hiện.
chiến thuật

@macneil nếu bạn đang làm việc với ngôn ngữ cấp cao hơn, hãy suy nghĩ ở cùng cấp độ.
chiến thuật

3

Điều này có thể không áp dụng quá nhiều cho mã hóa mục đích chung, nhưng tôi chủ yếu nhúng phát triển ngày nay. Chúng tôi có một bộ xử lý mục tiêu cụ thể (sẽ không nhanh hơn - nó sẽ dường như bị lỗi thời bởi thời gian họ nghỉ hưu hệ thống trong hơn 20 năm) và thời hạn rất hạn chế đối với phần lớn mã. Bộ xử lý, giống như tất cả các bộ xử lý, có một số điểm nhất định liên quan đến hoạt động nào nhanh hay chậm.

Chúng tôi có một kỹ thuật được sử dụng để đảm bảo chúng tôi đang tạo mã hiệu quả nhất, trong khi vẫn duy trì khả năng đọc cho cả nhóm. Ở những nơi mà cấu trúc ngôn ngữ tự nhiên nhất không tạo ra mã hiệu quả nhất, chúng tôi đã tạo một macro để đảm bảo mã tối ưu được sử dụng. Nếu chúng tôi thực hiện dự án tiếp theo cho một bộ xử lý khác, chúng tôi có thể cập nhật các macro cho phương thức tối ưu trên bộ xử lý đó.

Như một ví dụ cụ thể, đối với bộ xử lý hiện tại của chúng tôi, các nhánh làm trống đường ống, làm trì hoãn bộ xử lý trong 8 chu kỳ. Trình biên dịch lấy mã này:

 bool isReady = (value > TriggerLevel);

và biến nó thành hội đồng tương đương

isReady = 0
if (value > TriggerLevel)
{
  isReady = 1;
}

Điều này sẽ mất 3 chu kỳ, hoặc 10 nếu nó nhảy qua isReady=1;. Nhưng bộ xử lý có một lệnh đơn chu kỳ max, vì vậy tốt hơn hết là viết mã để tạo ra chuỗi này được đảm bảo luôn luôn thực hiện 3 chu kỳ:

diff = value-TriggerLevel;
diff = max(diff, 0);
isReady = min(1,diff);

Rõ ràng, ý định ở đây ít rõ ràng hơn bản gốc. Vì vậy, chúng tôi đã tạo một macro mà chúng tôi sử dụng bất cứ khi nào chúng tôi muốn so sánh Boolean Greater-Than:

#define BOOL_GT(a,b) min(max((a)-(b),0),1)

//isReady = value > TriggerLevel;
isReady = BOOL_GT(value, TriggerLevel);

Chúng ta có thể làm những điều tương tự cho các so sánh khác. Đối với người ngoài, mã ít dễ đọc hơn nếu chúng ta chỉ sử dụng cấu trúc tự nhiên. Tuy nhiên, nó nhanh chóng trở nên rõ ràng sau khi dành một ít thời gian làm việc với mã, và nó tốt hơn nhiều so với việc để mọi lập trình viên thử nghiệm các kỹ thuật tối ưu hóa của riêng họ.


3

Chà, lời khuyên đầu tiên sẽ là tránh tối ưu hóa sớm như vậy cho đến khi bạn biết chính xác điều gì đang xảy ra với mã, để bạn chắc chắn rằng bạn đang thực sự làm cho nó nhanh hơn, và không chậm hơn.

Ví dụ, trong C #, trình biên dịch sẽ tối ưu hóa mã nếu bạn đang lặp chiều dài của một mảng, vì nó biết rằng nó không phải kiểm tra phạm vi chỉ mục khi bạn truy cập vào mảng. Nếu bạn cố gắng tối ưu hóa nó bằng cách đặt độ dài mảng vào một biến, bạn sẽ phá vỡ kết nối giữa vòng lặp và mảng và thực sự làm cho mã chậm hơn rất nhiều.

Nếu bạn định tối ưu hóa vi mô, bạn nên giới hạn bản thân trong những thứ được biết là sử dụng nhiều tài nguyên. Nếu chỉ có một mức tăng hiệu suất nhẹ, thay vào đó bạn nên sử dụng mã dễ đọc và dễ bảo trì nhất. Làm thế nào máy tính làm việc thay đổi theo thời gian, vì vậy một cái gì đó mà bạn phát hiện ra bây giờ nhanh hơn một chút, có thể không theo cách đó.


3

Tôi có một kỹ thuật rất đơn giản.

  1. Tôi làm cho mã của tôi hoạt động.
  2. Tôi kiểm tra nó cho tốc độ.
  3. Nếu nó nhanh, tôi quay lại bước 1 cho một số tính năng khác. Nếu nó chậm, tôi cấu hình nó để tìm ra nút cổ chai.
  4. Tôi sửa chữa nút cổ chai. Quay trở lại bước 1.

Có rất nhiều lần nó tiết kiệm thời gian để phá vỡ quy trình này, nhưng nói chung bạn sẽ biết nếu đó là trường hợp. Nếu có nghi ngờ, tôi mặc định dính vào điều này.


2

Tận dụng ngắn mạch:

if(someVar || SomeMethod())

mất nhiều thời gian để viết mã và có thể đọc được như sau:

if(someMethod() || someVar)

nhưng nó sẽ đánh giá nhanh hơn theo thời gian.


1

Đợi sáu tháng, nhờ sếp mua cho mọi người máy tính mới. Nghiêm túc. Thời gian lập trình viên đắt hơn nhiều so với phần cứng trong thời gian dài. Máy tính hiệu năng cao cho phép các lập trình viên viết mã theo cách đơn giản mà không cần quan tâm đến tốc độ.


6
Er ... Còn hiệu suất mà khách hàng của bạn nhìn thấy thì sao? Bạn có đủ giàu để mua máy tính mới cho họ không?
Robert Harvey

2
Và chúng tôi đã gần như đạt được thành tích; tính toán đa lõi là lối thoát duy nhất, nhưng chờ đợi sẽ không làm cho các chương trình của bạn sử dụng nó.
mbq

+1 Câu trả lời này bổ sung nhận xét liên quan của tôi cho chính câu hỏi.
Tamara Wijsman

3
Không có thời gian lập trình không đắt hơn phần cứng khi bạn có hàng ngàn hoặc hàng nghìn người dùng. Thời gian lập trình viên KHÔNG quan trọng hơn thời gian của người dùng, hãy thông qua đầu của bạn càng sớm càng tốt.
HLGEM

1
Tập thói quen tốt, sau đó không mất thời gian của lập trình viên vì đó là việc bạn làm mọi lúc.
Dominique McDonnell

1

Cố gắng không tối ưu hóa quá nhiều trước thời hạn, sau đó khi bạn tối ưu hóa lo lắng một chút về khả năng đọc.

Tôi ít ghét hơn sự phức tạp không cần thiết, nhưng khi bạn gặp phải một tình huống phức tạp, một giải pháp phức tạp thường được yêu cầu.

Nếu bạn viết mã theo cách rõ ràng nhất thì hãy đưa ra nhận xét giải thích tại sao nó bị thay đổi khi bạn thực hiện thay đổi phức tạp.

Cụ thể theo ý nghĩa của bạn, tôi thấy rằng rất nhiều lần thực hiện Boolean ngược lại với cách tiếp cận mặc định đôi khi giúp:

for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}

có thể trở thành

for(int i = collection.length(); i > 0; i-=1 ){
// stuff here
}

Trong nhiều ngôn ngữ miễn là bạn thực hiện các điều chỉnh phù hợp cho phần "nội dung" và nó vẫn có thể đọc được. Nó chỉ không tiếp cận vấn đề theo cách mà hầu hết mọi người sẽ nghĩ về việc làm nó trước tiên vì nó đếm ngược.

trong c # chẳng hạn:

        string[] collection = {"a","b"};

        string result = "";

        for (int i = 0, j = collection.Count() - 1; i < j; i++)
        {
            result += collection[i] + "~";
        }

cũng có thể được viết là:

        for (int i = collection.Count() - 1; i > 0; i -= 1)
        {
            result = collection[i] + "~" + result;
        }

(và vâng, bạn nên o điều đó với một người tham gia hoặc một người xây dựng chuỗi, nhưng tôi đang cố gắng làm một ví dụ đơn giản)

Có nhiều thủ thuật khác người ta có thể sử dụng không khó để thực hiện nhưng nhiều trong số chúng không áp dụng trên tất cả các ngôn ngữ như sử dụng mid ở bên trái của bài tập trong vb cũ để tránh hình phạt gán lại chuỗi hoặc đọc tệp văn bản ở chế độ nhị phân trong .net để vượt qua hình phạt đệm khi tệp quá lớn cho một lần đọc.

Trường hợp thực sự chung duy nhất khác mà tôi có thể nghĩ rằng sẽ áp dụng ở mọi nơi sẽ là áp dụng một số đại số Boolean cho các điều kiện phức tạp để cố gắng biến đổi phương trình thành một cái gì đó có cơ hội tốt hơn để tận dụng điều kiện ngắn mạch hoặc biến phức tập hợp các câu lệnh if-then hoặc case lồng nhau thành một phương trình hoàn toàn. Cả hai đều không hoạt động trong mọi trường hợp, nhưng chúng có thể là những người tiết kiệm thời gian đáng kể.


đó là một giải pháp, nhưng trình biên dịch có thể sẽ đưa ra các cảnh báo vì đối với hầu hết các lớp phổ biến () sẽ trả về một kiểu không dấu
stijn

Nhưng bằng cách đảo ngược chỉ số, việc lặp lại có thể trở nên phức tạp hơn.
Tamara Wijsman

@stijn Tôi đã nghĩ đến c # khi tôi viết nó, nhưng có lẽ gợi ý này cũng thuộc danh mục ngôn ngữ cụ thể vì lý do đó - xem chỉnh sửa ... @ToWij chắc chắn, tôi không nghĩ có nhiều đề xuất về bản chất này Điều đó không có nguy cơ về điều đó. Nếu // công cụ của bạn là một loại thao tác ngăn xếp, thậm chí có thể không thể đảo ngược logic một cách chính xác, nhưng trong nhiều trường hợp, nó không quá khó hiểu nếu được thực hiện cẩn thận trong hầu hết các trường hợp đó IMHO.
Hóa đơn

bạn đúng; trong C ++, tôi vẫn thích vòng lặp 'bình thường' nhưng với lệnh gọi độ dài () được đưa ra khỏi vòng lặp (như trong const size_t len ​​= Collection.length (); for (size_t i = 0; i <len; ++ i) {}) vì hai lý do: Tôi thấy vòng lặp đếm ngược 'bình thường' dễ đọc / dễ hiểu hơn (nhưng có lẽ chỉ vì nó phổ biến hơn) và nó gọi vòng lặp bất biến vòng lặp () ra khỏi vòng lặp.
stijn

1
  1. Hồ sơ. Chúng ta thậm chí có một vấn đề? Ở đâu?
  2. Trong 90% trường hợp liên quan đến IO bằng cách nào đó, hãy áp dụng bộ nhớ đệm (và có thể nhận thêm bộ nhớ)
  3. Nếu nó liên quan đến CPU, hãy áp dụng bộ nhớ đệm
  4. Nếu hiệu suất vẫn là một vấn đề, chúng ta đã rời khỏi lĩnh vực kỹ thuật đơn giản - làm toán.

1

Sử dụng các công cụ tốt nhất bạn có thể tìm thấy - trình biên dịch tốt, trình biên dịch tốt, thư viện tốt. Làm cho các thuật toán đúng, hoặc tốt hơn vẫn là - sử dụng đúng thư viện để làm điều đó cho bạn. Tối ưu hóa vòng lặp tầm thường là khoai tây nhỏ, cộng với bạn không thông minh như trình biên dịch tối ưu hóa.


1

Đơn giản nhất với tôi là sử dụng ngăn xếp khi có thể bất cứ khi nào một mẫu sử dụng trường hợp thông thường phù hợp với một phạm vi, giả sử, [0, 64) nhưng có những trường hợp hiếm hoi không có giới hạn trên nhỏ.

Ví dụ đơn giản C (trước):

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int* values = calloc(n, sizeof(int));

    // do stuff with values
    ...
    free(values);
}

Và sau:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    int values_mem[64] = {0}
    int* values = (n <= 64) ? values_mem: calloc(n, sizeof(int));

    // do stuff with values
    ...
    if (values != values_mem)
        free(values);
}

Tôi đã khái quát điều này giống như vậy vì các loại điểm nóng này mọc lên rất nhiều trong hồ sơ:

void some_hotspot_called_in_big_loops(int n, ...)
{
    // 'n' is, 99% of the time, <= 64.
    MemFast values_mem;
    int* values = mf_calloc(&values_mem, n, sizeof(int));

    // do stuff with values
    ...

    mf_free(&values_mem);
}

Ở trên sử dụng ngăn xếp khi dữ liệu được phân bổ đủ nhỏ trong 99,9% trường hợp đó và sử dụng heap khác.

Trong C ++, tôi đã khái quát hóa điều này với một chuỗi nhỏ tuân thủ tiêu chuẩn (tương tự như SmallVector triển khai ngoài kia) xoay quanh cùng một khái niệm.

Đây không phải là một tối ưu hóa sử thi (tôi đã nhận được giảm từ 3 giây để hoàn thành một hoạt động xuống còn 1,8 giây), nhưng nó đòi hỏi nỗ lực tầm thường như vậy để áp dụng. Khi bạn có thể giảm thứ gì đó từ 3 giây xuống còn 1,8 giây chỉ bằng cách giới thiệu một dòng mã và thay đổi hai, đó là một tiếng nổ khá tốt cho một thùng nhỏ như vậy.


0

Vâng, có rất nhiều thay đổi về hiệu suất bạn có thể thực hiện khi truy cập dữ liệu sẽ có tác động rất lớn đến ứng dụng của bạn. Nếu bạn viết truy vấn hoặc sử dụng ORM để truy cập cơ sở dữ liệu, thì bạn cần đọc một số sách điều chỉnh hiệu suất cho phụ trợ cơ sở dữ liệu bạn sử dụng. Có thể bạn đang sử dụng các techinques hoạt động kém được biết đến. Không có lý do để làm điều này ngoại trừ sự thiếu hiểu biết. Đây không phải là tối ưu hóa sớm (tôi nguyền rủa anh chàng đã nói điều này bởi vì nó đã được xen kẽ rộng rãi như không bao giờ lo lắng về hiệu suất), đây là thiết kế tốt.

Chỉ là một mẫu nhanh chóng của các trình cải tiến hiệu suất cho SQL Server: Sử dụng các chỉ mục thích hợp, tránh các con trỏ - sử dụng logic dựa trên tập hợp, sử dụng các mệnh đề trong các mệnh đề, không chồng các lượt xem lên trên các lượt xem, không trả lại nhiều dữ liệu hơn bạn cần hoặc nhiều hơn các cột hơn bạn cần, không sử dụng các truy vấn con tương quan.


0

Nếu đây là C ++, bạn nên tập thói quen ++ichứ không phải i++. ++isẽ không bao giờ tệ hơn, nó có nghĩa chính xác giống như một tuyên bố độc lập và trong một số trường hợp, nó có thể là một cải tiến hiệu suất.

Không đáng để thay đổi mã hiện tại nếu không có khả năng nó sẽ có ích, nhưng đó là một thói quen tốt để tham gia.


0

Tôi đã có một chút khác biệt về nó. Chỉ cần làm theo lời khuyên mà bạn nhận được ở đây sẽ không tạo ra nhiều khác biệt, bởi vì có một số sai lầm bạn cần mắc phải, sau đó bạn cần sửa chữa, sau đó bạn cần học hỏi.

Sai lầm bạn cần mắc phải là thiết kế cấu trúc dữ liệu của bạn theo cách mọi người làm. Đó là, với dữ liệu dư thừa và nhiều lớp trừu tượng, với các thuộc tính và thông báo lan truyền khắp cấu trúc cố gắng giữ cho nó nhất quán.

Sau đó, bạn cần thực hiện điều chỉnh hiệu suất (định hình) và để nó chỉ cho bạn cách, theo nhiều cách, điều khiến bạn tốn nhiều chu kỳ là nhiều lớp trừu tượng, với các thuộc tính và thông báo lan truyền khắp cấu trúc cố gắng giữ cho nó nhất quán.

Bạn có thể khắc phục những vấn đề này một chút mà không cần thay đổi lớn đối với mã.

Sau đó, nếu bạn may mắn, bạn có thể học được rằng cấu trúc dữ liệu ít hơn là tốt hơn và tốt hơn là có thể chịu đựng sự không nhất quán tạm thời hơn là cố gắng giữ nhiều thứ chặt chẽ với sóng tin nhắn.

Làm thế nào bạn viết vòng lặp thực sự không có gì để làm với nó.

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.