2013-12-11 21 views
8

Làm thế nào để trình biên dịch avoid linear growth trong kích thước của hệ nhị phân đã biên dịch với mỗi kiểu khởi tạo mới của một mẫu?Trình biên dịch C++ tối ưu hóa mã mẫu như thế nào?

Tôi không thấy cách chúng ta có thể tránh tạo bản sao của tất cả các mã mẫu khi sử dụng một phiên bản mới.

Tôi cảm thấy rằng thời gian biên dịch và kích thước nhị phân sẽ được thực hiện vô cùng khó sử dụng cho tất cả trừ các mẫu đơn giản nhất trong một cơ sở mã hợp lý lớn. Nhưng sự phổ biến của chúng cho thấy rằng các trình biên dịch có thể thực hiện một số phép thuật để làm cho chúng thực tế.

+7

Họ không. (Mặc dù họ có thể làm một số coalescing cho cùng một loại) Đó là lý do tại sao nó có thể sụp đổ một trình biên dịch C + + với một vài dòng mã mẫu cẩn thận bằng văn bản. – Mysticial

+0

Điều gì làm cho bạn nghĩ rằng họ làm gì? –

+1

Việc khởi tạo mẫu lớp sẽ xuất hiện trong exe của bạn một lần, cho dù bạn đã tạo một phiên bản của nó bao nhiêu lần. Cùng với các mẫu chức năng. Bạn không nhận được một thực thi lớn chỉ vì bạn đã khai báo 1000 biến loại vector . – polkadotcadaver

Trả lời

6

Nhiều chức năng mẫu đủ nhỏ để nội tuyến hiệu quả, vì vậy bạn làm có được sự tăng trưởng tuyến tính trong hệ nhị phân - nhưng không nhiều hơn bạn sẽ nhận được với các hàm mẫu không tương đương.

Quy tắc một định nghĩa quan trọng ở đây, vì nó cho phép trình biên dịch giả định rằng bất kỳ mẫu nào với các tham số mẫu giống hệt nhau đều tạo ra mã giống nhau. Nếu phát hiện rằng chức năng mẫu đã được khởi tạo trước đó trong một tệp nguồn, nó có thể sử dụng bản sao đó thay vì tạo một bản sao mới. Tên mangling làm cho nó có thể cho một mối liên kết để nhận ra cùng một chức năng từ các nguồn khác nhau biên dịch. Không ai trong số này được đảm bảo vì chương trình của bạn không thể cho biết sự khác biệt giữa các bản sao giống hệt nhau của một hàm, nhưng trình biên dịch thực hiện tối ưu hóa khó hơn so với mỗi ngày.

Một lần mà các bản sao được yêu cầu phải được lọc ra là khi hàm chứa biến tĩnh - chỉ có thể có một bản sao. Nhưng điều đó có thể đạt được bằng cách lọc ra các hàm trùng lặp hoặc tự lọc ra các biến tĩnh.

3

Tôi nghĩ bạn hiểu nhầm cách thức triển khai mẫu. Các mẫu được biên dịch trên cơ sở cần sử dụng vào một lớp/chức năng tương ứng.

Xét đoạn mã sau ...

template <typename Type> 
Type mymax(Type a, Type b) { 
    return a > b ? a : b; 
} 

int main(int argc, char** argv) 
{ 
} 

Biên soạn này, tôi nhận được lắp ráp sau.

.file "example.cpp" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB1: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movl %edi, -4(%rbp) 
    movq %rsi, -16(%rbp) 
    movl $0, %eax 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE1: 
    .size main, .-main 
    .ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1" 
    .section .note.GNU-stack,"",@progbits 

Bạn sẽ nhận thấy nó chỉ chứa chức năng chính. Bây giờ tôi cập nhật mã của tôi để sử dụng chức năng mẫu.

int main(int argc, char** argv) 
{ 
    mymax<double>(3,4); 
} 

Biên dịch rằng tôi nhận được kết quả lắp ráp dài hơn bao gồm chức năng mẫu để xử lý tăng gấp đôi. Trình biên dịch thấy chức năng mẫu đã được sử dụng bởi kiểu "double" để tạo ra một hàm để xử lý trường hợp đó.

