2012-02-04 44 views
5

Hôm qua, tôi đã có một cuộc phỏng vấn. Ở đó họ hỏi tôi khi tối ưu hóa mã xảy ra? Say,Khi tối ưu hóa mã xảy ra?

int abc;//Global variable 
abc = 3; 
if(abc == 3) 
{ 
    printf("abc will be always 3"); 
} 
else 
{ 
    printf("This will never executed"); 
} 

Bây giờ câu hỏi là khi tối ưu hóa sẽ xảy ra? A ... Tại thời gian chạy B ... Tại thời gian biên dịch. Tôi trả lời lúc biên dịch ... Với tôi tôi nghĩ, trình biên dịch kiểm tra từ khóa dễ bay hơi tại thời gian biên dịch. Nếu biến không được khai báo là dễ bay hơi thì nó sẽ tối ưu hóa mã. Nhưng, khi trình biên dịch đến để biết rằng, biến này không bao giờ sẽ là khác hơn 3? Nếu nó ở thời gian chạy, sau đó khi trình biên dịch đến để biết rằng biến đó sẽ không bao giờ là khác hơn 3? Bởi vì nếu biến sẽ được thay đổi sau khi phần này của mã được thực hiện. Vui lòng xóa bỏ nghi ngờ của tôi

Trả lời

4

Hầu hết các tối ưu hóa mã không có thể xảy ra trong thời gian chạy, ít nhất là không làm thế nào bạn có ý nghĩa. Mã không thể thay đổi trong khi thực thi để phản ánh một tập hợp biến mới hoặc khác, điều đó sẽ tạo ra một mớ hỗn độn tuyệt đối. Những gì có thể được thực hiện trong thời gian chạy là chọn con đường tốt nhất thông qua mã, nhưng điều đó phải được thực hiện chủ yếu bằng tay để tạo ra các đường dẫn riêng biệt, đầu ra sớm, chi nhánh và vv để cho phép tối ưu hóa.

Mã như thế này, như được viết, cho phép tối ưu hóa thời gian biên dịch vì trình biên dịch có thể kiểm tra bất kỳ giá trị thay thế nào có thể là abc và nếu không tìm thấy, hãy tối ưu hóa cuộc gọi. Phạm vi tìm kiếm ảnh hưởng rất lớn cho dù điều đó có thể xảy ra, tuy nhiên, và các thiết lập trình biên dịch ảnh hưởng đến điều đó.

Nếu trình biên dịch đơn giản là tối ưu hóa một cách ngây thơ các tệp đối tượng đơn lẻ và dòng thứ hai của bạn nằm trong tệp khác từ phần in, thì có thể không đảm bảo được abc và không thể tối ưu hóa điều này. Bất kể việc sử dụng biến, nó phụ thuộc vào mức độ tích cực của các thiết lập trình biên dịch của bạn và liệu chúng có được phép loại bỏ các nhánh chết hay không, hoặc sẽ xem xét làm như vậy.Tối ưu hóa cho kích thước có thể có nhiều khả năng để loại bỏ các chi nhánh hơn cho tốc độ, nhưng cài đặt trung bình/cao sẽ có khả năng làm điều đó một trong hai cách (nếu có thể).

Trình biên dịch hiện đại nhất có tùy chọn tối ưu hóa toàn bộ chương trình, điều này sẽ trì hoãn phần lớn tối ưu hóa cho đến giai đoạn trình liên kết. Điều này cho phép nó tìm kiếm toàn bộ chương trình, có khả năng phát hiện ra rằng abc không bao giờ được thay đổi hoặc được sử dụng ở bất kỳ nơi nào khác, và loại bỏ biến và chi nhánh thất bại khỏi điều kiện. Tối ưu hóa toàn bộ chương trình có thể hiệu quả hơn nhiều so với việc tối ưu hóa riêng cho từng đối tượng, bởi vì nó có thể cho phép tìm kiếm chính xác hơn.

Trong trường hợp không thể trình biên dịch cắt mã chết, bạn có thể gợi ý nó (các cấu trúc gần đây như constexpr có thể trợ giúp điều đó) hoặc tự thêm tối ưu hóa. Nó có thể đơn giản như đặt con đường có khả năng nhất đầu tiên và bao gồm một sự trở lại trước khi người khác, tiết kiệm CPU từ làm một bước nhảy. Đó là loại tối ưu hóa vi mô là không cần thiết, chắc chắn không phải trong một ví dụ đơn giản như thế này, nơi mà các if là rất nhiều tối ưu hóa của chính nó.

