19

Chức năng của tôi sẽ được gọi là hàng ngàn lần. Nếu tôi muốn làm cho nó nhanh hơn, sẽ thay đổi các biến chức năng cục bộ thành tĩnh được sử dụng bất kỳ? Logic của tôi đằng sau điều này, bởi vì các biến tĩnh liên tục giữa các cuộc gọi hàm, chúng chỉ được cấp phát lần đầu tiên, và do đó, mọi cuộc gọi tiếp theo sẽ không cấp phát bộ nhớ cho chúng và sẽ trở nên nhanh hơn, vì bước cấp phát bộ nhớ không được thực hiện.Trong C, việc sử dụng các biến tĩnh trong một hàm có làm cho nó nhanh hơn không?

Ngoài ra, nếu điều trên là đúng, thì sẽ sử dụng các biến toàn cục thay vì các thông số sẽ nhanh hơn để truyền thông tin đến hàm mỗi khi nó được gọi? Tôi nghĩ rằng không gian cho các tham số cũng được phân bổ trên mọi cuộc gọi hàm, cho phép đệ quy (đó là lý do tại sao đệ quy sử dụng nhiều bộ nhớ hơn), nhưng vì chức năng của tôi không đệ quy, và nếu lý luận của tôi là chính xác, nó nhanh hơn.

Tôi biết những điều tôi muốn làm là thói quen lập trình khủng khiếp, nhưng xin vui lòng, cho tôi biết nếu nó là khôn ngoan. Tôi sẽ thử nó anyway nhưng xin vui lòng cho tôi ý kiến ​​của bạn.

+10

Không tối ưu hóa mã TRƯỚC KHI lập hồ sơ! ... –

+1

http://stackoverflow.com/questions/3730000/can-static-local-variables-cut-down-on-memory-allocation-time – jamesdlin

+0

Làm thường xấu những thứ để đạt được tốc độ cực kỳ cận biên cho một chức năng gọi là hàng ngàn lần là một ý tưởng thực sự tồi tệ. Nếu bạn có thể tiết kiệm 10ns cho mỗi lần gọi hàm cho một hàm gọi là hàng ngàn lần ... bạn đã lưu một số bội số của 10 micro giây, điều này không quan trọng, trừ khi bạn đang làm việc trên một hệ thống thời gian thực cứng và có một vấn đề nghiêm trọng với thời gian cắt. –

Trả lời

22

Chi phí của các biến cục bộ bằng 0. Mỗi khi bạn gọi hàm, bạn đã thiết lập ngăn xếp cho các tham số, giá trị trả về, v.v. Thêm biến cục bộ có nghĩa là bạn đang thêm số lớn hơn một chút vào con trỏ ngăn xếp (số được tính tại thời gian biên dịch) .

Ngoài ra, các biến cục bộ có thể nhanh hơn do vị trí bộ nhớ cache.

Nếu bạn chỉ gọi hàm "nghìn lần" của bạn (không phải hàng triệu hoặc hàng tỷ), thì bạn nên xem thuật toán của mình để có cơ hội tối ưu hóa sau bạn đã chạy trình thu thập thông tin.


Re: địa phương cache (read more here): biến toàn cục thường truy cập có thể có địa phương thời gian. Chúng cũng có thể được sao chép vào một thanh ghi trong khi thực thi hàm, nhưng sẽ được ghi lại vào bộ nhớ (cache) sau khi một hàm trả về (nếu không chúng sẽ không thể truy cập được vào bất kỳ thứ gì khác; thanh ghi không có địa chỉ).

Biến cục bộ nói chung sẽ có cả địa phương thời gian và không gian (chúng nhận được điều đó nhờ vào việc tạo ra trên ngăn xếp). Ngoài ra, chúng có thể được "cấp phát" trực tiếp cho các thanh ghi và không bao giờ được ghi vào bộ nhớ.

+3

+1 về tốc độ CPU hiện đại, "một nghìn lần một giây" là "một lần vài triệu chu kỳ". –

+2

+1 mặc dù nó phụ thuộc tất nhiên cách trình biên dịch tạo mã. Đối với trình biên dịch thông minh. sự khác biệt là giữa 'sub sp, 20' và' sub sp, 24', không có sự khác biệt nào cả. – paxdiablo

+0

+1: Tôi vừa viết gần như cùng một phản hồi. – dawg

9

Cách tốt nhất để tìm hiểu là thực sự chạy một hồ sơ. Điều này có thể đơn giản như thực hiện một số phép thử theo thời gian bằng cách sử dụng cả hai phương pháp và sau đó tính trung bình kết quả và so sánh, hoặc bạn có thể xem xét một công cụ lược tả đầy đủ, nó tự gắn với một quá trình và sử dụng bộ nhớ theo thời gian và tốc độ thực thi.