.file "example.cpp" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB1: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $32, %rsp 
    movl %edi, -4(%rbp) 
    movq %rsi, -16(%rbp) 
    movabsq $4616189618054758400, %rdx 
    movabsq $4613937818241073152, %rax 
    movq %rdx, -24(%rbp) 
    movsd -24(%rbp), %xmm1 
    movq %rax, -24(%rbp) 
    movsd -24(%rbp), %xmm0 
    call _Z5mymaxIdET_S0_S0_ 
    movl $0, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE1: 
    .size main, .-main 
    .section .text._Z5mymaxIdET_S0_S0_,"axG",@progbits,_Z5mymaxIdET_S0_S0_,comdat 
    .weak _Z5mymaxIdET_S0_S0_ 
    .type _Z5mymaxIdET_S0_S0_, @function 
_Z5mymaxIdET_S0_S0_: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movsd %xmm0, -8(%rbp) 
    movsd %xmm1, -16(%rbp) 
    movsd -8(%rbp), %xmm0 
    ucomisd -16(%rbp), %xmm0 
    jbe .L9 
    movq -8(%rbp), %rax 
    jmp .L6 
.L9: 
    movq -16(%rbp), %rax 
.L6: 
    movq %rax, -24(%rbp) 
    movsd -24(%rbp), %xmm0 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size _Z5mymaxIdET_S0_S0_, .-_Z5mymaxIdET_S0_S0_ 
    .ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1" 
    .section .note.GNU-stack,"",@progbits 

Bây giờ, hãy thay đổi mã để sử dụng chức năng đó hai lần.

int main(int argc, char** argv) 
{ 
    mymax<double>(3,4); 
    mymax<double>(4,5); 

} 

Một lần nữa, hãy nhìn vào hội đồng mà nó tạo ra. Nó có thể so sánh với đầu ra trước đó vì hầu hết mã đó chỉ là trình biên dịch tạo hàm mymax trong đó "Loại" được thay đổi thành gấp đôi. Dù tôi sử dụng chức năng đó bao nhiêu lần, nó sẽ chỉ được khai báo một lần.

.file "example.cpp" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB1: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    subq $32, %rsp 
    movl %edi, -4(%rbp) 
    movq %rsi, -16(%rbp) 
    movabsq $4616189618054758400, %rdx 
    movabsq $4613937818241073152, %rax 
    movq %rdx, -24(%rbp) 
    movsd -24(%rbp), %xmm1 
    movq %rax, -24(%rbp) 
    movsd -24(%rbp), %xmm0 
    call _Z5mymaxIdET_S0_S0_ 
    movabsq $4617315517961601024, %rdx 
    movabsq $4616189618054758400, %rax 
    movq %rdx, -24(%rbp) 
    movsd -24(%rbp), %xmm1 
    movq %rax, -24(%rbp) 
    movsd -24(%rbp), %xmm0 
    call _Z5mymaxIdET_S0_S0_ 
    movl $0, %eax 
    leave 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE1: 
    .size main, .-main 
    .section .text._Z5mymaxIdET_S0_S0_,"axG",@progbits,_Z5mymaxIdET_S0_S0_,comdat 
    .weak _Z5mymaxIdET_S0_S0_ 
    .type _Z5mymaxIdET_S0_S0_, @function 
_Z5mymaxIdET_S0_S0_: 
.LFB2: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movsd %xmm0, -8(%rbp) 
    movsd %xmm1, -16(%rbp) 
    movsd -8(%rbp), %xmm0 
    ucomisd -16(%rbp), %xmm0 
    jbe .L9 
    movq -8(%rbp), %rax 
    jmp .L6 
.L9: 
    movq -16(%rbp), %rax 
.L6: 
    movq %rax, -24(%rbp) 
    movsd -24(%rbp), %xmm0 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE2: 
    .size _Z5mymaxIdET_S0_S0_, .-_Z5mymaxIdET_S0_S0_ 
    .ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1" 
    .section .note.GNU-stack,"",@progbits 