8

Mã C thường được biên dịch bằng cách sử dụng trình biên dịch tĩnh (còn gọi là trước thời hạn). Mã được đặt trong đá khi nó rời khỏi trình biên dịch; nó không thể thay đổi khi chạy.

Điều này trái ngược với các ngôn ngữ sử dụng just-in-time compilation, chẳng hạn như Java. Ở đó, tối ưu hóa có thể xảy ra khá nhiều bất cứ lúc nào trong khi chương trình đang chạy.

+1

Điều gần nhất với tối ưu hóa thời gian chạy trong C là tối ưu hóa dựa trên profiler. Bạn biên dịch chương trình, sau đó chạy nó, trong khi sử dụng một trình thu thập thông tin để thu thập số liệu thống kê. Sau đó, bạn biên dịch lại, đưa ra các số liệu thống kê này cho trình biên dịch. Biên dịch thứ hai sẽ sử dụng thông tin này để tạo mã nhanh hơn. – ugoren

1

Tôi không quá quen thuộc với cách trình biên dịch tối ưu hóa mã, tuy nhiên tôi biết rằng từ mã bạn có ở đó, trình biên dịch có thể suy ra rằng abc không bao giờ bị thay đổi. Điều này có nghĩa là số có thể hoàn toàn đưa ra tuyên bố if và chỉ cần gọi hàm printf đầu tiên.

Sau đó, khi chạy, không còn tối ưu hóa nhiều, vì mã này khá đơn giản. Nếu abc được thay đổi, thì rõ ràng là không thể elided được if, sau đó, khi chạy, có những thứ như dự đoán nhánh trên CPU sẽ thử dự đoán đường dẫn mã đang được thực hiện. Mà cũng có thể được coi là một hình thức tối ưu hóa.

Lưu ý: Tôi không tuyên bố rằng đây là những gì xảy ra, nhưng tôi có thể thấy rằng trình biên dịch có thể tối ưu hóa theo cách đó, trong cài đặt tối ưu hóa tích cực.

2

Tại thời gian biên dịch :)

(Vâng, về nguyên tắc nó phụ thuộc vào trình biên dịch, vv)

1

Tôi tự hỏi nếu họ đang tìm kiếm một câu trả lời hoặc một vài câu trả lời có thể có.

Trình biên dịch không làm bất cứ điều gì vào thời gian chạy, thường là. Trình biên dịch nhị phân và các thành phần của nó không bắt buộc phải có mặt (đối với ngôn ngữ C) tại môi trường thời gian chạy.