Không thực hiện điều chỉnh mã vi mô ngẫu nhiên vì bạn có cảm giác ruột sẽ nhanh hơn. Các trình biên dịch tất cả đều có những sự triển khai hơi khác nhau và những gì là đúng trên một trình biên dịch trên một môi trường có thể là sai trên một cấu hình khác.

Để giải quyết nhận xét đó về ít thông số hơn: quá trình "nội tuyến" chức năng sẽ loại bỏ chi phí liên quan đến việc gọi hàm. Cơ hội là một hàm nhỏ sẽ được trình biên dịch tự động điền, nhưng bạn cũng có thể suggest a function be inlined. Trong một ngôn ngữ khác, C++, chuẩn mới sắp ra hỗ trợ chuyển tiếp hoàn hảo và ngữ nghĩa di chuyển hoàn hảo với các tham chiếu rvalue giúp loại bỏ nhu cầu tạm thời trong một số trường hợp có thể làm giảm chi phí gọi hàm.

Tôi nghi ngờ bạn đang tối ưu hóa sớm, tuy nhiên, bạn không nên quan tâm đến hiệu suất này cho đến khi bạn phát hiện ra các nút cổ chai thực sự của mình.

+0

+1 cho việc hợp lý và chống lại sự thôi thúc nguy hiểm đoán. :) –

+0

cảm ơn! tôi đã thử nó, như tôi đã nói tôi sẽ làm. chương trình của tôi đã có một đoạn mã đếm số giây cần thiết để làm điều đó. những gì nó đã làm trong 60 giây trước điều tĩnh/toàn cầu hiện nay mất 49 giây. tôi vẫn không thể nói đó là một ý tưởng hay, nhưng nó dường như đã làm việc lần này, cho kết quả nhất quán :) tôi không biết về tối ưu hóa trình biên dịch hoặc ngăn xếp cũng được sử dụng cho các biến địa phương của hàm (tôi vẫn còn nhiều của một người mới). Ngoài ra, tôi sẽ chắc chắn nhìn vào c + + 0x khi nó ở đây (tất cả các tính năng của nó: tôi nghĩ rằng điều rvalue và lambdas đã có trong GCC: D). cảm ơn!! –

3

Tuyệt đối không! Các chỉ "hiệu suất" khác biệt là khi các biến được khởi tạo

int anint = 42; 
vs 
    static int anint = 42; 

Trong trường hợp đầu tiên các số nguyên sẽ được thiết lập để 42 mỗi khi hàm được gọi trong trường hợp thứ hai ot sẽ được thiết lập để 42 khi chương trình được tải .

Tuy nhiên, sự khác biệt này không đáng kể đến mức khó nhận thấy. Quan niệm sai lầm phổ biến của nó là lưu trữ phải được phân bổ cho các biến "tự động" trên mọi cuộc gọi. Đây không phải là C sử dụng không gian đã được phân bổ trong ngăn xếp cho các biến này.

Biến tĩnh thực sự có thể làm chậm bạn vì không thể thực hiện một số tối ưu hóa tăng cường trên các biến tĩnh. Cũng như người dân địa phương đang ở trong một khu vực liền kề của ngăn xếp chúng được dễ dàng hơn để lưu trữ hiệu quả.

1

Có, sử dụng các biến tĩnh sẽ làm cho hàm hoạt động nhanh hơn một chút. Tuy nhiên, điều này sẽ gây ra vấn đề nếu bạn muốn làm cho chương trình của bạn đa luồng. Vì các biến tĩnh được chia sẻ giữa các lời gọi hàm, việc gọi hàm đồng thời trong các luồng khác nhau sẽ dẫn đến hành vi không xác định.Đa luồng là loại điều bạn có thể muốn làm trong tương lai để tăng tốc mã của bạn.

Hầu hết những điều bạn đề cập được gọi là tối ưu hóa vi mô. Nói chung, lo lắng về những điều này là bad idea. Nó làm cho mã của bạn khó đọc hơn và khó bảo trì hơn. Nó cũng rất có khả năng để giới thiệu lỗi. Bạn sẽ có thể nhận được nhiều bang hơn cho buck của bạn làm tối ưu hóa ở một cấp độ cao hơn.

Như đề xuất M2tM, chạy trình chiếu cũng là một ý tưởng hay. Hãy xem gprof cho một thiết bị khá dễ sử dụng.

1

Bạn luôn có thể đặt ứng dụng của mình để thực sự xác định điều gì là nhanh nhất. Đây là những gì tôi hiểu: (tất cả điều này phụ thuộc vào kiến ​​trúc bộ vi xử lý của bạn, btw)

