2013-05-21 49 views
16

Trong khi bước qua một số mã Qt, tôi đã xem xét những điều sau đây. Chức năng QMainWindowLayout::invalidate() có việc thực hiện sau đây:Tại sao trình biên dịch tạo ra assembly này?

void QMainWindowLayout::invalidate() 
{ 
QLayout::invalidate() 
minSize = szHint = QSize(); 
} 

Nó được biên dịch như sau:

<invalidate()>  push %rbx 
<invalidate()+1>  mov %rdi,%rbx 
<invalidate()+4>  callq 0x7ffff4fd9090 <QLayout::invalidate()> 
<invalidate()+9>  movl $0xffffffff,0x564(%rbx) 
<invalidate()+19>  movl $0xffffffff,0x568(%rbx) 
<invalidate()+29>  mov 0x564(%rbx),%rax 
<invalidate()+36>  mov %rax,0x56c(%rbx) 
<invalidate()+43>  pop %rbx 
<invalidate()+44>  retq 

Việc lắp ráp từ vô hiệu + 9 để làm mất hiệu lực + 36 có vẻ ngu ngốc. Đầu tiên mã viết -1 đến% rbx + 0x564 và% rbx + 0x568, nhưng sau đó nó tải -1 từ% rbx + 0x564 trở lại vào sổ đăng ký chỉ để ghi nó ra% rbx + 0x56c. Điều này có vẻ giống như một cái gì đó trình biên dịch sẽ dễ dàng có thể tối ưu hóa vào một động thái khác ngay lập tức.

Vì vậy, đây là mã ngu ngốc (và nếu như vậy, tại sao trình biên dịch sẽ không tối ưu hóa nó?) Hoặc bằng cách nào đó rất thông minh và nhanh hơn chỉ sử dụng một động thái khác ngay lập tức?

(Lưu ý:.. Mã này là từ bình thường xây dựng thư viện phát hành vận chuyển bởi ubuntu, vì vậy nó có lẽ đã được biên soạn bởi GCC trong chế độ tối ưu hóa minSizeszHint biến là các biến bình thường của loại QSize)

+3

QT là giao diện người dùng, đúng không? Bạn cần phải làm mất bao nhiêu lần để làm mất hiệu lực cửa sổ? Làm thế nào để thực hiện mà thực sự cần phải được? Loại tối ưu hóa vi mô mà bạn mô tả gần như chắc chắn không đáng để nỗ lực tối thiểu mà sẽ tích lũy được. –

+0

Dường như tối ưu thực sự, có thể là trình tối ưu hóa lổ nhìn trộm chỉ không nhận được điều này. –

+11

@RobertHarvey Nhưng đó không phải là vấn đề ở đây - OP không cố gắng tối ưu hóa, anh ấy đang cố hiểu lý do. –

Trả lời

13

Không chắc bạn đúng khi bạn nói nó ngu ngốc. Tôi nghĩ rằng trình biên dịch có thể đang cố gắng tối ưu hóa kích thước mã ở đây. Không có lệnh trực tiếp 64-bit cho bộ nhớ mov. Vì vậy, trình biên dịch có để tạo ra 2 hướng dẫn mov giống như nó đã làm ở trên. Mỗi người trong số họ sẽ là 10 byte, 2 di chuyển được tạo ra là 14 byte. Nó được viết cho nên hầu như không có độ trễ bộ nhớ vì vậy tôi không nghĩ rằng bạn sẽ thực hiện bất kỳ hit hiệu suất nào ở đây.

+0

... và ngoài ra, nếu bạn thực hiện 'mov ..., (addr)' theo sau là 'mov (addr), ...' thì thứ 2 là bộ nhớ cache nóng, tức là có ít hình phạt cho nó. Tối ưu hóa duy nhất tôi có thể nghĩ ở đây sẽ là 'pcmpeq% xmm0,% xmm0; movdqu% xmm0, 0x564 (% rbx) 'để đặt toàn bộ 16 byte cho tất cả' 0xff..', nhưng đó là một yêu cầu khá khó khăn để "hợp nhất" hai biến trong thời trang này - và có lẽ không hoàn toàn phù hợp với tiêu chuẩn. để đảm bảo khả năng hiển thị tải/lưu trữ của C++. –

+2

+1 cho * "Không có lệnh trực tiếp 64-bit cho bộ nhớ mov," * đó là tất cả những gì cần phải nói. –

+0