Loại tối ưu hóa sẽ loại bỏ nhánh chết này sẽ sử dụng một số kỹ thuật để viết tối ưu hóa trình biên dịch. Xem sách của Muchnik về các trình biên dịch. Các kỹ thuật sử dụng có thể liên quan đến việc tạo ra một đồ thị có hướng của các khối cơ bản, sau đó sử dụng một trong số:

  • def sử dụng phân tích
  • tĩnh phân đơn (SSA) phân tích
    cùng với
  • tuyên truyền không đổi (chuyển đổi nếu vào if (3 == 3)
    và sau đó
  • loại bỏ biểu thức hằng
    sau đó loại bỏ
  • mã chết/chi nhánh chết

Mặt khác, một số trình biên dịch có thể không tính toán đủ để loại bỏ điều này trong quá trình tối ưu hóa.

Trong trường hợp đó, nếu bạn chạy mã trên chip có dự đoán nhánh, chip sẽ "tìm hiểu" nhánh đầu tiên được dự đoán và bộ nhớ cache (tìm nạp) nhánh đó thuận lợi. Nhưng đây không phải là một cơ chế biên dịch, thường không được gọi là tối ưu hóa.

1

Câu trả lời đơn giản nhất: Tối ưu hóa mã xảy ra tại thời điểm viết.

Câu trả lời đơn giản: có thể, trình biên dịch phụ thuộc vào phạm vi.

Câu trả lời khó: Với phạm vi toàn cầu, trình biên dịch nên để nguyên điều đó với giả định rằng nó có thể được truy cập ở nơi khác trong tệp. Nhiều đường chuyền có thể tối ưu hóa nó. Nếu nó không được coi là tĩnh đối với các tập tin của trình biên dịch (nghĩ rằng các hệ thống với các mô hình bộ nhớ phẳng), sau đó toàn cầu thực sự là toàn cầu và giả định nên được bất cứ điều gì có thể thay đổi giá trị. Đây là lý do tại sao bạn tránh globals trừ khi ý định thực sự là truy cập toàn cầu.

Tùy thuộc vào bộ xử lý, đối số dự đoán nhánh sẽ được áp dụng, nhưng chủ yếu là thời gian biên dịch hoặc không hoàn toàn.

ps: Tôi thực sự không thích các câu hỏi phỏng vấn như thế này.

+0

Đồng ý với câu trả lời của bạn, nhưng một nghi ngờ khác xuất hiện, nếu biến toàn cục (Non-Volatile) có thể được thay đổi tại bất kỳ thời điểm nào thì tại sao chúng ta cần dễ bay hơi? –

+0

dễ bay hơi là cần thiết cho ngữ cảnh xuất hiện tĩnh. Hãy suy nghĩ về một giá trị được chuyển bởi con trỏ. Ngoài ra nếu bạn trực tiếp ánh xạ thanh ghi phần cứng (nghĩ bản đồ bộ nhớ ngoại vi hoặc thanh ghi thiết bị) giá trị có thể thay đổi mà không cần bất kỳ mã nào đang chạy. – Dtyree

3

Không trả lời câu hỏi nhưng đưa ra ví dụ về tối ưu hóa thời gian biên dịch. gcc tối ưu hóa mã khi được yêu cầu làm như vậy. Tùy chọn -O (Tối ưu hóa) cho phép tối ưu hóa ở các mức leves khác nhau. Nó có thể được sử dụng như -O1, -O2 và -O3. Trang người dùng gcc mô tả chính xác ý nghĩa của từng cấp.

Tùy chọn -S dịch C thành lắp ráp và lưu trên tệp .s.

test.c

#include <stdio.h> 

int abc;//Global variable 

void main() 
{ 
    abc = 3; 
    if(abc == 3) 
     printf("abc will be always 3"); 
    else 
     printf("This will never executed"); 
} 

whitout gcc tối ưu hóa hai chuỗi xuất hiện trên mã lắp ráp.

$ gcc -S test.c; mèo test.s

.file "test.c" 
    .comm abc,4,4 
    .section .rodata 
.LC0: 
    .string "abc will be always 3" 
.LC1: 
    .string "This will never executed" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushq %rbp 
    .cfi_def_cfa_offset 16 
    .cfi_offset 6, -16 
    movq %rsp, %rbp 
    .cfi_def_cfa_register 6 
    movl $3, abc(%rip) 
    movl abc(%rip), %eax 
    cmpl $3, %eax 
    jne .L2 
    movl $.LC0, %eax 
    movq %rax, %rdi 
    movl $0, %eax 
    call printf 
    jmp .L1 
.L2: 
    movl $.LC1, %eax 
    movq %rax, %rdi 
    movl $0, %eax 
    call printf 
.L1: 
    popq %rbp 
    .cfi_def_cfa 7, 8 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (GNU) 4.6.1 20110908 (Red Hat 4.6.1-9)" 
    .section .note.GNU-stack,"",@progbits 

Whit mức gcc 1 tối ưu hóa chỉ có một chuỗi được dịch sang lắp ráp

$ gcc -O1 -S thử nghiệm. c; cat test.s

.file "test.c" 
    .section .rodata.str1.1,"aMS",@progbits,1 
.LC0: 
    .string "abc will be always 3" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB11: 
    .cfi_startproc 
    subq $8, %rsp 
    .cfi_def_cfa_offset 16 
    movl $3, abc(%rip) 
    movl $.LC0, %edi 
    movl $0, %eax 
    call printf 
    addq $8, %rsp 
    .cfi_def_cfa_offset 8 
    ret 
    .cfi_endproc 
.LFE11: 
    .size main, .-main 
    .comm abc,4,4 
    .ident "GCC: (GNU) 4.6.1 20110908 (Red Hat 4.6.1-9)" 
    .section .note.GNU-stack,"",@progbits 
+0

Xuất sắc .... giải thích –