Tôi muốn hỏi những người có kinh nghiệm làm việc với các hệ thống quy mô của Visual Studio: điều gì khiến họ chậm chạp? Đây có phải là các lớp dựa trên các lớp trừu tượng cần thiết để giữ cơ sở mã hóa trong khả năng hiểu của con người? Đây có phải là số lượng mã cần phải được chạy qua không? Có phải đó là xu hướng hiện đại đối với các phương pháp tiết kiệm thời gian của lập trình viên với chi phí (rất lớn) trong bộ phận sử dụng bộ nhớ / chu kỳ đồng hồ?
Tôi nghĩ rằng bạn đã đoán được một số trong số họ nhưng tôi muốn đưa ra những gì tôi coi là yếu tố lớn nhất, đã làm việc trên một cơ sở mã lớn tương đối (không chắc là nó có lớn như Visual Studio không - nằm trong hàng triệu dòng mã loại và khoảng một nghìn plugin) trong khoảng 10 năm và quan sát hiện tượng xảy ra.
Nó cũng ít gây tranh cãi hơn vì nó không đi sâu vào API hoặc các tính năng ngôn ngữ hoặc bất cứ thứ gì tương tự. Những chi phí liên quan đến "chi phí" có thể tạo ra một cuộc tranh luận thay vì "chi tiêu" và tôi muốn tập trung vào "chi tiêu".
Phối hợp lỏng lẻo và di sản
Những gì tôi quan sát được là sự phối hợp lỏng lẻo và một di sản dài có xu hướng dẫn đến rất nhiều chất thải tích lũy.
Ví dụ, tôi tìm thấy khoảng một trăm cấu trúc tăng tốc trong cơ sở mã này, nhiều trong số chúng là dự phòng.
Chúng ta muốn có một cây KD để tăng tốc một động cơ vật lý, một cây khác cho một động cơ vật lý mới thường chạy song song với động cơ cũ, chúng ta có hàng tá các phép thực hiện cho các thuật toán lưới khác nhau, một cây KD khác để kết xuất , hái, v.v ... Đây đều là những cấu trúc cây to, cồng kềnh được sử dụng để tăng tốc tìm kiếm. Mỗi cá nhân có thể mất hàng trăm megabyte đến gigabyte bộ nhớ cho đầu vào có kích thước rất trung bình. Chúng không phải lúc nào cũng được khởi tạo và sử dụng mọi lúc, nhưng tại bất kỳ thời điểm nào, 4 hoặc 5 trong số chúng có thể nằm trong bộ nhớ cùng một lúc.
Bây giờ tất cả những thứ này đang lưu trữ cùng một dữ liệu để tăng tốc tìm kiếm cho chúng. Bạn có thể tưởng tượng nó giống như cơ sở dữ liệu cũ tương tự lưu trữ tất cả các trường của nó thành 20 bản đồ / từ điển dự phòng / cây B + dự phòng khác nhau cùng một lúc, được tổ chức giống hệt nhau và tìm kiếm tất cả chúng. Bây giờ chúng tôi đang chiếm 20 lần bộ nhớ và xử lý.
Ngoài ra, vì sự dư thừa, có ít thời gian để tối ưu hóa bất kỳ một trong số chúng với thẻ giá bảo trì đi kèm với điều đó, và ngay cả khi chúng tôi đã làm, nó sẽ chỉ có 5% hiệu quả lý tưởng.
Điều gì gây ra hiện tượng này? Phối hợp lỏng lẻo là nguyên nhân số một tôi thấy. Rất nhiều thành viên trong nhóm thường làm việc trong các hệ sinh thái biệt lập của họ, phát triển hoặc sử dụng cấu trúc dữ liệu của bên thứ ba, nhưng không sử dụng cùng cấu trúc mà các thành viên khác trong nhóm đang sử dụng ngay cả khi họ hoàn toàn trùng lặp với những mối quan tâm chính xác.
Điều gì gây ra hiện tượng này để tồn tại? Di sản và khả năng tương thích là nguyên nhân số một tôi thấy. Vì chúng tôi đã trả chi phí để thực hiện các cấu trúc dữ liệu này và một lượng lớn mã phụ thuộc vào các giải pháp này, nên thường rất rủi ro khi cố gắng hợp nhất chúng thành ít cấu trúc dữ liệu hơn. Mặc dù nhiều cấu trúc dữ liệu này rất dư thừa về mặt khái niệm, chúng không phải lúc nào cũng giống hệt nhau trong các thiết kế giao diện của chúng. Vì vậy, thay thế chúng sẽ là một thay đổi lớn, đầy rủi ro thay vì chỉ để chúng tiêu tốn bộ nhớ và thời gian xử lý.
Hiệu quả bộ nhớ
Thông thường, việc sử dụng bộ nhớ và tốc độ có xu hướng liên quan ở mức độ lớn nhất. Bạn thường có thể phát hiện ra phần mềm chậm bằng cách nó chiếm dụng bộ nhớ. Không phải lúc nào cũng có nhiều bộ nhớ dẫn đến chậm, vì vấn đề quan trọng là bộ nhớ "nóng" (bộ nhớ nào đang được truy cập mọi lúc - nếu một chương trình sử dụng một bộ nhớ thuyền nhưng chỉ sử dụng 1 megabyte thời gian, nó không phải là một vấn đề lớn về tốc độ).
Vì vậy, bạn có thể phát hiện ra những con lợn tiềm năng dựa trên việc sử dụng bộ nhớ rất nhiều thời gian. Nếu một ứng dụng cần hàng chục đến hàng trăm megabyte bộ nhớ khi khởi động, có lẽ nó sẽ không hiệu quả lắm. Hàng chục megabyte có vẻ nhỏ khi chúng ta có hàng gigabyte DRAM ngày nay, nhưng bộ nhớ CPU lớn nhất và chậm nhất vẫn nằm trong phạm vi megabyte, và nhanh nhất vẫn nằm trong phạm vi kilobyte. Kết quả là, một chương trình sử dụng 20 megabyte chỉ để khởi động và không làm gì thực sự vẫn sử dụng khá nhiều "bộ nhớ" từ quan điểm bộ nhớ cache của CPU phần cứng, đặc biệt là nếu tất cả 20 megabyte bộ nhớ đó sẽ được truy cập liên tục và thường xuyên như chương trình đang chạy.
Giải pháp
Đối với tôi, giải pháp là tìm kiếm các nhóm nhỏ hơn, phối hợp hơn để xây dựng sản phẩm, những nhóm có thể theo dõi "chi tiêu" của họ và tránh "mua" các mặt hàng tương tự lặp đi lặp lại.
Giá cả
Tôi sẽ nhúng vào khía cạnh "chi phí" gây tranh cãi hơn chỉ là một chút tuổi teen với hiện tượng "chi tiêu" mà tôi đã quan sát thấy. Nếu một ngôn ngữ kết thúc với một mức giá không thể tránh khỏi cho một đối tượng (như một ngôn ngữ cung cấp sự phản ánh thời gian chạy và không thể buộc phân bổ liên tục cho một loạt các đối tượng), thì thẻ giá đó chỉ đắt trong bối cảnh của một yếu tố rất chi tiết, như một đơn Pixel
hoặc Boolean
.
Tuy nhiên, tôi thấy rất nhiều mã nguồn cho các chương trình xử lý một tải nặng (ví dụ: xử lý hàng trăm nghìn đến hàng triệu Pixel
hoặc các Boolean
trường hợp) trả chi phí đó ở mức chi tiết như vậy.
Lập trình hướng đối tượng có thể làm trầm trọng thêm điều đó. Tuy nhiên, đó không phải là chi phí của "đối tượng" mỗi lần hoặc thậm chí OOP có lỗi, chỉ đơn giản là các chi phí đó được trả ở mức độ chi tiết của một yếu tố tuổi teen sẽ được hàng triệu người khởi tạo.
Vì vậy, đó là hiện tượng "chi phí" và "chi tiêu" khác mà tôi đang quan sát. Chi phí là một xu, nhưng đồng xu sẽ tăng thêm nếu chúng ta mua riêng một triệu lon soda thay vì đàm phán với nhà sản xuất để mua số lượng lớn.
Giải pháp ở đây với tôi là mua hàng "số lượng lớn". Các đối tượng hoàn toàn ổn ngay cả trong các ngôn ngữ có một số giá từng đồng xu với điều kiện là chi phí này không được trả riêng một triệu lần so với tương đương với một lon soda.
Tối ưu hóa sớm
Tôi chưa bao giờ thích từ ngữ mà Knuth sử dụng ở đây, bởi vì "tối ưu hóa sớm" hiếm khi làm cho các chương trình sản xuất trong thế giới thực diễn ra nhanh hơn. Một số người giải thích rằng "tối ưu hóa sớm" khi Knuth có nghĩa giống như "tối ưu hóa mà không có kiến thức / kinh nghiệm phù hợp để biết tác động thực sự của nó đối với phần mềm". Nếu bất cứ điều gì, hiệu quả thực tế của tối ưu hóa sớm thực sự thường sẽ làm cho phần mềm chậm hơn , vì sự xuống cấp trong khả năng bảo trì có nghĩa là có ít thời gian để tối ưu hóa các đường dẫn quan trọng thực sự quan trọng .
Đây là hiện tượng cuối cùng tôi quan sát thấy, nơi các nhà phát triển tìm cách tiết kiệm từng đồng xu khi mua một lon soda, không bao giờ được mua nữa, hoặc tệ hơn, một ngôi nhà, đã lãng phí tất cả thời gian của họ (hoặc tệ hơn, đồng xu tưởng tượng từ không hiểu trình biên dịch của họ hoặc kiến trúc của phần cứng) khi có hàng tỷ đô la bị lãng phí chi tiêu ở nơi khác.
Thời gian là rất hữu hạn vì vậy cố gắng tối ưu hóa tuyệt đối mà không có thông tin theo ngữ cảnh phù hợp thường làm mất cơ hội tối ưu hóa những nơi thực sự quan trọng, và do đó, về mặt hiệu quả thực tế, tôi sẽ nói rằng "tối ưu hóa sớm làm cho phần mềm chậm hơn nhiều. "
Vấn đề là có những kiểu nhà phát triển sẽ lấy những gì tôi đã viết ở trên về các đối tượng và cố gắng thiết lập một tiêu chuẩn mã hóa cấm lập trình hướng đối tượng hoặc một cái gì đó điên rồ kiểu đó. Tối ưu hóa hiệu quả là ưu tiên hiệu quả và hoàn toàn vô giá trị nếu chúng ta chìm đắm trong một biển các vấn đề bảo trì.