Câu hỏi gốc
Tại sao một vòng lặp chậm hơn nhiều so với hai vòng lặp?
Phần kết luận:
Trường hợp 1 là một vấn đề nội suy cổ điển xảy ra là một vấn đề không hiệu quả. Tôi cũng nghĩ rằng đây là một trong những lý do hàng đầu khiến nhiều kiến trúc sư và nhà phát triển kết thúc việc xây dựng và thiết kế các hệ thống đa lõi với khả năng thực hiện các ứng dụng đa luồng cũng như lập trình song song.
Nhìn vào phương pháp này theo cách tiếp cận mà không liên quan đến cách Phần cứng, HĐH và Trình biên dịch hoạt động cùng nhau để thực hiện phân bổ heap liên quan đến làm việc với RAM, Cache, Tệp trang, v.v.; toán học là nền tảng của các thuật toán này cho chúng ta thấy cái nào trong hai cái này là giải pháp tốt hơn.
Chúng ta có thể sử dụng một sự tương tự của một Boss
sinh vật Summation
sẽ đại diện cho một người For Loop
phải đi lại giữa các công nhân A
& B
.
Chúng ta có thể dễ dàng thấy rằng Trường hợp 2 nhanh nhất ít nhất là một nửa nếu không hơn một chút so với Trường hợp 1 do sự khác biệt về khoảng cách cần thiết để đi lại và thời gian giữa các công nhân. Toán học này sắp xếp gần như hoàn hảo và hoàn hảo với cả Thời báo BenchMark cũng như số lượng khác biệt trong Hướng dẫn lắp ráp.
Bây giờ tôi sẽ bắt đầu giải thích làm thế nào tất cả những điều này hoạt động dưới đây.
Đánh giá vấn đề
Mã của OP:
const int n=100000;
for(int j=0;j<n;j++){
a1[j] += b1[j];
c1[j] += d1[j];
}
Và
for(int j=0;j<n;j++){
a1[j] += b1[j];
}
for(int j=0;j<n;j++){
c1[j] += d1[j];
}
Sự xem xét
Xem xét câu hỏi ban đầu của OP về 2 biến thể của vòng lặp và câu hỏi sửa đổi của anh ấy đối với hành vi của bộ nhớ cache cùng với nhiều câu trả lời xuất sắc và nhận xét hữu ích khác; Tôi muốn thử và làm điều gì đó khác biệt ở đây bằng cách thực hiện một cách tiếp cận khác về tình huống và vấn đề này.
Tiếp cận
Xem xét hai vòng lặp và tất cả các cuộc thảo luận về bộ đệm và lưu trang tôi muốn thực hiện một cách tiếp cận khác để xem xét điều này từ một quan điểm khác. Một cách không liên quan đến bộ đệm và tệp trang cũng như thực thi để phân bổ bộ nhớ, trên thực tế, phương pháp này thậm chí không liên quan đến phần cứng hoặc phần mềm thực tế.
Quan điểm
Sau khi xem mã một lúc, nó trở nên khá rõ ràng vấn đề là gì và cái gì đang tạo ra nó. Chúng ta hãy giải quyết vấn đề này thành một vấn đề thuật toán và xem xét nó từ góc độ sử dụng các ký hiệu toán học sau đó áp dụng một sự tương tự cho các vấn đề toán học cũng như các thuật toán.
Những gì chúng ta biết
Chúng tôi biết rằng vòng lặp này sẽ chạy 100.000 lần. Chúng tôi cũng biết rằng a1
, b1
, c1
& d1
là gợi ý về một kiến trúc 64-bit. Trong C ++ trên máy 32 bit, tất cả các con trỏ là 4 byte và trên máy 64 bit, chúng có kích thước 8 byte do các con trỏ có độ dài cố định.
Chúng tôi biết rằng chúng tôi có 32 byte để phân bổ trong cả hai trường hợp. Sự khác biệt duy nhất là chúng ta đang phân bổ 32 byte hoặc 2 bộ 2-8byte trên mỗi lần lặp trong trường hợp thứ 2 chúng ta đang phân bổ 16 byte cho mỗi lần lặp cho cả hai vòng lặp độc lập.
Cả hai vòng vẫn bằng 32 byte trong tổng phân bổ. Với thông tin này, bây giờ chúng ta hãy tiếp tục và hiển thị toán học chung, thuật toán và sự tương tự của các khái niệm này.
Chúng tôi biết số lần mà cùng một nhóm hoặc nhóm hoạt động sẽ phải được thực hiện trong cả hai trường hợp. Chúng tôi biết số lượng bộ nhớ cần được phân bổ trong cả hai trường hợp. Chúng tôi có thể đánh giá rằng khối lượng công việc chung của phân bổ giữa cả hai trường hợp sẽ xấp xỉ nhau.
Những gì chúng ta không biết
Chúng tôi không biết sẽ mất bao lâu cho mỗi trường hợp trừ khi chúng tôi đặt bộ đếm và chạy thử nghiệm điểm chuẩn. Tuy nhiên, điểm chuẩn đã được bao gồm từ câu hỏi ban đầu và từ một số câu trả lời cũng như nhận xét; và chúng ta có thể thấy một sự khác biệt đáng kể giữa hai và đây là toàn bộ lý do cho đề xuất này cho vấn đề này.
Hãy điều tra
Rõ ràng là nhiều người đã thực hiện điều này bằng cách xem xét phân bổ heap, kiểm tra điểm chuẩn, xem xét RAM, Cache và tệp trang. Nhìn vào các điểm dữ liệu cụ thể và các chỉ số lặp cụ thể cũng được đưa vào và các cuộc hội thoại khác nhau về vấn đề cụ thể này khiến nhiều người bắt đầu đặt câu hỏi về những điều liên quan khác về nó. Làm thế nào để chúng ta bắt đầu xem xét vấn đề này bằng cách sử dụng các thuật toán toán học và áp dụng một sự tương tự cho nó? Chúng tôi bắt đầu bằng cách đưa ra một vài khẳng định! Sau đó, chúng tôi xây dựng thuật toán của chúng tôi từ đó.
Khẳng định của chúng tôi:
- Chúng ta sẽ để vòng lặp và các vòng lặp của nó là một Tóm tắt bắt đầu từ 1 và kết thúc ở 100000 thay vì bắt đầu bằng 0 như trong các vòng lặp vì chúng ta không cần phải lo lắng về sơ đồ lập chỉ mục 0 của địa chỉ bộ nhớ vì chúng ta chỉ quan tâm đến các thuật toán chính nó.
- Trong cả hai trường hợp, chúng ta có 4 hàm để làm việc và 2 lệnh gọi hàm với 2 thao tác được thực hiện trên mỗi lệnh gọi hàm. Chúng tôi sẽ thiết lập các lên như chức năng và các cuộc gọi đến các chức năng như sau:
F1()
, F2()
, f(a)
, f(b)
, f(c)
và f(d)
.
Các thuật toán:
Trường hợp thứ nhất: - Chỉ có một tổng kết nhưng hai lệnh gọi hàm độc lập.
Sum n=1 : [1,100000] = F1(), F2();
F1() = { f(a) = f(a) + f(b); }
F2() = { f(c) = f(c) + f(d); }
Trường hợp thứ 2: - Hai phép tóm tắt nhưng mỗi phép có hàm gọi riêng.
Sum1 n=1 : [1,100000] = F1();
F1() = { f(a) = f(a) + f(b); }
Sum2 n=1 : [1,100000] = F1();
F1() = { f(c) = f(c) + f(d); }
Nếu bạn nhận thấy F2()
chỉ tồn tại trong Sum
từ Case1
nơi F1()
được chứa trong Sum
từ Case1
và trong cả hai Sum1
và Sum2
từ Case2
. Điều này sẽ được chứng minh sau này khi chúng ta bắt đầu kết luận rằng có một sự tối ưu hóa đang diễn ra trong thuật toán thứ hai.
Các lần lặp thông qua các trường hợp đầu tiên Sum
gọi f(a)
sẽ tự thêm vào nó, f(b)
sau đó nó gọi f(c)
sẽ thực hiện tương tự nhưng thêm f(d)
vào chính nó cho mỗi 100000
lần lặp. Trong trường hợp thứ hai, chúng ta có Sum1
và Sum2
cả hai đều hoạt động giống như thể chúng có cùng chức năng được gọi hai lần liên tiếp.
Trong trường hợp này chúng ta có thể đối xử với Sum1
và Sum2
như chỉ đơn giản cũ Sum
nơi Sum
trong trường hợp này trông như sau: Sum n=1 : [1,100000] { f(a) = f(a) + f(b); }
và bây giờ điều này trông giống như một tối ưu hóa nơi chúng tôi chỉ có thể coi nó là chức năng tương tự.
Tóm tắt với Tương tự
Với những gì chúng ta đã thấy trong trường hợp thứ hai, nó gần như xuất hiện như thể có tối ưu hóa vì cả hai vòng lặp đều có cùng chữ ký chính xác, nhưng đây không phải là vấn đề thực sự. Vấn đề không phải là công việc đang được thực hiện bằng cách f(a)
, f(b)
, f(c)
, và f(d)
. Trong cả hai trường hợp và so sánh giữa hai trường hợp, đó là sự khác biệt về khoảng cách mà Tổng kết phải đi trong mỗi trường hợp mang lại cho bạn sự khác biệt về thời gian thực hiện.
Hãy nghĩ về For Loops
như là Summations
mà không được lặp đi lặp lại như là một Boss
mà ra lệnh cho hai người A
& B
và rằng công việc của họ là để thịt C
& D
tương ứng và chọn một vài gói từ họ và gửi lại. Trong sự tương tự này, các vòng lặp for hoặc lặp tổng và kiểm tra điều kiện tự chúng không thực sự đại diện cho Boss
. Những gì thực sự đại diện Boss
không phải là từ các thuật toán toán học thực tế mà là từ khái niệm thực tế Scope
và Code Block
trong một chương trình con hoặc chương trình con, phương thức, hàm, đơn vị dịch thuật, v.v. Thuật toán đầu tiên có 1 phạm vi trong đó thuật toán thứ 2 có 2 phạm vi liên tiếp.
Trong trường hợp đầu tiên trên mỗi phiếu gọi, Boss
chuyển đến A
và đưa ra lệnh và A
tắt để tìm nạp B's
gói, sau đó Boss
chuyển đến C
và đưa ra các lệnh để thực hiện tương tự và nhận gói từ D
mỗi lần lặp.
Trong trường hợp thứ hai, các Boss
công việc trực tiếp với A
đi và tìm nạp B's
gói cho đến khi tất cả các gói được nhận. Sau đó, các Boss
công việc C
để làm tương tự để có được tất cả các D's
gói.
Vì chúng tôi đang làm việc với một con trỏ 8 byte và xử lý phân bổ heap, hãy xem xét vấn đề sau. Hãy nói rằng Boss
100 feet từ A
và A
500 feet từ C
. Chúng ta không cần phải lo lắng về việc Boss
ban đầu từ bao xa C
vì thứ tự thực hiện. Trong cả hai trường hợp, Boss
ban đầu đi từ A
đầu tiên sau đó đến B
. Sự tương tự này không phải để nói rằng khoảng cách này là chính xác; nó chỉ là một kịch bản trường hợp thử nghiệm hữu ích để hiển thị hoạt động của các thuật toán.
Trong nhiều trường hợp khi thực hiện phân bổ heap và làm việc với các tệp bộ đệm và trang, các khoảng cách giữa các vị trí địa chỉ có thể không thay đổi nhiều hoặc chúng có thể thay đổi đáng kể tùy thuộc vào bản chất của các loại dữ liệu và kích thước mảng.
Các trường hợp thử nghiệm:
Trường hợp đầu tiên: Trong lần lặp đầu tiên,Boss
ban đầu phải đi 100 feet để đưa ra lệnh trượtA
vàA
tắt và thực hiện công việc của mình, nhưng sau đóBoss
phải đi 500 feetC
để đưa cho anh ta phiếu trượt. Sau đó, ở lần lặp tiếp theo và mỗi lần lặp khác sau khiBoss
phải quay đi quay lại 500 feet giữa hai lần.
Thứ hai trường hợp: CácBoss
có để đi du lịch 100 feet trên phiên đầu tiên đểA
, nhưng sau đó, ông đã có và chỉ cần chờ đợiA
để có được trở lại cho đến khi tất cả phiếu được lấp đầy. Sau đó,Boss
phải di chuyển 500 feet trong lần lặp đầu tiên đếnC
vì cáchC
500 feetA
. Vì điều nàyBoss( Summation, For Loop )
đang được gọi ngay sau khi làm việc vớiA
anh ta, sau đó chỉ chờ ở đó như anh ta đã làmA
cho đến khi tất cả cácC's
phiếu đặt hàng được thực hiện.
Sự khác biệt về khoảng cách di chuyển
const n = 100000
distTraveledOfFirst = (100 + 500) + ((n-1)*(500 + 500);
// Simplify
distTraveledOfFirst = 600 + (99999*100);
distTraveledOfFirst = 600 + 9999900;
distTraveledOfFirst = 10000500;
// Distance Traveled On First Algorithm = 10,000,500ft
distTraveledOfSecond = 100 + 500 = 600;
// Distance Traveled On Second Algorithm = 600ft;
So sánh các giá trị tùy ý
Chúng ta có thể dễ dàng thấy rằng 600 là ít hơn 10 triệu. Bây giờ, điều này không chính xác, bởi vì chúng tôi không biết sự khác biệt thực tế về khoảng cách giữa địa chỉ RAM hoặc từ Bộ nhớ cache hoặc Tệp trang mà mỗi cuộc gọi trên mỗi lần lặp sẽ do nhiều biến không nhìn thấy khác. Đây chỉ là một đánh giá về tình huống cần nhận thức và xem xét nó từ tình huống xấu nhất.
Từ những con số này, nó gần như sẽ xuất hiện như thể Thuật toán Một phải 99%
chậm hơn Thuật toán Hai; Tuy nhiên, đây chỉ là Boss's
một phần hoặc trách nhiệm của các thuật toán và nó không chiếm người lao động thực tế A
, B
, C
, & D
và những gì họ phải làm trên mỗi và mỗi lần lặp của vòng lặp. Vì vậy, công việc của ông chủ chỉ chiếm khoảng 15 - 40% tổng số công việc đang làm. Phần lớn công việc được thực hiện thông qua các công nhân có tác động lớn hơn một chút đối với việc giữ tỷ lệ chênh lệch tốc độ ở mức khoảng 50-70%
Quan sát: - Sự khác biệt giữa hai thuật toán
Trong tình huống này, nó là cấu trúc của quá trình công việc đang được thực hiện. Nó cho thấy rằng Trường hợp 2 hiệu quả hơn từ cả tối ưu hóa một phần của việc có một khai báo và định nghĩa hàm tương tự trong đó chỉ có các biến khác nhau theo tên và khoảng cách di chuyển.
Chúng ta cũng thấy rằng tổng quãng đường di chuyển trong Trường hợp 1 xa hơn nhiều so với Trường hợp 2 và chúng ta có thể xem xét khoảng cách này đã di chuyển Yếu tố thời gian của chúng ta giữa hai thuật toán. Trường hợp 1 có nhiều việc phải làm hơn trường hợp 2 .
Điều này có thể quan sát được từ các bằng chứng của các ASM
hướng dẫn đã được thể hiện trong cả hai trường hợp. Cùng với những gì đã được nêu về các trường hợp này, điều này không giải thích cho thực tế là trong trường hợp 1 , ông chủ sẽ phải chờ cả hai A
và C
quay lại trước khi anh ta có thể quay lại A
lần nữa cho mỗi lần lặp. Điều này cũng không giải thích cho thực tế rằng nếu A
hoặc B
mất một thời gian cực kỳ dài thì cả Boss
(các) công nhân khác đều đang chờ để được thực thi.
Trong trường hợp 2, người duy nhất không hoạt động là Boss
cho đến khi công nhân trở lại. Vì vậy, ngay cả điều này có tác động đến thuật toán.
Các câu hỏi sửa đổi của OP
EDIT: Câu hỏi hóa ra không liên quan, vì hành vi phụ thuộc rất nhiều vào kích thước của mảng (n) và bộ đệm CPU. Vì vậy, nếu có thêm sự quan tâm, tôi viết lại câu hỏi:
Bạn có thể cung cấp một số cái nhìn sâu sắc về các chi tiết dẫn đến các hành vi bộ đệm khác nhau như được minh họa bởi năm vùng trên biểu đồ sau?
Cũng có thể thú vị khi chỉ ra sự khác biệt giữa các kiến trúc CPU / bộ đệm, bằng cách cung cấp một biểu đồ tương tự cho các CPU này.
Về những câu hỏi này
Như tôi đã chứng minh mà không nghi ngờ gì, có một vấn đề tiềm ẩn ngay cả trước khi Phần cứng và Phần mềm có liên quan.
Bây giờ đối với việc quản lý bộ nhớ và bộ nhớ đệm cùng với các tệp trang, v.v ... tất cả đều hoạt động cùng nhau trong một bộ hệ thống tích hợp giữa các mục sau:
The Architecture
{Phần cứng, phần sụn, một số Trình điều khiển nhúng, Bộ hướng dẫn hạt nhân và ASM}.
The OS
{Hệ thống quản lý tập tin và bộ nhớ, trình điều khiển và sổ đăng ký}.
The Compiler
{Đơn vị dịch thuật và tối ưu hóa mã nguồn}.
- Và ngay cả
Source Code
chính nó với (các) thuật toán đặc biệt của nó.
Chúng tôi đã có thể thấy rằng có một nút cổ chai mà đang xảy ra trong thuật toán đầu tiên trước khi chúng tôi thậm chí áp dụng nó vào máy tính bất kỳ với bất kỳ tùy ý Architecture
, OS
và Programmable Language
so với các thuật toán thứ hai. Đã tồn tại một vấn đề trước khi liên quan đến nội tại của một máy tính hiện đại.
Kết quả cuối cùng
Tuy nhiên; không phải để nói rằng những câu hỏi mới này không quan trọng bởi vì chính chúng và chúng thực sự đóng một vai trò. Chúng có tác động đến các quy trình và hiệu suất tổng thể và điều đó thể hiện rõ qua các biểu đồ và đánh giá khác nhau từ nhiều người đã đưa ra câu trả lời và hoặc nhận xét của họ.
Nếu bạn chú ý đến sự tương tự của Boss
và hai công nhân A
& B
những người phải đi và lấy các gói từ C
& D
tương ứng và xem xét các ký hiệu toán học của hai thuật toán được đề cập; bạn có thể nhìn thấy mà không có sự tham gia của các phần cứng máy tính và phần mềm Case 2
là xấp xỉ 60%
nhanh hơn Case 1
.
Khi bạn nhìn vào biểu đồ và biểu đồ sau khi các thuật toán này được áp dụng cho một số mã nguồn, được biên dịch, tối ưu hóa và được thực thi thông qua HĐH để thực hiện các hoạt động của chúng trên một phần cứng nhất định, bạn thậm chí có thể thấy sự xuống cấp hơn một chút giữa các khác biệt trong các thuật toán này.
Nếu Data
bộ này khá nhỏ, ban đầu nó có vẻ không tệ lắm. Tuy nhiên, vì Case 1
là về 60 - 70%
chậm hơn so với Case 2
chúng ta có thể nhìn vào sự phát triển của chức năng này về sự khác biệt trong hành thời gian:
DeltaTimeDifference approximately = Loop1(time) - Loop2(time)
//where
Loop1(time) = Loop2(time) + (Loop2(time)*[0.6,0.7]) // approximately
// So when we substitute this back into the difference equation we end up with
DeltaTimeDifference approximately = (Loop2(time) + (Loop2(time)*[0.6,0.7])) - Loop2(time)
// And finally we can simplify this to
DeltaTimeDifference approximately = [0.6,0.7]*Loop2(time)
Giá trị gần đúng này là sự khác biệt trung bình giữa hai vòng lặp cả về hoạt động của thuật toán và máy liên quan đến tối ưu hóa phần mềm và hướng dẫn máy.
Khi tập dữ liệu phát triển tuyến tính, sự khác biệt về thời gian giữa hai dữ liệu. Thuật toán 1 có nhiều lần tìm nạp hơn thuật toán 2, điều này thể hiện rõ khi Boss
phải di chuyển qua lại khoảng cách tối đa giữa A
& C
cho mỗi lần lặp sau lần lặp đầu tiên trong khi Thuật toán 2 Boss
phải di chuyển đến A
một lần và sau khi thực hiện xong A
. một khoảng cách tối đa chỉ một lần khi đi từ A
đến C
.
Cố gắng Boss
tập trung làm hai việc tương tự cùng một lúc và tung hứng chúng qua lại thay vì tập trung vào các nhiệm vụ liên tiếp tương tự sẽ khiến anh ấy khá tức giận vào cuối ngày kể từ khi anh ấy phải đi lại và làm việc gấp đôi. Do đó, đừng để mất phạm vi của tình huống bằng cách để sếp của bạn rơi vào tình trạng tắc nghẽn nội suy vì vợ / chồng và con cái của sếp sẽ không đánh giá cao điều đó.
Sửa đổi: Nguyên tắc thiết kế kỹ thuật phần mềm
- Sự khác biệt giữa Local Stack
và Heap Allocated
tính toán trong vòng lặp cho các vòng lặp và sự khác biệt giữa công dụng, hiệu quả và hiệu quả của chúng -
Thuật toán toán học mà tôi đề xuất ở trên chủ yếu áp dụng cho các vòng lặp thực hiện các hoạt động trên dữ liệu được phân bổ trên heap.
- Hoạt động ngăn xếp liên tiếp:
- Nếu các vòng lặp đang thực hiện các thao tác trên dữ liệu cục bộ trong một khối mã hoặc phạm vi trong khung ngăn xếp thì nó vẫn sẽ được áp dụng, nhưng các vị trí bộ nhớ gần hơn nhiều trong đó chúng thường tuần tự và chênh lệch về thời gian di chuyển hoặc thời gian thực hiện gần như không đáng kể Vì không có phân bổ nào được thực hiện trong heap, bộ nhớ không bị phân tán và bộ nhớ sẽ không được tải qua ram. Bộ nhớ thường tuần tự và liên quan đến khung ngăn xếp và con trỏ ngăn xếp.
- Khi các hoạt động liên tiếp đang được thực hiện trên ngăn xếp, Bộ xử lý hiện đại sẽ lưu trữ các giá trị lặp lại và địa chỉ giữ các giá trị này trong các thanh ghi bộ đệm cục bộ. Thời gian hoạt động hoặc hướng dẫn ở đây là theo thứ tự nano giây.
- Các hoạt động phân bổ liên tiếp của Heap:
- Khi bạn bắt đầu áp dụng phân bổ heap và bộ xử lý phải tìm nạp các địa chỉ bộ nhớ trong các cuộc gọi liên tiếp, tùy thuộc vào kiến trúc của CPU, Bộ điều khiển Bus và các mô-đun Ram, thời gian hoạt động hoặc thực thi có thể theo thứ tự micro mili giây. So với các hoạt động ngăn xếp được lưu trữ, chúng khá chậm.
- CPU sẽ phải tìm nạp địa chỉ bộ nhớ từ Ram và thông thường mọi thứ trên bus hệ thống đều chậm so với đường dẫn dữ liệu bên trong hoặc bus dữ liệu trong chính CPU.
Vì vậy, khi bạn đang làm việc với dữ liệu cần có trong heap và bạn đang duyệt qua chúng trong các vòng lặp, sẽ hiệu quả hơn khi giữ mỗi bộ dữ liệu và các thuật toán tương ứng của nó trong vòng lặp riêng của nó. Bạn sẽ có được sự tối ưu hóa tốt hơn so với việc cố gắng tạo ra các vòng lặp liên tiếp bằng cách đặt nhiều hoạt động của các tập dữ liệu khác nhau trên heap vào một vòng lặp.
Bạn có thể thực hiện việc này với dữ liệu trên ngăn xếp vì chúng thường được lưu trong bộ nhớ cache, nhưng không phải đối với dữ liệu phải có địa chỉ bộ nhớ của nó truy vấn mỗi lần lặp.
Đây là lúc Kỹ thuật phần mềm và Thiết kế kiến trúc phần mềm phát huy tác dụng. Đó là khả năng biết cách sắp xếp dữ liệu của bạn, biết khi nào nên lưu trữ dữ liệu của bạn, biết khi nào phân bổ dữ liệu của bạn trên heap, biết cách thiết kế và thực hiện các thuật toán của bạn và biết khi nào và ở đâu để gọi chúng.
Bạn có thể có cùng một thuật toán liên quan đến cùng một tập dữ liệu, nhưng bạn có thể muốn một thiết kế triển khai cho biến thể ngăn xếp của nó và một thuật toán khác cho biến thể được phân bổ heap của nó chỉ vì vấn đề nêu trên do sự O(n)
phức tạp của thuật toán khi làm việc với đống.
Từ những gì tôi nhận thấy trong nhiều năm qua, nhiều người không xem xét thực tế này. Họ sẽ có xu hướng thiết kế một thuật toán hoạt động trên một tập dữ liệu cụ thể và họ sẽ sử dụng nó bất kể tập dữ liệu nào được lưu trữ cục bộ trên ngăn xếp hoặc nếu nó được phân bổ trên heap.
Nếu bạn muốn tối ưu hóa thực sự, vâng, nó có vẻ giống như sao chép mã, nhưng để khái quát hóa, sẽ có hiệu quả hơn khi có hai biến thể của cùng một thuật toán. Một cho các hoạt động ngăn xếp, và một cho các hoạt động heap được thực hiện trong các vòng lặp!
Đây là một ví dụ giả: Hai cấu trúc đơn giản, một thuật toán.
struct A {
int data;
A() : data{0}{}
A(int a) : data{a}{}
};
struct B {
int data;
B() : data{0}{}
A(int b) : data{b}{}
}
template<typename T>
void Foo( T& t ) {
// do something with t
}
// some looping operation: first stack then heap.
// stack data:
A dataSetA[10] = {};
B dataSetB[10] = {};
// For stack operations this is okay and efficient
for (int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]);
Foo(dataSetB[i]);
}
// If the above two were on the heap then performing
// the same algorithm to both within the same loop
// will create that bottleneck
A* dataSetA = new [] A();
B* dataSetB = new [] B();
for ( int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]); // dataSetA is on the heap here
Foo(dataSetB[i]); // dataSetB is on the heap here
} // this will be inefficient.
// To improve the efficiency above, put them into separate loops...
for (int i = 0; i < 10; i++ ) {
Foo(dataSetA[i]);
}
for (int i = 0; i < 10; i++ ) {
Foo(dataSetB[i]);
}
// This will be much more efficient than above.
// The code isn't perfect syntax, it's only psuedo code
// to illustrate a point.
Đây là những gì tôi đã đề cập bằng cách có các triển khai riêng cho các biến thể ngăn xếp so với các biến thể heap. Các thuật toán tự nó không quan trọng quá nhiều, đó là các cấu trúc lặp mà bạn sẽ sử dụng chúng trong đó.