C chức năng tạo một khung ngăn xếp, đó là nơi thông số được đưa vào, và biến địa phương được đặt, cũng như trả lại con trỏ trở lại nơi người gọi được gọi là hàm. Không có phân bổ quản lý bộ nhớ ở đây. Nó thường là một chuyển động con trỏ đơn giản và thats nó. Truy cập dữ liệu ra khỏi ngăn xếp cũng khá nhanh. Hình phạt thường đi vào chơi khi bạn đang đối phó với con trỏ.

Đối với biến toàn cầu hoặc tĩnh, chúng giống nhau ... từ quan điểm rằng chúng sẽ được phân bổ trong cùng một vùng bộ nhớ. Việc truy cập chúng có thể sử dụng một phương thức truy cập khác với các biến cục bộ, phụ thuộc vào trình biên dịch.

Sự khác biệt chính giữa các kịch bản của bạn là dấu chân bộ nhớ, không quá nhiều tốc độ.

+2

Đây là một điểm quan trọng - miễn là các biến của bạn không được khởi tạo, được phân bổ 100 biến tự động chỉ nhanh như phân bổ một biến. – caf

+0

Cần lưu ý rằng trình biên dịch là "cấp phát" bộ nhớ, chứ không phải hệ thống quản lý bộ nhớ. – KFro

0

Tôi đồng ý với những nhận xét khác về hồ sơ để tìm ra nội dung như vậy, nhưng nói chung, các biến tĩnh chức năng sẽ chậm hơn. Nếu bạn muốn họ, những gì bạn đang thực sự sau là một toàn cầu. Chức năng statics chèn mã/dữ liệu để kiểm tra xem điều đã được khởi tạo đã được chạy mỗi khi chức năng của bạn được gọi.

1

Sử dụng biến tĩnh thực sự có thể làm cho mã của bạn đáng kể chậm hơn. Các biến tĩnh phải tồn tại trong vùng 'dữ liệu' của bộ nhớ.Để sử dụng biến đó, hàm phải thực thi lệnh tải để đọc từ bộ nhớ chính hoặc lệnh lưu trữ để ghi vào nó. Nếu khu vực đó không có trong bộ nhớ cache, bạn sẽ mất nhiều chu kỳ. Một biến cục bộ sống trên stack chắc chắn sẽ có một địa chỉ nằm trong cache, và thậm chí có thể nằm trong một thanh ghi cpu, không bao giờ xuất hiện trong bộ nhớ.

+0

* mỗi lần hàm được gọi, nó phải kiểm tra để đảm bảo rằng biến tĩnh là [chưa] được khởi tạo * <- Điều này không chính xác. Trước khi hàm main() chạy, tất cả các biến tĩnh được khởi tạo (trong __start()). Globals cũng được khởi tạo vào lúc này. –

+0

thường là hướng dẫn tải sẽ được sử dụng cho cả cục bộ trên ngăn xếp hoặc cục bộ trong vùng dữ liệu. nhận được biến khởi tạo lần đầu tiên là một điểm tốt, tốt mã hóa đòi hỏi rằng nếu sau đó khác. Biết nếu trình biên dịch của bạn/môi trường zero mà bộ nhớ trên khởi động chương trình là một phím tắt cho điều này, nguy hiểm, phong cách mã hóa xấu, nhưng thường hoạt động và nhanh (er). –

+0

@dwelch: vấn đề là một địa phương có thể không xuất hiện trong bộ nhớ chính, nó có thể được tối ưu hóa (an toàn) để chỉ sống trong một thanh ghi. – SingleNegationElimination

3

Không có câu trả lời nào cho điều này. Nó sẽ thay đổi với CPU, trình biên dịch, cờ trình biên dịch, số lượng biến cục bộ bạn có, những gì CPU đã làm trước khi bạn gọi hàm, và có thể là giai đoạn của mặt trăng.

Cân nhắc hai cực đoan; nếu bạn chỉ có một hoặc một vài biến địa phương, nó/chúng có thể dễ dàng được lưu trữ trong sổ đăng ký thay vì được cấp phát các vị trí bộ nhớ. Nếu đăng ký "áp lực" là đủ thấp mà điều này có thể xảy ra mà không thực hiện bất kỳ hướng dẫn nào cả.

Ở cực đối diện, có một số máy (ví dụ: các máy tính lớn của IBM) không có ngăn xếp. Trong trường hợp này, những gì chúng ta thường nghĩ là khung stack thực sự được phân bổ như một danh sách liên kết trên heap. Như bạn có thể đoán, điều này có thể là khá chậm.