Tôi không biết phần nào về việc di chuyển 64 bit ngay lập tức, vì vậy đó có lẽ là giải pháp. Ngoài ra, dường như không có chi phí thực trên x86 nếu truy cập bộ nhớ không được ký hiệu không vượt qua ranh giới bộ nhớ cache – JanKanis

1

Tôi muốn phá vỡ các dòng như thế này (nghĩ nhiều có bình luận cùng bước)

Hai dòng này xuất phát từ định nghĩa inline của QSize()http://qt.gitorious.org/qt/qt/blobs/4.7/src/corelib/tools/qsize.h mà thiết lập từng lĩnh vực riêng biệt. Ngoài ra, tôi đoán là 0x564 (% rbx) là địa chỉ của szHint cũng được đặt cùng một lúc.

<invalidate()+9>  movl $0xffffffff,0x564(%rbx) 
<invalidate()+19>  movl $0xffffffff,0x568(%rbx) 

Những dòng cuối cùng đang thiết minSize sử dụng hoạt động 64bit vì trình biên dịch bây giờ biết kích thước của một đối tượng QSize. Và địa chỉ của minSize là 0x56c (% rbx)

<invalidate()+29>  mov 0x564(%rbx),%rax 
<invalidate()+36>  mov %rax,0x56c(%rbx) 

Lưu ý. Phần đầu tiên là thiết lập hai trường riêng biệt, và phần tiếp theo là sao chép một đối tượng QSize (bất kể nội dung). Câu hỏi đặt ra là, trình biên dịch có đủ thông minh để xây dựng một giá trị 64bit ghép bởi vì nó đã thấy các giá trị đặt trước ngay trước đó? Không chắc chắn về điều đó ...

+2

Có, trình biên dịch thường có thể thực hiện các loại tối ưu hóa này. Nó được gọi là gấp liên tục. – JanKanis

+0

@Somejan Cool, không biết điều đó :) – epatel

0

Ngoài câu trả lời của Guillaume, tải/lưu trữ 64 bit không được căn chỉnh. Nhưng theo số Intel optimization guide (p 3-62)

Truy cập dữ liệu không đúng có thể bị phạt nặng. Điều này đặc biệt đúng đối với các phân tách dòng bộ nhớ cache. Kích thước của bộ nhớ cache là 64 byte trong Pentium 4 và các bộ vi xử lý Intel gần đây khác, bao gồm bộ vi xử lý dựa trên vi kiến ​​trúc Intel Core.

Quyền truy cập vào dữ liệu chưa được căn chỉnh trên ranh giới 64 byte dẫn đến hai bộ nhớ truy cập và yêu cầu nhiều lệnh được thực thi (thay vì một). Truy cập ranh giới khoảng 64 byte đó có khả năng phải chịu một khoản phạt hiệu suất lớn, chi phí của mỗi gian hàng thường lớn hơn trên máy có đường ống dài hơn.

IMO ngụ ý rằng một tải/lưu trữ không được ký kết không vượt qua ranh giới dòng bộ nhớ cache là rẻ. Trong trường hợp này con trỏ cơ sở trong quá trình tôi đã gỡ lỗi là 0x10f9bb0, do đó, hai biến là 20 và 28 byte vào trong đường dẫn.

Bộ xử lý thông thường của Intel sử dụng cửa hàng để tải chuyển tiếp, do đó, tải trọng của một giá trị vừa được lưu trữ thậm chí không cần chạm vào bộ nhớ cache. Nhưng cùng một hướng dẫn cũng nói rằng một tải trọng lớn của một số cửa hàng nhỏ hơn không lưu trữ-tải về phía trước nhưng quầy hàng: (p 3-66, p 3-68)

Quy tắc mã hóa/biên dịch 49. (H tác động, M tổng quát) Dữ liệu của một tải được chuyển tiếp từ cửa hàng phải được chứa hoàn toàn trong dữ liệu cửa hàng.

; A. Large load stall 
mov  mem, eax  ; Store dword to address “MEM" 
mov  mem + 4, ebx ; Store dword to address “MEM + 4" 
fld  mem    ; Load qword at address “MEM", stalls 

Vì vậy, các mã trong câu hỏi có thể gây ra một gian hàng, và do đó tôi nghiêng để tin rằng nó không phải là tối ưu. Tôi sẽ không ngạc nhiên nếu GCC không có những hạn chế như vậy một cách đầy đủ. Có ai biết nếu/làm thế nào nhiều mô hình của các giới hạn chuyển tiếp lưu trữ để tải GCC không?