Vì vậy, các mẫu cơ bản không ảnh hưởng đến kích thước thực thi nhiều hơn viết chức năng bằng tay. Nó chỉ là một tiện lợi. Trình biên dịch sẽ tạo ra một hàm cho một hoặc nhiều cách sử dụng của một kiểu đã cho nên nếu tôi sử dụng nó 1 hoặc 1000 lần, sẽ chỉ có một thể hiện của nó.Bây giờ nếu tôi cập nhật mã của mình để xử lý một kiểu mới như float, tôi sẽ nhận được một hàm khác trong tệp thực thi của mình, nhưng chỉ một hàm bất kể tôi sử dụng hàm đó bao nhiêu lần.

+0

Anh ta nói "mỗi ** kiểu mới ** của một mẫu?". mymax (3,4); sẽ tăng kích thước của tệp thực thi. –

+0

Anh ấy biết điều đó. Câu trả lời anh liên kết cũng nói vậy.Tôi chỉ giải thích về câu trả lời cho thấy rằng các hàm mẫu được thêm vào thực thi trên cơ sở cần thiết và nhiều "instantiations" bằng cách sử dụng danh pháp của câu hỏi được liên kết, không tăng kích thước thực thi nhiều vì nhiều instantiations sẽ sử dụng cùng chức năng. – voodoogiant

5

Có nhiều điều mà dẫn đến nhiều sự khởi tạo không quá có hại cho kích thước exacutable:

  1. Nhiều mẫu chỉ là đi qua mọi thứ thông qua để lớp khác. Mặc dù có thể có khá nhiều mã nó hầu như biến mất khi mã được khởi tạo và được gạch chân. Lưu ý nội tuyến [và thực hiện một số tối ưu hóa] có thể dễ dàng dẫn đến mã lớn hơn. Lưu ý rằng nội tuyến các hàm nhỏ thường dẫn đến mã nhỏ hơn (và nhanh hơn) (về cơ bản vì trình tự gọi cần thiết thường yêu cầu hướng dẫn nhiều hơn nội dung được inline và trình tối ưu hóa có cơ hội tốt hơn để giảm mã hơn nữa xem những gì đang xảy ra).
  2. Trường hợp mã mẫu không được gạch chân, các cảnh báo trùng lặp trong các đơn vị dịch khác nhau cần phải được hợp nhất thành một bản sao. Tôi không phải là chuyên gia liên kết nhưng hiểu biết của tôi là, ví dụ: ELF sử dụng các phần khác nhau và trình liên kết có thể chọn chỉ bao gồm những phần thực sự được sử dụng.
  3. Trong tệp thi hành lớn hơn, bạn sẽ cần một số loại từ vựng và bản sao được sử dụng ở nhiều nơi và được chia sẻ hiệu quả. Làm tất cả mọi thứ bằng cách sử dụng một loại tùy chỉnh sẽ là ý tưởng tồi và loại tẩy xoá chắc chắn là một công cụ quan trọng để tránh quá nhiều loại.

Điều đó nói rằng, nếu có thể, nó sẽ trả hết để tạo mẫu trước, đặc biệt nếu chỉ có một số lượng nhỏ các bản tóm tắt thường được sử dụng. Ví dụ tuyệt vời là thư viện IOStreams không được sử dụng với hơn 4 loại (thường được sử dụng chỉ với một): di chuyển các định nghĩa mẫu và các phiên bản của chúng thành các đơn vị dịch riêng biệt có thể không giảm kích thước thực thi nhưng chắc chắn sẽ giảm thời gian biên dịch! Bắt đầu với C++ 11, bạn có thể khai báo các mẫu instantiations là extern cho phép các định nghĩa hiển thị mà không nhận được một cách ngầm rõ ràng về các chuyên môn được biết là được khởi tạo ở nơi khác.

+0

Tôi cũng nghĩ rằng OP bắt đầu từ ý tưởng (sai) rằng template = functions, nhưng điều này rõ ràng là sai, các mẫu trong C++ là một ví dụ về lập trình meta, nó là công nghệ tạo phần mềm bên trong phần mềm của bạn, khái niệm bắt buộc và ít trừu tượng hơn. cũng có các cơ chế khác nhau, ví dụ quá nhiều lần một chuyên môn hóa một phần cho một khuôn mẫu được gọi là "quá tải", và đây là thứ có thể khiến ai đó nghĩ rằng nó hoạt động giống như quá tải hàm lúc chạy. – user2485710

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