Tôi đang tối ưu hóa một hàm tạo được gọi trong một trong các vòng trong cùng của ứng dụng của chúng tôi. Lớp trong câu hỏi rộng khoảng 100 byte, bao gồm một bó của int
s, float
s, bool
s và các cấu trúc tầm thường, và nên được sao chép một cách trivially (nó có một hàm tạo mặc định không độc hại, nhưng không có hàm hủy hoặc hàm ảo). Nó được xây dựng thường đủ để mỗi nano giây thời gian sử dụng trong ctor này hoạt động với khoảng 6.000 đô la phần cứng máy chủ bổ sung mà chúng tôi cần mua.GCC có thể bị ép buộc để tạo ra các nhà thầu hiệu quả cho các đối tượng liên kết bộ nhớ không?
Tuy nhiên, tôi thấy rằng GCC không phát ra mã rất hiệu quả cho hàm tạo này (ngay cả với -O3 -march
v.v.). Việc thực thi hàm tạo của GCC, điền vào các giá trị mặc định thông qua một danh sách khởi tạo, mất khoảng 34ns để chạy. Nếu thay vì hàm tạo mặc định này, tôi sử dụng hàm viết tay ghi trực tiếp vào không gian bộ nhớ của đối tượng với một loạt các nội tại và toán tử con trỏ của SIMD, việc xây dựng mất khoảng 8ns.
Tôi có thể yêu cầu GCC phát ra một hàm tạo hiệu quả cho các đối tượng như vậy khi tôi __attribute__
chúng được liên kết với bộ nhớ trên ranh giới SIMD không? Hay tôi phải sử dụng kỹ thuật cũ của trường như viết bộ khởi tạo bộ nhớ của riêng mình trong hội đồng?
Đối tượng này chỉ được xây dựng dưới dạng cục bộ trên ngăn xếp, vì vậy, mọi chi phí mới/malloc đều không áp dụng.
Bối cảnh:
Lớp này được sử dụng bằng cách xây dựng nó trên stack như là một biến địa phương, lựa chọn cách viết một vài lĩnh vực có giá trị không phải mặc định, và sau đó đi qua nó (bằng cách tham khảo) để một chức năng, mà chuyển tham chiếu của nó đến một cái khác và vân vân.
struct Trivial {
float x,y,z;
Trivial() : x(0), y(0), z(0) {};
};
struct Frobozz
{
int na,nb,nc,nd;
bool ba,bb,bc;
char ca,cb,cc;
float fa,fb;
Trivial va, vb; // in the real class there's several different kinds of these
// and so on
Frobozz() : na(0), nb(1), nc(-1), nd(0),
ba(false), bb(true), bc(false),
ca('a'), cb('b'), cc('c'),
fa(-1), fb(1.0) // etc
{}
} __attribute__((aligned(16)));
// a pointer to a func that takes the struct by reference
typedef int (*FrobozzSink_t)(Frobozz&);
// example of how a function might construct one of the param objects and send it
// to a sink. Imagine this is one of thousands of event sources:
int OversimplifiedExample(int a, float b)
{
Frobozz params;
params.na = a; params.fb = b; // other fields use their default values
FrobozzSink_t funcptr = AssumeAConstantTimeOperationHere();
return (*funcptr)(params);
}
Nhà xây dựng tối ưu ở đây sẽ hoạt động bằng cách sao chép từ cá thể "mẫu" tĩnh vào cá thể mới được xây dựng, lý tưởng sử dụng toán tử SIMD để làm việc 16 byte tại một thời điểm. Thay vào đó GCC thực hiện chính xác điều sai đối với OversimplifiedExample() — một loạt các ops ngay lập tức để điền vào cấu trúc byte-by-byte.
// from objdump -dS
int OversimplifiedExample(int a, float b)
{
a42:55 push %ebp
a43:89 e5 mov %esp,%ebp
a45:53 push %ebx
a46:e8 00 00 00 00 call a4b <_Z21OversimplifiedExampleif+0xb>
a4b:5b pop %ebx
a4c:81 c3 03 00 00 00 add $0x3,%ebx
a52:83 ec 54 sub $0x54,%esp
// calling the 'Trivial()' constructors which move zero, word by word...
a55:89 45 e0 mov %eax,-0x20(%ebp)
a58:89 45 e4 mov %eax,-0x1c(%ebp)
a5b:89 45 e8 mov %eax,-0x18(%ebp)
a5e:89 45 ec mov %eax,-0x14(%ebp)
a61:89 45 f0 mov %eax,-0x10(%ebp)
a64:89 45 f4 mov %eax,-0xc(%ebp)
// filling out na/nb/nc/nd..
a67:c7 45 c4 01 00 00 00 movl $0x1,-0x3c(%ebp)
a71:c7 45 c8 ff ff ff ff movl $0xffffffff,-0x38(%ebp)
a78:89 45 c0 mov %eax,-0x40(%ebp)
a7b:c7 45 cc 00 00 00 00 movl $0x0,-0x34(%ebp)
a82:8b 45 0c mov 0xc(%ebp),%eax
// doing the bools and chars by moving one immediate byte at a time!
a85:c6 45 d0 00 movb $0x0,-0x30(%ebp)
a89:c6 45 d1 01 movb $0x1,-0x2f(%ebp)
a8d:c6 45 d2 00 movb $0x0,-0x2e(%ebp)
a91:c6 45 d3 61 movb $0x61,-0x2d(%ebp)
a95:c6 45 d4 62 movb $0x62,-0x2c(%ebp)
a99:c6 45 d5 63 movb $0x63,-0x2b(%ebp)
// now the floats...
a9d:c7 45 d8 00 00 80 bf movl $0xbf800000,-0x28(%ebp)
aa4:89 45 dc mov %eax,-0x24(%ebp)
// FrobozzSink_t funcptr = GetFrobozz();
aa7:e8 fc ff ff ff call aa8 <_Z21OversimplifiedExampleif+0x68>
// return (*funcptr)(params);
aac:8d 55 c0 lea -0x40(%ebp),%edx
aaf:89 14 24 mov %edx,(%esp)
ab2:ff d0 call *%eax
ab4:83 c4 54 add $0x54,%esp
ab7:5b pop %ebx
ab8:c9 leave
ab9:c3 ret
}
Tôi cố gắng để khuyến khích GCC để xây dựng một đơn 'mẫu mặc định' của đối tượng này, và sau đó số lượng lớn-sao chép nó trong constructor mặc định, bằng cách làm một chút thủ đoạn gian trá với một 'giả' constructor ẩn mà làm các cơ sở mẫu mực và sau đó có mặc định chỉ cần sao chép nó:
struct Frobozz
{
int na,nb,nc,nd;
bool ba,bb,bc;
char ca,cb,cc;
float fa,fb;
Trivial va, vb;
inline Frobozz();
private:
// and so on
inline Frobozz(int dummy) : na(0), /* etc etc */ {}
} __attribute__((aligned(16)));
Frobozz::Frobozz()
{
const static Frobozz DefaultExemplar(69105);
// analogous to copy-on-write idiom
*this = DefaultExemplar;
// or:
// memcpy(this, &DefaultExemplar, sizeof(Frobozz));
}
Nhưng điều này tạo ra ngay cả chậm mã hơn mặc định cơ bản với danh sách initializer, do một số đống sao chép dự phòng.
Cuối cùng tôi đành phải viết một chức năng inlined tự do làm những bước *this = DefaultExemplar
, sử dụng intrinsics trình biên dịch và giả định về sự liên kết bộ nhớ để cấp pipelinedMOVDQA opcodes SSE2 đó sao chép các struct hiệu quả. Điều này đã cho tôi hiệu suất tôi cần, nhưng nó rất khốc liệt. Tôi nghĩ rằng ngày của tôi viết initializers trong lắp ráp được phía sau tôi, và tôi thực sự muốn chỉ có tối ưu hóa của GCC phát ra mã ngay tại địa điểm đầu tiên.
Có cách nào tôi có thể nhận GCC để tạo mã tối ưu cho hàm dựng của tôi, một số cài đặt trình biên dịch hoặc bổ sung __attribute__
Tôi đã bỏ lỡ?
Đây là GCC 4.4 chạy trên Ubuntu.Cờ biên dịch bao gồm -m32 -march=core2 -O3 -fno-strict-aliasing -fPIC
(trong số những người khác). Khả năng di động là không phải là xem xét và tôi hoàn toàn sẵn sàng hy sinh tuân thủ các tiêu chuẩn về hiệu suất tại đây.
Thời gian được thực hiện bằng cách trực tiếp đọc các tem thời gian truy cập với rdtsc
, ví dụ đo một vòng lặp của N OversimplifiedExample() gọi giữa các mẫu với quan tâm đúng mức đến độ phân giải hẹn giờ và bộ nhớ cache và ý nghĩa thống kê và vân vân.
Tôi cũng đã tối ưu hóa điều này bằng cách giảm số lượng trang web gọi càng nhiều càng tốt, tất nhiên, nhưng tôi vẫn muốn biết làm thế nào để nói chung có được ctors tốt hơn trong GCC.
Bạn đã thử GCC mới hơn, như 4.6.2 (hoặc bản chụp mới nhất của bản phát hành sớm sẽ được phát hành 4.7)? –
Bạn có thể bỏ qua định nghĩa của hàm tạo và viết nó hoàn toàn bằng tay trong asm không? Rủi ro và khó khăn để duy trì, nhưng đối với 34 * $ 6000 nó sẽ trả cho chính nó Tôi nghi ngờ – Flexo
Bạn cũng đã thử thêm một số cờ '-msse' khác nhau chưa? Tôi nghĩ rằng họ cần thiết cho sse trong một số trường hợp. Ngoài ra tôi đề nghị bạn chỉ cần có được một gcc gần đây và duyệt manpage của nó, suy nghĩ về nếu mọi tùy chọn có thể cải thiện tình hình của bạn và sau đó thử nó ra. – PlasmaHH