EDIT: một số thử nghiệm với việc thêm các giá trị bộ nạp trước khi các trường minSize/szHint cho thấy GCC không quan tâm ở tất cả các ranh giới của đường bộ nhớ cache và cũng không bị nghẽn.

8

Mã "nhỏ hơn hoàn hảo".

Đối với kích thước mã, 4 hướng dẫn đó thêm tối đa 34 byte. Một chuỗi nhỏ hơn nhiều (19 byte) là có thể:

00000000 31C0    xor eax,eax 
00000002 48F7D0   not rax 
00000005 48898364050000 mov [rbx+0x564],rax 
0000000C 4889836C050000 mov [rbx+0x56c],rax 

;Note: XOR above clears RAX due to zero extension 

Đối với những thứ hiệu suất không đơn giản như vậy. CPU muốn thực hiện nhiều lệnh cùng một lúc, và đoạn mã trên phá vỡ nó. Ví dụ:

xor eax,eax 
not rax     ;Must wait until previous instruction finishes 
mov [rbx+0x564],rax  ;Must wait until previous instruction finishes 
mov [rbx+0x56c],rax  ;Must wait until "not" finishes 

Đối với hiệu suất mà bạn muốn làm điều này:

00000000 48C7C0FFFFFFFF  mov rax,0xffffffff 
00000007 C78364050000FFFFFFFF mov dword [rbx+0x564],0xffffffff 
00000011 C78368050000FFFFFFFF mov dword [rbx+0x568],0xffffffff 
0000001B C7836C050000FFFFFFFF mov dword [rbx+0x56c],0xffffffff 
00000025 C78370050000FFFFFFFF mov dword [rbx+0x570],0xffffffff 

;Note: first MOV sets RAX to 0xFFFFFFFFFFFFFFFF due to sign extension 

này cho phép tất cả các hướng dẫn được thực hiện song song, không phụ thuộc bất cứ nơi nào. Đáng buồn thay, nó cũng lớn hơn nhiều (45 byte).

Nếu bạn cố gắng cân bằng giữa kích thước và hiệu suất mã; sau đó bạn có thể hy vọng rằng lệnh đầu tiên (đặt giá trị trong RAX) hoàn tất trước khi lệnh cuối cùng/s cần biết giá trị trong RAX. Đây có thể là một cái gì đó như thế này:

mov rax,-1 
mov dword [rbx+0x564],0xffffffff 
mov dword [rbx+0x568],0xffffffff 
mov dword [rbx+0x56c],rax 

Đây là 34 byte (cùng kích thước với mã gốc). Đây có thể là một sự thỏa hiệp tốt giữa kích thước và hiệu suất mã.

Bây giờ; hãy xem mã gốc và xem lý do tại sao mã này xấu:

mov dword [rbx+0x564],0xffffffff 
mov dword [rbx+0x568],0xffffffff 
mov rax,[rbx+0x564]    ;Massive problem 
mov [rbx+0x56C],rax    ;Depends on previous instruction 

CPU hiện đại có lưu trữ trong bộ đệm và lần đọc sau có thể lấy giá trị từ bộ đệm này để tránh đọc giá trị từ bộ nhớ cache.Trớ trêu thay, điều này chỉ hoạt động nếu kích thước của đọc nhỏ hơn hoặc bằng kích thước của ghi. "Chuyển tiếp cửa hàng" sẽ không hoạt động đối với mã này vì có 2 lần viết và đọc lớn hơn cả hai. Điều này có nghĩa là lệnh thứ ba phải chờ cho đến khi 2 lệnh đầu tiên được ghi vào bộ nhớ đệm và sau đó phải đọc giá trị từ bộ nhớ đệm; mà có thể dễ dàng thêm đến một hình phạt khoảng 30 chu kỳ hoặc hơn. Sau đó, lệnh thứ tư phải đợi lệnh thứ ba (và không thể xảy ra song song với bất kỳ thứ gì) vì vậy đó là một vấn đề khác.

+0

+1 để sử dụng cú pháp intel. Câu hỏi nhanh, mã gốc có 'mov [rbx + 0x56C], rax' nhưng trong ví dụ tối ưu của bạn' mov dword [rbx + 0x56C], rax'. Điều này có nghĩa là bản gốc di chuyển 8 byte (QWORD) vào '[rbx + 0x56c]' trong khi bạn di chuyển 4 byte (DWORD)? Đây có phải là mục đích không? – greatwolf

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