2014-09-24 18 views
22

Gần đây tôi đã được biết về các chức năng tích hợp của GCC đối với một số chức năng quản lý bộ nhớ của thư viện C, cụ thể là __builtin_malloc() và các phần tích hợp có liên quan (xem https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html). Sau khi tìm hiểu về __builtin_malloc(), tôi đã tự hỏi làm thế nào nó có thể làm việc để cung cấp cải tiến hiệu suất trên các thói quen thư viện liên quan malloc() đồng bằng. Ví dụ, nếu hàm thành công, nó phải cung cấp một khối có thể được giải phóng bằng một cuộc gọi đến số free() đơn giản vì con trỏ có thể được giải phóng bởi một mô-đun được biên dịch mà không cần bật __builtin_malloc() hoặc __builtin_free() (hoặc tôi có sai không về điều này và nếu sử dụng __builtin_malloc(), nội dung dựng sẵn phải được sử dụng trên toàn cầu?). Do đó đối tượng được phân bổ phải là một thứ có thể được quản lý với các cấu trúc dữ liệu đơn giản là hợp đồng với malloc()free().Những cải tiến nào của `__builtin_malloc()` của GCC cung cấp trên `malloc() đơn giản`?

Tôi không thể tìm thấy bất kỳ chi tiết nào về cách hoạt động của __builtin_malloc() hoặc chính xác (tôi không phải là người biên dịch trình biên dịch, vì vậy việc chuyển qua mã nguồn GCC không có trong buồng lái của tôi). Trong một số thử nghiệm đơn giản, nơi tôi đã cố gắng gọi trực tiếp __builtin_malloc(), nó chỉ đơn giản là kết thúc được phát ra trong mã đối tượng như một cuộc gọi đến đồng bằng malloc(). Tuy nhiên, có thể có chi tiết tinh tế hoặc nền tảng mà tôi không cung cấp trong các thử nghiệm đơn giản này.

Những loại cải tiến hiệu suất nào có thể __builtin_malloc() cung cấp cuộc gọi đến số malloc() đơn giản? Liệu __builtin_malloc() có phụ thuộc vào cấu trúc dữ liệu khá phức tạp mà việc sử dụng thực thi malloc() của glibc không? Hoặc ngược lại, số malloc()/free() của glibc có một số mã để xử lý các khối có thể được phân bổ bởi __builtin_malloc() không?

Về cơ bản, nó hoạt động như thế nào?

Trả lời

30

Tôi tin rằng không có triển khai GCC bên trong đặc biệt là __builtin_malloc(). Thay vào đó, nó tồn tại như một nội trang chỉ để nó có thể được tối ưu hóa trong một số trường hợp nhất định.

Hãy ví dụ này:

#include <stdlib.h> 
int main(void) 
{ 
    int *p = malloc(4); 
    *p = 7; 
    free(p); 
    return 0; 
} 

Nếu chúng ta vô hiệu hóa lệnh nội trú (với -fno-builtins) và nhìn vào sản lượng được tạo ra:

$ gcc -fno-builtins -O1 -Wall -Wextra builtin_malloc.c && objdump -d -Mintel a.out 

0000000000400580 <main>: 
    400580: 48 83 ec 08    sub rsp,0x8 
    400584: bf 04 00 00 00   mov edi,0x4 
    400589: e8 f2 fe ff ff   call 400480 <[email protected]> 
    40058e: c7 00 07 00 00 00  mov DWORD PTR [rax],0x7 
    400594: 48 89 c7    mov rdi,rax 
    400597: e8 b4 fe ff ff   call 400450 <[email protected]> 
    40059c: b8 00 00 00 00   mov eax,0x0 
    4005a1: 48 83 c4 08    add rsp,0x8 
    4005a5: c3      ret  

Các cuộc gọi đến malloc/free được phát ra, như mong đợi.

Tuy nhiên, bằng cách cho phép malloc là một BUILTIN,

$ gcc -O1 -Wall -Wextra builtin_malloc.c && objdump -d -Mintel a.out 

00000000004004f0 <main>: 
    4004f0: b8 00 00 00 00   mov eax,0x0 
    4004f5: c3      ret  

Tất cả main() được tối ưu hóa đi!

Về cơ bản, bằng cách cho phép malloc là một nội trang dựng sẵn, GCC hoàn toàn miễn phí để loại bỏ các cuộc gọi nếu kết quả của nó không bao giờ được sử dụng, vì không có tác dụng phụ bổ sung.


Đó là cơ chế tương tự cho phép "lãng phí" các cuộc gọi đến printf phải được thay đổi để cuộc gọi đến puts:

#include <stdio.h> 

int main(void) 
{ 
    printf("hello\n"); 
    return 0; 
} 

dựng sẵn bị tắt:

$ gcc -fno-builtin -O1 -Wall builtin_printf.c && objdump -d -Mintel a.out 

0000000000400530 <main>: 
    400530: 48 83 ec 08    sub rsp,0x8 
    400534: bf e0 05 40 00   mov edi,0x4005e0 
    400539: b8 00 00 00 00   mov eax,0x0 
    40053e: e8 cd fe ff ff   call 400410 <[email protected]> 
    400543: b8 00 00 00 00   mov eax,0x0 
    400548: 48 83 c4 08    add rsp,0x8 
    40054c: c3      ret  

builtins được kích hoạt:

gcc -O1 -Wall builtin_printf.c && objdump -d -Mintel a.out 

0000000000400530 <main>: 
    400530: 48 83 ec 08    sub rsp,0x8 
    400534: bf e0 05 40 00   mov edi,0x4005e0 
    400539: e8 d2 fe ff ff   call 400410 <[email protected]> 
    40053e: b8 00 00 00 00   mov eax,0x0 
    400543: 48 83 c4 08    add rsp,0x8 
    400547: c3      ret  
+3

Lời giải thích thú vị và hữu ích. Vì vậy, tính năng thiết yếu của phiên bản tích hợp là nó có thể đảm bảo một hành vi đã biết cho trình biên dịch, cho phép nó được tối ưu hóa, và có lẽ để nhận các tối ưu hóa khác ... –

+2

@DanLenski Đó là cách tôi nhìn thấy nó. Sau một số thí nghiệm, điều duy nhất tôi có thể khiến GCC làm "đặc biệt" với '__builtin_malloc' là tối ưu hóa nó. Tôi đã thử truyền nó '0', nhưng chuyển kết quả tới hàm khác (bên ngoài) đã gây ra một lệnh gọi' malloc' được phát ra. –

+3

Trình biên dịch có thể biết rằng kết quả của __builtin_malloc không bí danh với bất kỳ con trỏ nào khác. Giả sử hàm của bạn có int * p làm tham số và gọi int * q = malloc (sizeof int); * q = 1; sau đó trình biên dịch biết rằng nhiệm vụ này đã không sửa đổi * p. – gnasher729

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