2015-03-07 41 views
11

Tôi có một số tệp cpp mất nhiều thời gian để biên dịch. Chúng chứa một số lớp/mã cơ bản, với một số mẫu, nhưng không có gì để biện minh cho thời gian biên dịch theo thứ tự của hàng chục giây.Xác định chức năng biên dịch chậm

tôi sử dụng một vài libs bên ngoài (tăng/opencv)

Đây là những gì gcc nói về thời gian biên dịch. Làm thế nào tôi có thể tìm thấy thư viện/bao gồm/chức năng gọi đó là để đổ lỗi cho thời gian biên dịch khủng khiếp?

Execution times (seconds) 
phase setup    : 0.00 (0%) usr 0.00 (0%) sys 0.01 (0%) wall 1445 kB (0%) ggc 
phase parsing   : 6.69 (46%) usr 1.61 (60%) sys 12.14 (47%) wall 488430 kB (66%) ggc 
phase lang. deferred : 1.59 (11%) usr 0.36 (13%) sys 3.83 (15%) wall 92964 kB (13%) ggc 
phase opt and generate : 6.25 (43%) usr 0.72 (27%) sys 10.09 (39%) wall 152799 kB (21%) ggc 
|name lookup   : 1.05 (7%) usr 0.28 (10%) sys 2.01 (8%) wall 52063 kB (7%) ggc 
|overload resolution : 0.83 (6%) usr 0.18 (7%) sys 1.48 (6%) wall 42377 kB (6%) ggc 
... 

Profiling the C++ compilation process giao dịch với việc xác định các tập tin chậm, nhưng tôi cần thông tin nhiều hơn hạt mịn để tìm ra thủ phạm

(file khác/dự án biên dịch trong mili giây/giây, vì vậy nó không phải là một vấn đề Tôi sử dụng gcc 4.9.1)

+2

Tìm kiếm nhị phân tệp bằng cách sử dụng '#if 0'? Tức là, đừng biên dịch nửa sau. Nếu bây giờ nhanh, đừng biên dịch lại quý sau. Nếu thay vào đó vẫn còn chậm, không biên dịch lại 3/4. Nếu mã của bạn có 1024 chức năng (!), Vấn đề được giải quyết trong 10 lần lặp (nếu tệp của bạn có 1024 chức năng, có thể kích thước tệp là sự cố). – Yakk

+2

@Yakk: Có lẽ không dễ dàng như vậy. Nửa sau có thể không biên dịch được nếu không có phông chữ. Hơn nữa, nếu mã sử dụng nhiều mẫu, thời gian dài có thể là do sự tương tác giữa hai nửa. – rodrigo

+1

@rodrigo Có, nó là đơn giản: nó xuất hiện bạn dự một thuật toán khác nhau vào mô tả của tôi. Tôi chỉ đề xuất biên dịch tiền tố của tập tin - bạn có thể nhận được lỗi liên kết, nhưng không phải lỗi trình biên dịch. Và đoạn mã đầu tiên gây ra tương tác làm tăng thời gian biên dịch sẽ là đoạn mã được tìm thấy (đoạn "cạnh" của mã, trong đó bao gồm nó làm cho thời gian nổ tung, và loại bỏ nó giữ một thời gian hợp lý). Tất nhiên, nó không giải quyết được vấn đề mà theo đó vấn đề là từ một khoản tiền lớn nhỏ, hoặc một khoản tiền nhỏ lớn, nhưng điều đó có xu hướng hiếm hơn. – Yakk

Trả lời

14

Về cơ bản có hai thứ gây ra thời gian biên dịch dài: quá nhiều bao gồm và quá nhiều mẫu.

Khi bạn bao gồm quá nhiều tiêu đề và các tiêu đề này bao gồm quá nhiều tiêu đề, điều đó có nghĩa là trình biên dịch có rất nhiều việc phải làm để tải tất cả các tệp này và nó sẽ chi tiêu một số tiền vô hạn thời gian trên quá trình xử lý mà nó phải thực hiện trên tất cả các mã, bất kể nó có thực sự được sử dụng hay không, như xử lý trước, phân tích từ vựng, xây dựng AST, v.v. Điều này có thể đặc biệt có vấn đề khi mã được trải rộng trên một số lượng lớn của các tiêu đề nhỏ, bởi vì hiệu suất là rất nhiều I/O bị ràng buộc (rất nhiều thời gian lãng phí chỉ lấy và đọc các tập tin từ đĩa cứng). Thật không may, thư viện Boost có xu hướng được cấu trúc rất nhiều theo cách này.