Khi nói đến việc truy cập các biến, tình huống hơi giống nhau - quyền truy cập vào máy đăng ký được đảm bảo khá nhanh hơn bất kỳ thứ gì được cấp phát trong bộ nhớ. OTOH, có thể truy cập vào các biến trên stack khá chậm - nó thường đòi hỏi một cái gì đó giống như một truy cập gián tiếp được lập chỉ mục, mà (đặc biệt là với các CPU cũ) có xu hướng khá chậm. OTOH, quyền truy cập vào toàn cầu (mà tĩnh là, mặc dù tên của nó không phải là toàn cầu có thể nhìn thấy) thường đòi hỏi phải tạo thành một địa chỉ tuyệt đối, mà một số CPU bị phạt ở một mức độ nào đó.

Dòng dưới cùng: ngay cả lời khuyên để cấu hình mã của bạn có thể bị đặt sai chỗ - sự khác biệt có thể dễ dàng đến mức thậm chí một trình thu thập thông tin sẽ không phát hiện nó một cách đáng tin cậy và cách chỉ để kiểm tra ngôn ngữ lắp ráp được sản xuất (và dành một vài năm học ngôn ngữ lắp ráp đủ tốt để biết nói bất cứ điều gì khi bạn làm xem xét nó). Mặt khác của điều này là khi bạn đang đối phó với một sự khác biệt, bạn thậm chí không thể đo lường một cách đáng tin cậy, cơ hội mà nó sẽ có tác dụng vật chất lên tốc độ của mã thực là quá xa đến nỗi có lẽ nó không đáng để gây rắc rối.

+0

Um, Jerry, thông báo – Will

2

Dường như tĩnh và không tĩnh đã hoàn toàn được đề cập nhưng về chủ đề của các biến toàn cầu. Thường thì những điều này sẽ làm chậm một chương trình thực thi hơn là tăng tốc độ nó lên.

Lý do là các biến có phạm vi chặt chẽ làm cho trình biên dịch dễ dàng tối ưu hóa, nếu trình biên dịch phải xem xét toàn bộ ứng dụng của bạn cho các trường hợp có thể sử dụng toàn cầu thì tối ưu hóa sẽ không tốt.

này trở nên phức tạp khi bạn giới thiệu gợi ý, nói rằng bạn có đoạn mã sau:

int myFunction() 
{ 
    SomeStruct *A, *B; 
    FillOutSomeStruct(B); 
    memcpy(A, B, sizeof(A); 
    return A.result; 
} 

trình biên dịch biết rằng con trỏ A và B không bao giờ có thể chồng chéo lên nhau và vì vậy nó có thể tối ưu hóa các bản sao. Nếu A và B là toàn cục thì chúng có thể trỏ đến bộ nhớ trùng lặp hoặc giống hệt nhau, điều này có nghĩa là trình biên dịch phải 'phát nó an toàn' chậm hơn. Vấn đề thường được gọi là 'con trỏ răng cưa' và có thể xảy ra trong rất nhiều tình huống không chỉ là bản sao bộ nhớ.

http://en.wikipedia.org/wiki/Pointer_alias

0

Profiling có thể không thấy sự khác biệt, tháo và biết những gì để tìm kiếm sức mạnh.

Tôi nghi ngờ bạn sẽ chỉ nhận được biến thể nhiều như một vài chu kỳ đồng hồ trên mỗi vòng lặp (trung bình tùy thuộc vào trình biên dịch, v.v.). Đôi khi sự thay đổi sẽ được cải thiện đáng kể hoặc chậm hơn đáng kể, và điều đó không nhất thiết là do các biến chủ đã chuyển đến/từ ngăn xếp. Cho phép nói rằng bạn tiết kiệm bốn chu kỳ đồng hồ cho mỗi cuộc gọi hàm cho 10000 cuộc gọi trên một bộ xử lý 2ghz. Tính toán rất thô: 20 micro giây được lưu. Là 20 micro giây rất nhiều hoặc một chút so với thời gian thực hiện hiện tại của bạn?

Bạn có thể sẽ nhận được nhiều cải thiện hiệu suất hơn bằng cách biến tất cả các biến char và ngắn thành ints, trong số những thứ khác. Tối ưu hóa vi mô là một điều tốt để biết nhưng mất rất nhiều thời gian thử nghiệm, tháo rời, định thời gian thực thi mã của bạn, hiểu rằng ít lệnh hơn không nhất thiết có nghĩa là nhanh hơn chẳng hạn.

Thực hiện chương trình cụ thể của bạn, tháo rời cả chức năng được đề cập và mã gọi nó. Có và không có tĩnh. Nếu bạn đạt được chỉ một hoặc hai hướng dẫn và đây là tối ưu hóa duy nhất bạn sẽ làm, nó có lẽ không có giá trị nó. Bạn có thể không thấy được sự khác biệt trong khi lược tả. Thay đổi trong đó các dòng bộ nhớ cache được nhấn có thể hiển thị trong hồ sơ trước khi thay đổi mã ví dụ.

Các vấn đề liên quan