Dưới đây là một vài cách hoặc các công cụ để giải quyết vấn đề này:

  • Bạn có thể sử dụng "include-what-you-use" công cụ. Đây là công cụ phân tích dựa trên Clang về cơ bản nhìn vào những gì bạn đang thực sự sử dụng trong mã của bạn, và tiêu đề những thứ đó xuất phát từ đó, và sau đó báo cáo về bất kỳ tối ưu tiềm năng nào bạn có thể thực hiện bằng cách loại bỏ các gói không cần thiết nhất định hoặc có thể thay thế các tiêu đề "tất cả trong một" rộng hơn bằng các tiêu đề chi tiết hơn.
  • Hầu hết các trình biên dịch có tùy chọn để đổ các nguồn đã qua xử lý (trên GCC/Clang, đó là -E hoặc -E -P tùy chọn hoặc đơn giản sử dụng chương trình tiền xử lý C của GCC cpp trực tiếp). Bạn có thể lấy tệp nguồn của mình và nhận xét các câu lệnh include hoặc nhóm gồm các câu lệnh include và đổ nguồn đã xử lý trước để xem tổng số mã mà các tiêu đề khác nhau kéo vào (và có thể sử dụng một lệnh đếm dòng, như $ g++ -E -P my_source.cpp | wc -l). Điều này có thể giúp bạn xác định, trong số lượng tuyệt đối của dòng mã để xử lý, tiêu đề nào là những kẻ phạm tội tồi tệ nhất. Sau đó, bạn có thể thấy những gì bạn có thể làm để tránh chúng hoặc giảm thiểu vấn đề bằng cách nào đó.
  • Bạn cũng có thể sử dụng các tiêu đề được biên dịch trước. Đây là một tính năng được hỗ trợ bởi hầu hết các trình biên dịch mà bạn có thể chỉ định các tiêu đề nhất định (đặc biệt là các tiêu đề "tất cả trong một") được biên dịch trước để tránh phân tích lại chúng cho mọi tệp nguồn bao gồm chúng.
  • Nếu hệ điều hành của bạn hỗ trợ nó, bạn có thể sử dụng ram-disk cho mã của bạn và tiêu đề của thư viện bên ngoài của bạn. Điều này về cơ bản chiếm một phần bộ nhớ RAM của bạn và làm cho nó trông giống như một đĩa cứng/tập tin hệ thống bình thường. Điều này có thể làm giảm đáng kể thời gian biên dịch bằng cách giảm độ trễ I/O, vì tất cả các tiêu đề và tệp nguồn được đọc từ bộ nhớ RAM thay vì đĩa cứng thực tế.

Vấn đề thứ hai là sự kiện mẫu. Trong báo cáo thời gian của bạn từ GCC, cần có một giá trị thời gian được báo cáo ở đâu đó cho giai đoạn tạo mẫu. Nếu con số đó cao, nó sẽ là ngay sau khi có bất kỳ số lượng đáng kể các mẫu meta-lập trình tham gia vào mã, sau đó bạn sẽ cần phải làm việc về vấn đề đó. Có rất nhiều lý do tại sao một số mã mẫu nặng có thể chậm chạp để biên dịch, bao gồm các mẫu diễn đạt đệ quy sâu sắc, thủ thuật Sfinae quá mức, lạm dụng các đặc điểm kiểu và kiểm tra khái niệm và mã chung cũ được thiết kế tốt. Nhưng cũng có những thủ thuật đơn giản có thể khắc phục rất nhiều vấn đề, như sử dụng không gian tên chưa được đặt tên (để tránh tất cả thời gian lãng phí khi tạo biểu tượng cho các cảnh báo không thực sự cần nhìn thấy bên ngoài đơn vị dịch). kiểm tra các mẫu (về cơ bản là "ngắn mạch" phần lớn các lập trình meta ưa thích đi vào chúng). Một giải pháp tiềm năng khác cho instantiations mẫu là sử dụng "extern templates" (từ C++ 11) để kiểm soát nơi instantiated mẫu cụ thể nên được khởi tạo (ví dụ: trong tệp cpp riêng) và tránh khởi tạo lại ở mọi nơi được sử dụng.

Dưới đây là một vài cách hoặc các công cụ để giúp bạn xác định tắc nghẽn:

  • Bạn có thể sử dụng "Templight" công cụ profiling (và phụ trợ của nó "Templight-tools" để đối phó với những dấu vết). Đây là một công cụ dựa trên Clang, có thể được sử dụng như một trình thay thế drop-in cho trình biên dịch Clang (công cụ này thực sự là một trình biên dịch đầy đủ) và nó sẽ tạo ra một hồ sơ hoàn chỉnh của tất cả các mẫu instantiations xảy ra trong quá trình biên dịch , bao gồm cả thời gian dành cho mỗi (và tùy chọn, ước lượng mức tiêu thụ bộ nhớ, mặc dù điều này sẽ ảnh hưởng đến các giá trị thời gian). Các dấu vết sau đó có thể được chuyển đổi sang một định dạng Callgrind và được hiển thị trong KCacheGrind, chỉ cần đọc mô tả về điều đó trên templight-tools page. Về cơ bản, điều này về cơ bản có thể được sử dụng như một trình thông báo thời gian chạy điển hình, nhưng để lược tả thời gian và mức tiêu thụ bộ nhớ khi biên dịch mã mẫu nặng.
  • Một cách thô sơ hơn về việc tìm kiếm những kẻ phạm tội tồi tệ nhất là tạo các tệp nguồn thử nghiệm để khởi tạo các mẫu cụ thể mà bạn nghi ngờ chịu trách nhiệm về thời gian biên dịch dài. Sau đó, bạn biên dịch những tập tin đó, thời gian và cố gắng làm việc theo cách của bạn (có thể trong một "thời trang tìm kiếm nhị phân") đối với những kẻ phạm tội tồi tệ nhất.

Nhưng ngay cả với các thủ thuật này, việc xác định các nút cổ chai tức thì mẫu dễ hơn là thực sự giải quyết chúng. Thật là may mắn vì điều đó.

1

Điều này không thể trả lời đầy đủ nếu không có thông tin về cách các tệp nguồn của bạn được tổ chức và xây dựng, do đó, chỉ cần một số quan sát chung.

  1. Bản mẫu có thể tăng thời gian biên dịch rất nhiều, đặc biệt nếu các mẫu phức tạp được khởi tạo cho nhiều loại/tham số khác nhau trong mỗi tệp nguồn. Lược đồ cho việc khởi tạo mẫu rõ ràng (tức là đảm bảo các mẫu chỉ được khởi tạo trong một vài tệp nguồn thay vì tất cả chúng) có thể giảm thời gian biên dịch trong các trường hợp như vậy (cũng như thời gian liên kết và kích thước tệp thực thi). Bạn cần phải đọc tài liệu biên dịch để biết cách làm điều này - nó không nhất thiết xảy ra theo mặc định và có thể có nghĩa là tái cơ cấu mã của bạn để hỗ trợ nó.
  2. Tệp tiêu đề là #include d trong nhiều tệp nguồn, dù cần hay không, có xu hướng tăng thời gian biên dịch. Tôi thấy một trường hợp một thành viên trong nhóm viết "globals.h" rằng mọi thứ và #include d ở mọi nơi - và thời gian xây dựng (trong một dự án lớn) đã được tăng lên theo thứ tự độ lớn.Đó là một whammy đôi - thời gian biên dịch của mỗi tập tin nguồn được tăng lên, và đó là nhân với số lượng các tập tin nguồn trực tiếp hoặc gián tiếp #include tiêu đề đó. Nếu bật các tính năng như "tiêu đề được biên dịch trước" sẽ làm tăng tốc độ xây dựng cho lần xây dựng thứ hai và tiếp theo, thì đây có thể là một cộng tác viên. (Bạn có thể xem các tiêu đề được biên dịch trước như là một giải pháp cho điều này, nhưng lưu ý rằng có những sự cân bằng khác khi sử dụng chúng).
  3. Nếu bạn đang sử dụng libs bên ngoài, kiểm tra để chắc chắn rằng họ đang cài đặt và cấu hình tại địa phương. Một quá trình biên soạn mà âm thầm đi tìm trên internet cho một số thành phần (ví dụ một tiêu đề tên tập tin mã hóa cứng đó là trên một số máy chủ từ xa) sẽ làm chậm điều đáng kể. Bạn sẽ ngạc nhiên về tần suất xảy ra với thư viện của bên thứ ba.

Ngoài ra, các kỹ thuật để tìm ra sự cố phụ thuộc vào cách quy trình xây dựng của bạn được cấu trúc.

Nếu bạn đang sử dụng một makefile (hoặc một số phương tiện khác) mà biên dịch file nguồn riêng biệt, sau đó sử dụng một số cách để thời gian biên soạn riêng và các lệnh liên kết. Ghi nhớ rằng nó có thể là thời gian liên kết thống trị.

Nếu bạn đang sử dụng một lệnh biên soạn đơn (ví dụ gcc gọi trên nhiều file trong một lệnh) sau đó chia nó ra thành các lệnh riêng cho mỗi tập tin nguồn.

Khi bạn đã bị cô lập mà tập tin nguồn (nếu có) là người phạm tội, sau đó chọn lọc loại bỏ một số phần từ nó để tìm mà mã bên trong nó là vấn đề. Như Yakk đã nói trong bình luận, sử dụng một "tìm kiếm nhị phân" cho điều này để loại bỏ các chức năng bên trong tập tin. Tôi khuyên bạn nên xóa toàn bộ hàm trước (để thu hẹp hàm vi phạm) và sau đó sử dụng cùng một kỹ thuật trong một hàm vi phạm.

Nó giúp để cấu trúc mã của bạn do đó số lượng các chức năng cho mỗi tập tin là hợp lý nhỏ. Điều đó làm giảm nhu cầu xây dựng lại các tệp lớn cho một thay đổi nhỏ của một hàm, và giúp cô lập các vấn đề như vậy dễ dàng hơn trong tương lai.

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