Khi nào lắp ráp nhanh hơn C?


475

Một trong những lý do đã nêu để biết trình biên dịch chương trình là, đôi khi, nó có thể được sử dụng để viết mã sẽ hiệu quả hơn so với viết mã đó bằng ngôn ngữ cấp cao hơn, đặc biệt là C. Tuy nhiên, tôi cũng đã nghe nói nhiều lần rằng mặc dù điều đó không hoàn toàn sai, nhưng trường hợp trình biên dịch thực sự có thể được sử dụng để tạo mã hiệu suất cao hơn thì cực kỳ hiếm và đòi hỏi kiến ​​thức chuyên môn và kinh nghiệm về lắp ráp.

Câu hỏi này thậm chí không đi vào thực tế là các hướng dẫn của trình biên dịch chương trình sẽ là dành riêng cho máy và không di động, hoặc bất kỳ khía cạnh nào khác của trình biên dịch chương trình. Dĩ nhiên, có rất nhiều lý do tốt để biết lắp ráp bên cạnh vấn đề này, nhưng đây có nghĩa là một câu hỏi cụ thể về các ví dụ và dữ liệu, không phải là một diễn ngôn mở rộng về trình biên dịch so với các ngôn ngữ cấp cao hơn.

Bất cứ ai cũng có thể cung cấp một số ví dụ cụ thể về các trường hợp lắp ráp sẽ nhanh hơn mã C được viết tốt bằng trình biên dịch hiện đại và bạn có thể hỗ trợ yêu cầu đó với bằng chứng hồ sơ không? Tôi khá tự tin những trường hợp này tồn tại, nhưng tôi thực sự muốn biết chính xác những trường hợp bí truyền này như thế nào, vì dường như đó là một điểm gây tranh cãi.


17
thực sự nó khá tầm thường để cải thiện mã được biên dịch. Bất cứ ai có kiến ​​thức vững chắc về ngôn ngữ lắp ráp và C đều có thể thấy điều này bằng cách kiểm tra mã được tạo. Bất kỳ điều dễ dàng nào là vách đá hiệu suất đầu tiên bạn rơi ra khi bạn hết các thanh ghi dùng một lần trong phiên bản đã biên dịch. Trung bình trình biên dịch sẽ làm tốt hơn nhiều so với con người cho một dự án lớn, nhưng không khó trong một dự án có kích thước phù hợp để tìm các vấn đề hiệu năng trong mã được biên dịch.
old_timer

14
Trên thực tế, câu trả lời ngắn gọn là: Trình biên dịch luôn nhanh hơn hoặc bằng tốc độ của C. Lý do là bạn có thể có lắp ráp mà không có C, nhưng bạn không thể có C mà không lắp ráp (ở dạng nhị phân, mà chúng ta ở cũ ngày gọi là "mã máy"). Điều đó nói rằng, câu trả lời dài là: Trình biên dịch C khá tốt trong việc tối ưu hóa và "suy nghĩ" về những điều bạn thường không nghĩ tới, vì vậy nó thực sự phụ thuộc vào kỹ năng của bạn, nhưng thông thường bạn luôn có thể đánh bại trình biên dịch C; nó vẫn chỉ là một phần mềm không thể nghĩ và lấy ý tưởng. Bạn cũng có thể viết trình biên dịch di động nếu bạn sử dụng macro và bạn kiên nhẫn.

11
Tôi hoàn toàn không đồng ý rằng câu trả lời cho câu hỏi này cần phải là "dựa trên ý kiến" - chúng hoàn toàn có thể khách quan - nó không giống như việc cố gắng so sánh hiệu suất của các ngôn ngữ thú cưng yêu thích, mà mỗi ngôn ngữ sẽ có điểm mạnh và thu hút. Đây là vấn đề hiểu được trình biên dịch có thể đưa chúng ta đi bao xa và từ đó tốt hơn là nên tiếp quản.
jsbueno 15/05/2015

21
Trước đây trong sự nghiệp của tôi, tôi đã viết rất nhiều trình biên dịch C và máy tính lớn tại một công ty phần mềm. Một trong những đồng nghiệp của tôi là cái mà tôi gọi là "purist purist" (mọi thứ phải là trình biên dịch), vì vậy tôi cá là anh ta có thể viết một thói quen nhất định chạy nhanh hơn C so với những gì anh ta có thể viết trong trình biên dịch. Tôi đã thắng. Nhưng trên hết, sau khi tôi thắng, tôi đã nói với anh ta rằng tôi muốn đặt cược lần thứ hai - rằng tôi có thể viết một cái gì đó nhanh hơn trong trình biên dịch chương trình so với chương trình C đã đánh bại anh ta khi đặt cược trước. Tôi cũng đã chiến thắng điều đó, chứng minh rằng hầu hết điều đó phụ thuộc vào kỹ năng và khả năng của lập trình viên hơn bất kỳ điều gì khác.
Valerie R

3
Trừ khi não của bạn có -O3cờ, tốt hơn hết là bạn nên để tối ưu hóa cho trình biên dịch C :-)
paxdiablo

Câu trả lời:


272

Dưới đây là một ví dụ thực tế: Nhân số điểm cố định trên các trình biên dịch cũ.

Chúng không chỉ hữu dụng trên các thiết bị không có điểm nổi, chúng tỏa sáng khi có độ chính xác vì chúng cung cấp cho bạn độ chính xác 32 bit với một lỗi có thể dự đoán được (float chỉ có 23 bit và khó dự đoán độ chính xác hơn). tức là đồng phục tuyệt đối độ chính xác trên toàn bộ phạm vi, thay vì độ chính xác tương đối gần đồng nhất ( float).


Trình biên dịch hiện đại tối ưu hóa ví dụ điểm cố định này một cách độc đáo, vì vậy đối với các ví dụ hiện đại hơn vẫn cần mã dành riêng cho trình biên dịch, hãy xem

  • Lấy phần cao của phép nhân số nguyên 64 bit : Phiên bản di động sử dụng uint64_tcho 32x32 => 64 bit không tối ưu hóa trên CPU 64 bit, do đó bạn cần nội tại hoặc __int128mã hiệu quả trên hệ thống 64 bit.
  • _umul128 trên Windows 32 bit : MSVC không phải lúc nào cũng làm tốt khi nhân các số nguyên 32 bit thành 64, vì vậy, nội tại đã giúp ích rất nhiều.

C không có toán tử nhân đầy đủ (kết quả 2N bit từ các đầu vào bit N). Cách thông thường để diễn đạt nó trong C là chuyển các đầu vào sang loại rộng hơn và hy vọng trình biên dịch nhận ra rằng các bit trên của các đầu vào không thú vị:

// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
  long long a_long = a; // cast to 64 bit.

  long long product = a_long * b; // perform multiplication

  return (int) (product >> 16);  // shift by the fixed point bias
}

Vấn đề với mã này là chúng tôi làm một cái gì đó không thể diễn đạt trực tiếp bằng ngôn ngữ C. Chúng tôi muốn nhân hai số 32 bit và nhận được kết quả 64 bit, trong đó chúng tôi trả về 32 bit giữa. Tuy nhiên, trong C nhân này không tồn tại. Tất cả những gì bạn có thể làm là quảng bá các số nguyên lên 64 bit và thực hiện phép nhân 64 * 64 = 64.

x86 (và ARM, MIPS và những người khác) tuy nhiên có thể thực hiện phép nhân trong một lệnh đơn. Một số trình biên dịch được sử dụng để bỏ qua thực tế này và tạo mã gọi hàm thư viện thời gian chạy để thực hiện phép nhân. Sự thay đổi của 16 cũng thường được thực hiện bởi một thói quen thư viện (cũng như x86 có thể thực hiện các ca như vậy).

Vì vậy, chúng tôi còn lại với một hoặc hai cuộc gọi thư viện chỉ để nhân lên. Điều này có hậu quả nghiêm trọng. Không chỉ là sự thay đổi chậm hơn, các thanh ghi phải được bảo toàn trong các lệnh gọi hàm và nó cũng không giúp nội tuyến và không kiểm soát mã.

Nếu bạn viết lại cùng một mã trong trình biên dịch (nội tuyến), bạn có thể tăng tốc đáng kể.

Thêm vào đó: sử dụng ASM không phải là cách tốt nhất để giải quyết vấn đề. Hầu hết các trình biên dịch cho phép bạn sử dụng một số hướng dẫn trình biên dịch ở dạng nội tại nếu bạn không thể diễn đạt chúng trong C. Trình biên dịch VS.NET2008 cho thấy 32 * 32 = 64 bit mul là __emul và dịch chuyển 64 bit là __ll_rshift.

Sử dụng nội tại, bạn có thể viết lại hàm theo cách mà trình biên dịch C có cơ hội hiểu những gì đang diễn ra. Điều này cho phép mã được nội tuyến, đăng ký được phân bổ, loại bỏ phổ biến phụ và lan truyền liên tục cũng có thể được thực hiện. Bạn sẽ nhận được rất nhiều cải tiến hiệu suất so với mã trình biên dịch viết tay theo cách đó.

Để tham khảo: Kết quả cuối cùng cho mul điểm cố định cho trình biên dịch VS.NET là:

int inline FixedPointMul (int a, int b)
{
    return (int) __ll_rshift(__emul(a,b),16);
}

Sự khác biệt hiệu suất của phân chia điểm cố định thậm chí còn lớn hơn. Tôi đã cải thiện đến hệ số 10 để phân chia mã điểm cố định nặng bằng cách viết một vài dòng asm-lines.


Sử dụng Visual C ++ 2013 cho cùng một mã lắp ráp cho cả hai cách.

gcc4.1 từ năm 2007 cũng tối ưu hóa phiên bản C tinh khiết độc đáo. (Trình thám hiểm trình biên dịch Godbolt không cài đặt bất kỳ phiên bản gcc nào trước đó, nhưng có lẽ các phiên bản GCC cũ hơn có thể thực hiện việc này mà không cần nội tại.)

Xem nguồn + asm cho x86 (32-bit) và ARM trên trình khám phá trình biên dịch Godbolt . (Thật không may, nó không có trình biên dịch nào đủ cũ để tạo mã xấu từ phiên bản C thuần túy đơn giản.)


CPU hiện đại có thể làm những việc C không có nhà khai thác cho ở tất cả , giống như popcnthoặc bit quét để tìm các bit set đầu tiên hoặc cuối cùng . (POSIX có ffs()chức năng, nhưng ngữ nghĩa của nó không khớp với x86 bsf/ bsr. Xem https://en.wikipedia.org/wiki/Find_first_set ).

Một số trình biên dịch đôi khi có thể nhận ra một vòng lặp mà đếm số lượng các thiết lập bit trong một số nguyên và biên dịch nó thành một popcnthướng dẫn (nếu được kích hoạt tại thời gian biên dịch), nhưng nó nhiều hơn đáng tin cậy để sử dụng __builtin_popcnttrong GNU C, hoặc trên x86 nếu bạn chỉ phần cứng nhắm mục tiêu với SSE4.2: _mm_popcnt_u32từ<immintrin.h> .

Hoặc trong C ++, assign để một std::bitset<32>và sử dụng .count(). (Đây là một trường hợp ngôn ngữ đã tìm ra cách để portably phơi bày một thực hiện tối ưu hóa của popcount thông qua thư viện chuẩn, trong một cách mà sẽ luôn luôn biên dịch một cái gì đó đúng, và có thể tận dụng lợi thế của bất cứ sự hỗ trợ mục tiêu.) Xem thêm https : //en.wikipedia.org/wiki/ Hamming_ weight # L Language_support .

Tương tự, ntohlcó thể biên dịch thành bswap(hoán đổi byte 32 bit để chuyển đổi cuối) trên một số triển khai C có nó.


Một lĩnh vực chính khác cho nội tại hoặc asm viết tay là vector hóa thủ công với các hướng dẫn SIMD. Trình biên dịch không tệ với các vòng lặp đơn giản như dst[i] += src[i] * 10.0;, nhưng thường làm xấu hoặc không tự động vector hóa khi mọi thứ trở nên phức tạp hơn. Ví dụ: bạn không thể nhận được bất cứ điều gì như Cách triển khai atoi bằng SIMD? được tạo tự động bởi trình biên dịch từ mã vô hướng.


6
Làm thế nào về những thứ như {x = c% d; y = c / d;}, trình biên dịch có đủ thông minh để biến nó thành một div hay idiv không?
Jens Bjornhager

4
Trên thực tế, một trình biên dịch tốt sẽ tạo ra mã tối ưu từ hàm đầu tiên. Che giấu mã nguồn bằng nội tại hoặc lắp ráp nội tuyến hoàn toàn không có lợi ích không phải là điều tốt nhất để làm.
kẻ lười biếng

65
Xin chào Slacker, tôi nghĩ rằng bạn chưa bao giờ phải làm việc với mã thời gian quan trọng trước khi ... lắp ráp nội tuyến có thể tạo ra sự khác biệt * lớn. Còn đối với trình biên dịch một nội tại cũng giống như số học bình thường trong C. Đó là thời điểm trong intrinsics. Họ cho phép bạn sử dụng một tính năng kiến ​​trúc mà không phải đối phó với những nhược điểm.
Nils Pipenbrinck

6
@slacker Trên thực tế, mã ở đây khá dễ đọc: mã nội tuyến thực hiện một thao tác duy nhất, ngay lập tức không ổn định khi đọc chữ ký phương thức. Mã chỉ bị mất chậm trong khả năng đọc khi một lệnh tối nghĩa được sử dụng. Vấn đề ở đây là chúng ta có một phương thức chỉ thực hiện một thao tác có thể nhận dạng rõ ràng và đó thực sự là cách tốt nhất để tạo mã có thể đọc được các hàm nguyên tử này. Nhân tiện, đây không phải là một nhận xét nhỏ như / * (a * b) >> 16 * / không thể giải thích ngay lập tức.
Dereckson

5
Công bằng mà nói, đây là một ví dụ nghèo nàn, ít nhất là ngày nay. Trình biên dịch C từ lâu đã có thể nhân bội 32x32 -> 64 ngay cả khi ngôn ngữ không cung cấp trực tiếp: họ nhận ra rằng khi bạn truyền các đối số 32 bit thành 64 bit và sau đó nhân chúng, thì không cần phải thực hiện phép nhân 64 bit đầy đủ, nhưng 32x32 -> 64 sẽ hoạt động tốt. Tôi đã kiểm tra và tất cả các kêu vang, gcc và MSVC trong phiên bản hiện tại của họ có được quyền này . Điều này không mới - tôi nhớ đã nhìn vào đầu ra của trình biên dịch và nhận thấy điều này một thập kỷ trước.
BeeOnRope

143

Cách đây nhiều năm, tôi đã dạy ai đó lập trình ở C. Bài tập là xoay đồ họa qua 90 độ. Anh ta trở lại với một giải pháp mất vài phút để hoàn thành, chủ yếu là vì anh ta đang sử dụng bội số và chia, v.v.

Tôi đã chỉ cho anh ta cách khắc phục sự cố bằng cách sử dụng dịch chuyển bit và thời gian xử lý giảm xuống còn khoảng 30 giây trên trình biên dịch không tối ưu hóa mà anh ta có.

Tôi vừa có một trình biên dịch tối ưu hóa và cùng một mã đã xoay đồ họa trong <5 giây. Tôi đã xem mã lắp ráp mà trình biên dịch đang tạo ra, và từ những gì tôi thấy đã quyết định ở đó và sau đó những ngày tôi viết trình biên dịch đã kết thúc.


3
Vâng, đó là một hệ thống đơn sắc một bit, cụ thể đó là các khối hình ảnh đơn sắc trên Atari ST.
lilburne

16
Trình biên dịch tối ưu hóa có biên dịch chương trình gốc hoặc phiên bản của bạn không?
Thorbjørn Ravn Andersen

Trên bộ xử lý nào? Trên 8086, tôi hy vọng rằng mã tối ưu cho xoay 8 x 8 sẽ tải DI với 16 bit dữ liệu bằng SI, lặp lại, add di,di / adc al,al / add di,di / adc ah,ahv.v. cho tất cả tám thanh ghi 8 bit, sau đó thực hiện lại tất cả 8 thanh ghi, sau đó lặp lại toàn bộ quy trình ba nhiều lần hơn và cuối cùng lưu bốn từ trong ax / bx / cx / dx. Không có cách nào một nhà lắp ráp sẽ đến gần đó.
supercat

1
Tôi thực sự không thể nghĩ đến bất kỳ nền tảng nào mà trình biên dịch có thể có được trong một hoặc hai yếu tố mã tối ưu cho một vòng quay 8x8.
supercat

65

Khá nhiều bất cứ lúc nào trình biên dịch thấy nổi điểm mã, một phiên bản viết tay sẽ nhanh hơn nếu bạn đang sử dụng một trình biên dịch xấu cũ. ( Cập nhật 2019: Nói chung điều này không đúng đối với các trình biên dịch hiện đại. Đặc biệt là khi biên dịch cho bất cứ điều gì khác hơn là x87; trình biên dịch có một thời gian dễ dàng hơn với SSE2 hoặc AVX cho toán vô hướng, hoặc bất kỳ phi x86 với một thanh ghi bộ FP phẳng, không giống như x87 của đăng ký stack.)

Lý do chính là trình biên dịch không thể thực hiện bất kỳ tối ưu hóa mạnh mẽ nào. Xem bài viết này từ MSDN để thảo luận về chủ đề này. Đây là một ví dụ trong đó phiên bản lắp ráp có tốc độ gấp đôi so với phiên bản C (được biên dịch với VS2K5):

#include "stdafx.h"
#include <windows.h>

float KahanSum(const float *data, int n)
{
   float sum = 0.0f, C = 0.0f, Y, T;

   for (int i = 0 ; i < n ; ++i) {
      Y = *data++ - C;
      T = sum + Y;
      C = T - sum - Y;
      sum = T;
   }

   return sum;
}

float AsmSum(const float *data, int n)
{
  float result = 0.0f;

  _asm
  {
    mov esi,data
    mov ecx,n
    fldz
    fldz
l1:
    fsubr [esi]
    add esi,4
    fld st(0)
    fadd st(0),st(2)
    fld st(0)
    fsub st(0),st(3)
    fsub st(0),st(2)
    fstp st(2)
    fstp st(2)
    loop l1
    fstp result
    fstp result
  }

  return result;
}

int main (int, char **)
{
  int count = 1000000;

  float *source = new float [count];

  for (int i = 0 ; i < count ; ++i) {
    source [i] = static_cast <float> (rand ()) / static_cast <float> (RAND_MAX);
  }

  LARGE_INTEGER start, mid, end;

  float sum1 = 0.0f, sum2 = 0.0f;

  QueryPerformanceCounter (&start);

  sum1 = KahanSum (source, count);

  QueryPerformanceCounter (&mid);

  sum2 = AsmSum (source, count);

  QueryPerformanceCounter (&end);

  cout << "  C code: " << sum1 << " in " << (mid.QuadPart - start.QuadPart) << endl;
  cout << "asm code: " << sum2 << " in " << (end.QuadPart - mid.QuadPart) << endl;

  return 0;
}

Và một số số từ PC của tôi đang chạy bản dựng phát hành mặc định * :

  C code: 500137 in 103884668
asm code: 500137 in 52129147

Không thích thú, tôi đổi vòng lặp với dec / jnz và nó không tạo ra sự khác biệt nào với thời gian - đôi khi nhanh hơn, đôi khi chậm hơn. Tôi đoán khía cạnh hạn chế bộ nhớ lùn tối ưu hóa khác. (Chú ý: nhiều khả năng FP trễ cổ chai là đủ để che giấu thêm chi phí loop. Làm hai summations Kahan song song cho các yếu tố chẵn / lẻ, và thêm những lúc kết thúc, có thể có thể tăng tốc độ này tăng hệ số 2. )

Rất tiếc, tôi đang chạy một phiên bản mã hơi khác và nó đã xuất ra các số sai vòng (tức là C nhanh hơn!). Đã sửa và cập nhật kết quả.


20
Hoặc trong GCC, bạn có thể cởi trói cho các nhà biên dịch về tối ưu hóa điểm nổi (miễn là bạn hứa sẽ không làm bất cứ điều gì với vô số hoặc NaN) bằng cách sử dụng cờ -ffast-math. Chúng có mức tối ưu hóa, -Ofasthiện tương đương với -O3 -ffast-math, nhưng trong tương lai có thể bao gồm nhiều tối ưu hóa hơn có thể dẫn đến việc tạo mã không chính xác trong các trường hợp góc (chẳng hạn như mã dựa trên IEEE NaN).
David Stone

2
Vâng, phao không giao hoán, trình biên dịch phải thực hiện CHÍNH XÁC những gì bạn đã viết, về cơ bản những gì @DavidStone đã nói.
Alec Teal

2
Bạn đã thử toán SSE chưa? Hiệu suất là một trong những lý do MS từ bỏ x87 hoàn toàn trong x86_64 và nhân đôi dài 80 bit trong x86
phuclv

4
@Praxeolitic: FP add là giao hoán ( a+b == b+a), nhưng không liên kết (sắp xếp lại các hoạt động, do đó làm tròn các trung gian là khác nhau). re: mã này: Tôi không nghĩ x87 không bị lỗi và một loophướng dẫn là một minh chứng rất tuyệt vời về asm nhanh. looprõ ràng không thực sự là một nút cổ chai vì độ trễ của FP. Tôi không chắc liệu anh ta có thực hiện các hoạt động của FP hay không; x87 là khó cho con người để đọc. Hai fstp resultsinsns ở cuối rõ ràng là không tối ưu. Popping kết quả bổ sung từ ngăn xếp sẽ được thực hiện tốt hơn với một cửa hàng không. Giống như fstp st(0)IIRC.
Peter Cordes

2
@PeterCordes: Một kết quả thú vị của việc thêm giao hoán là trong khi 0 + x và x + 0 tương đương với nhau, không phải luôn luôn tương đương với x.
supercat

58

Không đưa ra bất kỳ ví dụ cụ thể hoặc bằng chứng trình lược tả nào, bạn có thể viết trình biên dịch tốt hơn trình biên dịch khi bạn biết nhiều hơn trình biên dịch.

Trong trường hợp chung, trình biên dịch C hiện đại biết nhiều hơn về cách tối ưu hóa mã được đề cập: nó biết cách hoạt động của đường ống xử lý, nó có thể cố gắng sắp xếp lại các hướng dẫn nhanh hơn so với con người, v.v. một máy tính tốt bằng hoặc tốt hơn máy nghe nhạc người giỏi nhất cho các trò chơi cờ, v.v ... đơn giản vì nó có thể giúp tìm kiếm trong không gian vấn đề nhanh hơn hầu hết con người. Mặc dù về mặt lý thuyết bạn có thể hoạt động tốt như máy tính trong một trường hợp cụ thể, bạn chắc chắn không thể làm điều đó với cùng tốc độ, khiến nó không thể thực hiện được hơn một vài trường hợp (tức là trình biên dịch chắc chắn sẽ vượt trội hơn bạn nếu bạn cố gắng viết nhiều hơn một vài thói quen trong trình biên dịch chương trình).

Mặt khác, có những trường hợp trình biên dịch không có nhiều thông tin - tôi chủ yếu nói khi làm việc với các dạng phần cứng bên ngoài khác nhau, trong đó trình biên dịch không có kiến ​​thức. Ví dụ chính có lẽ là trình điều khiển thiết bị, trong đó trình biên dịch kết hợp với kiến ​​thức sâu sắc về phần cứng của con người có thể mang lại kết quả tốt hơn trình biên dịch C có thể làm.

Những người khác đã đề cập đến các hướng dẫn mục đích đặc biệt, đó là những gì tôi đang nói trong đoạn trên - hướng dẫn mà trình biên dịch có thể bị hạn chế hoặc không có kiến ​​thức nào cả, giúp con người có thể viết mã nhanh hơn.


Nói chung, tuyên bố này là đúng. Trình biên dịch thực hiện tốt nhất cho DWIW, nhưng trong một số trường hợp biên, trình biên dịch mã hóa tay hoàn thành công việc khi hiệu suất thời gian thực là bắt buộc.
spoulson

1
@Liedman: "nó có thể cố gắng sắp xếp lại các hướng dẫn nhanh hơn so với con người". OCaml được biết đến là nhanh và đáng ngạc nhiên, trình biên dịch mã gốc của nó ocamloptbỏ qua lập lịch hướng dẫn trên x86 và thay vào đó, để nó lên CPU vì nó có thể sắp xếp lại hiệu quả hơn trong thời gian chạy.
Jon Harrop

1
Trình biên dịch hiện đại làm rất nhiều, và sẽ mất quá nhiều thời gian để làm bằng tay, nhưng chúng không ở đâu hoàn hảo. Tìm kiếm trình theo dõi lỗi của gcc hoặc llvm để tìm lỗi "tối ưu hóa bị bỏ lỡ". Có nhiều. Ngoài ra, khi viết bằng asm, bạn có thể dễ dàng tận dụng các điều kiện tiên quyết như "đầu vào này không thể âm" mà trình biên dịch khó có thể chứng minh.
Peter Cordes

48

Trong công việc của tôi, có ba lý do để tôi biết và sử dụng lắp ráp. Theo thứ tự quan trọng:

  1. Gỡ lỗi - Tôi thường nhận được mã thư viện có lỗi hoặc tài liệu không đầy đủ. Tôi tìm ra những gì nó đang làm bằng cách bước vào cấp độ lắp ráp. Tôi phải làm điều này khoảng một lần một tuần. Tôi cũng sử dụng nó như một công cụ để gỡ lỗi các vấn đề trong đó mắt tôi không phát hiện ra lỗi thành ngữ trong C / C ++ / C #. Nhìn vào hội đã vượt qua điều đó.

  2. Tối ưu hóa - trình biên dịch thực hiện khá tốt trong việc tối ưu hóa, nhưng tôi chơi ở một sân bóng khác so với hầu hết. Tôi viết mã xử lý hình ảnh thường bắt đầu bằng mã trông như thế này:

    for (int y=0; y < imageHeight; y++) {
        for (int x=0; x < imageWidth; x++) {
           // do something
        }
    }

    "làm một phần gì đó" thường xảy ra theo thứ tự vài triệu lần (nghĩa là từ 3 đến 30). Bằng cách loại bỏ các chu kỳ trong giai đoạn "làm một cái gì đó", hiệu suất đạt được sẽ được phóng to. Tôi thường không bắt đầu ở đó - Tôi thường bắt đầu bằng cách viết mã để làm việc trước, sau đó cố hết sức để cấu trúc lại C để tự nhiên tốt hơn (thuật toán tốt hơn, tải ít hơn trong vòng lặp, v.v.). Tôi thường cần đọc lắp ráp để xem những gì đang xảy ra và hiếm khi cần phải viết nó. Tôi làm điều này có thể hai hoặc ba tháng một lần.

  3. làm một cái gì đó ngôn ngữ sẽ không cho phép tôi. Chúng bao gồm - có được kiến ​​trúc bộ xử lý và các tính năng bộ xử lý cụ thể, truy cập các cờ không có trong CPU (người đàn ông, tôi thực sự muốn C cấp cho bạn quyền truy cập vào cờ mang theo), v.v. Tôi làm điều này có thể một hoặc hai năm một lần.


Bạn không gạch vòng của bạn? :-)
Jon Harrop

1
@plinth: làm thế nào để bạn có nghĩa là "chu kỳ cạo"?
lang2

@ lang2: có nghĩa là loại bỏ càng nhiều thời gian không cần thiết trong vòng lặp bên trong càng tốt - bất cứ điều gì trình biên dịch không quản lý để rút ra, có thể bao gồm sử dụng đại số để nhấc một bội số ra khỏi một vòng lặp để thêm vào ở bên trong, v.v.
plinth

1
Ốp lát vòng có vẻ không cần thiết nếu bạn chỉ thực hiện một lần truyền dữ liệu.
James M. Lay

@ JamesM.Lay: Nếu bạn chỉ chạm vào mọi yếu tố một lần, một trật tự truyền tải tốt hơn có thể cung cấp cho bạn địa phương không gian. (ví dụ: sử dụng tất cả các byte của một dòng bộ đệm mà bạn đã chạm vào, thay vì lặp xuống các cột của ma trận bằng một phần tử trên mỗi dòng bộ đệm.)
Peter Cordes

42

Chỉ khi sử dụng một số lệnh mục đích đặc biệt, trình biên dịch không hỗ trợ.

Để tối đa hóa khả năng tính toán của CPU hiện đại với nhiều đường ống và phân nhánh dự đoán, bạn cần cấu trúc chương trình lắp ráp theo cách khiến cho con người gần như không thể viết b) thậm chí không thể duy trì.

Ngoài ra, các thuật toán, cấu trúc dữ liệu và quản lý bộ nhớ tốt hơn sẽ cung cấp cho bạn ít nhất một thứ tự hiệu năng lớn hơn so với các tối ưu hóa vi mô bạn có thể thực hiện khi lắp ráp.


4
+1, mặc dù câu cuối cùng không thực sự thuộc về cuộc thảo luận này - người ta sẽ cho rằng trình biên dịch chỉ hoạt động sau khi tất cả các cải tiến có thể của thuật toán, v.v.
mghie

18
@Matt: ASM viết tay thường tốt hơn rất nhiều đối với một số CPU nhỏ của EE với sự hỗ trợ của trình biên dịch nhà cung cấp xảo quyệt.
Zan Lynx

5
"Chỉ khi sử dụng một số bộ hướng dẫn mục đích đặc biệt" ?? Bạn có thể chưa bao giờ viết một đoạn mã asm được tối ưu hóa bằng tay trước đây. Một kiến ​​thức gần gũi vừa phải về kiến ​​trúc bạn đang làm việc mang lại cơ hội tốt cho bạn để tạo mã tốt hơn (kích thước và tốc độ) so với trình biên dịch của bạn. Rõ ràng, như @mghie nhận xét, bạn luôn bắt đầu mã hóa các thuật toán tốt nhất bạn có thể đi kèm với vấn đề của mình. Ngay cả đối với các trình biên dịch rất tốt, bạn thực sự phải viết mã C của mình theo cách dẫn trình biên dịch đến mã được biên dịch tốt nhất. Nếu không, mã được tạo sẽ là tối ưu phụ.
ysap

2
@ysap - trên các máy tính thực tế (không phải chip nhúng yếu) trong sử dụng trong thế giới thực, mã "tối ưu" sẽ không nhanh hơn vì đối với bất kỳ tập dữ liệu lớn nào, hiệu suất của bạn sẽ bị giới hạn bởi quyền truy cập bộ nhớ và lỗi trang ( và nếu bạn không có một bộ dữ liệu lớn thì điều này cũng sẽ nhanh chóng và không có điểm nào tối ưu hóa nó) - những ngày đó tôi làm việc chủ yếu ở C # (thậm chí không phải c) và hiệu suất đạt được từ trình quản lý bộ nhớ nén cân nhắc chi phí chung của việc thu gom rác, nén và biên dịch JIT.
Nir

4
+1 để nói rằng trình biên dịch (đặc biệt là JIT) có thể thực hiện công việc tốt hơn con người, nếu chúng được tối ưu hóa cho phần cứng mà chúng đang chạy.
Sebastian

38

Mặc dù C "gần" với thao tác ở mức thấp của dữ liệu 8 bit, 16 bit, 32 bit, 64 bit, có một số phép toán không được C hỗ trợ, thường có thể được thực hiện một cách tao nhã trong một số hướng dẫn lắp ráp nhất định bộ:

  1. Phép nhân điểm cố định: Tích của hai số 16 bit là số 32 bit. Nhưng các quy tắc trong C nói rằng tích của hai số 16 bit là số 16 bit và tích của hai số 32 bit là số 32 bit - nửa dưới trong cả hai trường hợp. Nếu bạn muốn nửa trên của bội số 16x16 hoặc nhân 32x32, bạn phải chơi trò chơi với trình biên dịch. Phương pháp chung là truyền tới độ rộng bit lớn hơn mức cần thiết, nhân, dịch chuyển xuống và đúc lại:

    int16_t x, y;
    // int16_t is a typedef for "short"
    // set x and y to something
    int16_t prod = (int16_t)(((int32_t)x*y)>>16);`

    Trong trường hợp này, trình biên dịch có thể đủ thông minh để biết rằng bạn thực sự chỉ đang cố gắng lấy nửa trên của bội số 16x16 và thực hiện đúng với 16x16multiply gốc của máy. Hoặc có thể là ngu ngốc và yêu cầu một cuộc gọi thư viện để thực hiện phép nhân 32x32 quá mức vì bạn chỉ cần 16 bit của sản phẩm - nhưng tiêu chuẩn C không cung cấp cho bạn bất kỳ cách nào để thể hiện bản thân.

  2. Một số thao tác bẻ khóa (xoay / mang):

    // 256-bit array shifted right in its entirety:
    uint8_t x[32];
    for (int i = 32; --i > 0; )
    {
       x[i] = (x[i] >> 1) | (x[i-1] << 7);
    }
    x[0] >>= 1;

    Điều này không quá không phù hợp trong C, nhưng một lần nữa, trừ khi trình biên dịch đủ thông minh để nhận ra những gì bạn đang làm, nó sẽ làm rất nhiều việc "không cần thiết". Nhiều bộ hướng dẫn lắp ráp cho phép bạn xoay hoặc dịch chuyển sang trái / phải với kết quả trong thanh ghi mang, do đó bạn có thể thực hiện các hướng dẫn trên trong 34 hướng dẫn: tải một con trỏ đến đầu mảng, xóa thực hiện và thực hiện 32 8- bit phải dịch chuyển, sử dụng tự động tăng trên con trỏ.

    Đối với một ví dụ khác, có các thanh ghi dịch chuyển phản hồi tuyến tính (LFSR) được thực hiện một cách tao nhã trong lắp ráp: Lấy một đoạn N bit (8, 16, 32, 64, 128, v.v.), dịch chuyển toàn bộ thứ phải bằng 1 (xem bên trên thuật toán), sau đó nếu kết quả mang là 1 thì bạn XOR theo mẫu bit đại diện cho đa thức.

Phải nói rằng, tôi sẽ không dùng đến những kỹ thuật này trừ khi tôi gặp phải những hạn chế nghiêm trọng về hiệu suất. Như những người khác đã nói, lắp ráp tài liệu / gỡ lỗi / kiểm tra / bảo trì khó hơn nhiều so với mã C: hiệu suất đạt được đi kèm với một số chi phí nghiêm trọng.

chỉnh sửa: 3. Có thể phát hiện tràn trong lắp ráp (thực sự không thể làm điều đó trong C), điều này làm cho một số thuật toán dễ dàng hơn nhiều.


23

Câu trả lời ngắn? Đôi khi.

Về mặt kỹ thuật, mọi sự trừu tượng đều có chi phí và ngôn ngữ lập trình là sự trừu tượng hóa về cách thức hoạt động của CPU. C tuy nhiên rất gần. Cách đây nhiều năm, tôi nhớ mình đã cười rất lớn khi đăng nhập vào tài khoản UNIX của mình và nhận được thông báo may mắn sau đây (khi những thứ đó phổ biến):

Ngôn ngữ lập trình C - Một ngôn ngữ kết hợp tính linh hoạt của ngôn ngữ lắp ráp với sức mạnh của ngôn ngữ lắp ráp.

Thật buồn cười vì nó đúng: C giống như ngôn ngữ lắp ráp di động.

Điều đáng chú ý là ngôn ngữ lắp ráp chỉ chạy theo cách bạn viết. Tuy nhiên, có một trình biên dịch ở giữa C và ngôn ngữ hợp ngữ mà nó tạo ra và điều đó cực kỳ quan trọng bởi vì mã C của bạn nhanh đến mức nào có liên quan nhiều đến việc trình biên dịch của bạn tốt như thế nào.

Khi gcc xuất hiện, một trong những điều khiến nó trở nên phổ biến là nó thường tốt hơn nhiều so với các trình biên dịch C được vận chuyển với nhiều hương vị UNIX thương mại. Không chỉ là ANSI C (không có rác K & R C này), còn mạnh mẽ hơn và thường được sản xuất mã tốt hơn (nhanh hơn). Không phải luôn luôn nhưng thường xuyên.

Tôi nói với bạn tất cả điều này bởi vì không có quy tắc về tốc độ của C và trình biên dịch vì không có tiêu chuẩn khách quan nào cho C.

Tương tự, trình biên dịch thay đổi rất nhiều tùy thuộc vào bộ xử lý bạn đang chạy, thông số hệ thống của bạn, bộ hướng dẫn nào bạn đang sử dụng, v.v. Trong lịch sử đã có hai họ kiến ​​trúc CPU: CISC và RISC. Người chơi lớn nhất trong CISC đã và vẫn là kiến ​​trúc Intel x86 (và tập lệnh). RISC thống trị thế giới UNIX (MIPS6000, Alpha, Sparc, v.v.). CISC đã chiến thắng trong cuộc chiến vì trái tim và khối óc.

Dù sao, sự khôn ngoan phổ biến khi tôi còn là một nhà phát triển trẻ tuổi là x86 viết tay thường có thể nhanh hơn C rất nhiều bởi vì cách kiến ​​trúc hoạt động, nó có một sự phức tạp được hưởng lợi từ một con người làm việc đó. Mặt khác, RISC dường như được thiết kế cho các trình biên dịch nên không ai (tôi biết) đã viết rằng trình biên dịch Sparc. Tôi chắc chắn những người như vậy tồn tại nhưng không còn nghi ngờ gì nữa, cả hai đều phát điên và được thể chế hóa ngay bây giờ.

Bộ hướng dẫn là một điểm quan trọng ngay cả trong cùng một họ bộ xử lý. Một số bộ xử lý Intel có các phần mở rộng như SSE đến SSE4. AMD đã có hướng dẫn SIMD của riêng họ. Lợi ích của ngôn ngữ lập trình như C là ai đó có thể viết thư viện của họ để nó được tối ưu hóa cho bất kỳ bộ xử lý nào bạn đang chạy. Đó là công việc khó khăn trong lắp ráp.

Vẫn có những tối ưu hóa mà bạn có thể thực hiện trong trình biên dịch chương trình mà không trình biên dịch nào có thể thực hiện được và thuật toán trình biên dịch được viết tốt sẽ nhanh hoặc nhanh hơn tương đương với C. Câu hỏi lớn hơn là: nó có đáng không?

Cuối cùng, mặc dù trình biên dịch chương trình là một sản phẩm thời đó và phổ biến hơn vào thời điểm chu kỳ CPU đắt đỏ. Ngày nay, một CPU có giá 5-10 đô la để sản xuất (Intel Atom) có thể làm được khá nhiều thứ mà bất cứ ai cũng muốn. Lý do thực sự duy nhất để viết trình biên dịch chương trình ngày nay là vì những thứ cấp thấp như một số phần của hệ điều hành (thậm chí phần lớn nhân Linux được viết bằng C), trình điều khiển thiết bị, có thể là thiết bị nhúng (mặc dù C có xu hướng thống trị ở đó quá) và như vậy. Hoặc chỉ cho đá (có phần bạo dâm).


Có rất nhiều người đã sử dụng trình biên dịch ARM làm ngôn ngữ được lựa chọn trên các máy Acorn (đầu những năm 90). IIRC họ nói rằng bộ hướng dẫn risc nhỏ làm cho nó dễ dàng và thú vị hơn. Nhưng tôi nghi ngờ đó là vì trình biên dịch C là sự xuất hiện muộn của Acorn và trình biên dịch C ++ chưa bao giờ kết thúc.
Andrew M

3
"... bởi vì không có tiêu chuẩn chủ quan cho C." Ý bạn là khách quan .
Thomas

@AndrewM: Vâng, tôi đã viết các ứng dụng ngôn ngữ hỗn hợp trong trình biên dịch BASIC và ARM trong khoảng 10 năm. Tôi đã học C trong thời gian đó nhưng nó không hữu ích lắm vì nó cồng kềnh như trình biên dịch và chậm hơn. Norcroft đã thực hiện một số tối ưu hóa tuyệt vời nhưng tôi nghĩ rằng tập lệnh có điều kiện là một vấn đề đối với các trình biên dịch trong ngày.
Jon Harrop

1
@AndrewM: tốt, thực sự ARM là loại RISC được thực hiện ngược. Các ISA RISC khác được thiết kế bắt đầu với những gì trình biên dịch sẽ sử dụng. ARM ARM dường như đã được thiết kế bắt đầu với những gì CPU cung cấp (bộ chuyển đổi nòng súng, cờ điều kiện → hãy để lộ chúng trong mỗi hướng dẫn).
ninjalj

16

Một trường hợp sử dụng có thể không áp dụng nữa nhưng vì niềm vui thích của bạn: Trên Amiga, CPU và chip đồ họa / âm thanh sẽ chiến đấu để truy cập vào một khu vực RAM nhất định (cụ thể là 2 MB RAM đầu tiên). Vì vậy, khi bạn chỉ có 2 MB RAM (hoặc ít hơn), hiển thị đồ họa phức tạp cộng với âm thanh phát sẽ giết chết hiệu suất của CPU.

Trong trình biên dịch chương trình, bạn có thể xen kẽ mã của mình một cách thông minh đến mức CPU chỉ cố gắng truy cập RAM khi các chip đồ họa / âm thanh bận rộn bên trong (tức là khi xe buýt rảnh). Vì vậy, bằng cách sắp xếp lại các hướng dẫn của bạn, sử dụng thông minh bộ đệm CPU, thời gian xe buýt, bạn có thể đạt được một số hiệu ứng mà đơn giản là không thể sử dụng bất kỳ ngôn ngữ cấp cao nào vì bạn phải đặt mọi lệnh, thậm chí chèn NOP ở đây và ở đó để giữ cho nhiều chip ra khỏi radar của nhau.

Đó là một lý do khác tại sao lệnh NOP (Không hoạt động - không làm gì) của CPU thực sự có thể làm cho toàn bộ ứng dụng của bạn chạy nhanh hơn.

[EDIT] Tất nhiên, kỹ thuật này phụ thuộc vào một thiết lập phần cứng cụ thể. Đó là lý do chính khiến nhiều trò chơi Amiga không thể đối phó với CPU nhanh hơn: Thời gian của các hướng dẫn đã bị tắt.


Amiga không có 16 MB RAM chip, giống như 512 kB đến 2 MB tùy thuộc vào chipset. Ngoài ra, rất nhiều game Amiga không hoạt động với CPU nhanh hơn do các kỹ thuật như bạn mô tả.
bk1e

1
@ bk1e - Amiga đã sản xuất một loạt lớn các mẫu máy tính khác nhau, Amiga 500 được vận chuyển với ram 512K được mở rộng lên 1Meg trong trường hợp của tôi. amigahistory.co.uk/amiedevsys.html là một amiga với 128Meg Ram
David Waters

@ bk1e: Tôi đứng sửa. Bộ nhớ của tôi có thể làm tôi thất vọng nhưng RAM không bị giới hạn trong không gian địa chỉ 24 bit đầu tiên (tức là 16MB)? Và Fast đã được ánh xạ trên đó?
Aaron Digulla

@Aaron Digulla: Wikipedia có thêm thông tin về sự khác biệt giữa chip / RAM nhanh / chậm: en.wikipedia.org/wiki/Amiga_Chip_RAM
bk1e

@ bk1e: Sai lầm của tôi. CPU 68k chỉ có 24 làn địa chỉ, đó là lý do tại sao tôi có 16 MB trong đầu.
Aaron Digulla

15

Điểm một mà không phải là câu trả lời.
Ngay cả khi bạn không bao giờ lập trình trong nó, tôi thấy hữu ích khi biết ít nhất một bộ hướng dẫn trình biên dịch. Đây là một phần của các lập trình viên không bao giờ kết thúc nhiệm vụ để biết nhiều hơn và do đó sẽ tốt hơn. Cũng hữu ích khi bước vào các khung công tác mà bạn không có mã nguồn và có ít nhất một ý tưởng sơ bộ về những gì đang diễn ra. Nó cũng giúp bạn hiểu JavaByteCode và .Net IL vì cả hai đều tương tự như trình biên dịch chương trình.

Để trả lời câu hỏi khi bạn có một lượng nhỏ mã hoặc một lượng lớn thời gian. Hữu ích nhất để sử dụng trong các chip nhúng, trong đó độ phức tạp chip thấp và cạnh tranh kém trong các trình biên dịch nhắm vào các chip này có thể giúp cân bằng lợi ích của con người. Ngoài ra, đối với các thiết bị bị hạn chế, bạn thường giao dịch với kích thước mã / kích thước bộ nhớ / hiệu suất theo cách khó có thể hướng dẫn trình biên dịch thực hiện. ví dụ: tôi biết hành động người dùng này không được gọi thường xuyên nên tôi sẽ có kích thước mã nhỏ và hiệu suất kém, nhưng chức năng khác trông tương tự này được sử dụng mỗi giây nên tôi sẽ có kích thước mã lớn hơn và hiệu suất nhanh hơn. Đó là loại đánh đổi một lập trình viên lắp ráp lành nghề có thể sử dụng.

Tôi cũng muốn thêm vào đó là rất nhiều nền tảng trung gian nơi bạn có thể viết mã trong biên dịch C và kiểm tra hội được tạo ra, sau đó thay đổi mã C của bạn hoặc chỉnh sửa và duy trì dưới dạng lắp ráp.

Bạn tôi làm việc trên các bộ điều khiển vi mô, hiện đang sử dụng chip để điều khiển động cơ điện nhỏ. Ông làm việc trong sự kết hợp của c cấp thấp và hội. Anh ấy từng nói với tôi về một ngày làm việc hiệu quả khi anh ấy giảm vòng lặp chính từ 48 hướng dẫn xuống 43. Anh ấy cũng phải đối mặt với các lựa chọn như mã đã phát triển để lấp đầy chip 256k và doanh nghiệp đang muốn một tính năng mới, bạn có

  1. Xóa một tính năng hiện có
  2. Giảm kích thước của một số hoặc tất cả các tính năng hiện có có thể bằng chi phí hiệu suất.
  3. Ủng hộ chuyển sang một chip lớn hơn với chi phí cao hơn, tiêu thụ điện năng cao hơn và yếu tố hình thức lớn hơn.

Tôi muốn thêm vào như một nhà phát triển thương mại với khá nhiều danh mục đầu tư hoặc ngôn ngữ, nền tảng, loại ứng dụng mà tôi chưa bao giờ cảm thấy cần phải lao vào viết lắp ráp. Tôi có bao giờ luôn đánh giá cao những kiến ​​thức tôi có được về nó. Và đôi khi gỡ lỗi vào nó.

Tôi biết tôi đã trả lời nhiều hơn câu hỏi "tại sao tôi nên học trình biên dịch" nhưng tôi cảm thấy đây là một câu hỏi quan trọng hơn khi nào thì nó nhanh hơn.

Vì vậy, hãy thử một lần nữa Bạn nên suy nghĩ về lắp ráp

  • làm việc trên chức năng hệ điều hành cấp thấp
  • Làm việc trên một trình biên dịch.
  • Làm việc trên một con chip cực kỳ hạn chế, hệ thống nhúng, v.v.

Hãy nhớ so sánh lắp ráp của bạn với trình biên dịch được tạo để xem cái nào nhanh hơn / nhỏ hơn / tốt hơn.

David.


4
+1 để xem xét các ứng dụng nhúng trên các chip nhỏ. Quá nhiều kỹ sư phần mềm ở đây không xem xét nhúng hoặc nghĩ rằng đó có nghĩa là điện thoại thông minh (32 bit, RAM MB, flash MB).
Martin

1
Ứng dụng nhúng thời gian là một ví dụ tuyệt vời! Thường có những hướng dẫn kỳ lạ (ngay cả những hướng dẫn thực sự đơn giản như avr'ssbicbi) mà trình biên dịch đã sử dụng (và đôi khi vẫn làm) không tận dụng hết, do kiến ​​thức hạn chế về phần cứng của chúng.
felixphew

15

Tôi ngạc nhiên không ai nói điều này. Các strlen()chức năng nhanh hơn nhiều nếu được viết trong lắp ráp! Trong C, điều tốt nhất bạn có thể làm là

int c;
for(c = 0; str[c] != '\0'; c++) {}

trong khi lắp ráp, bạn có thể tăng tốc đáng kể:

mov esi, offset string
mov edi, esi
xor ecx, ecx

lp:
mov ax, byte ptr [esi]
cmp al, cl
je  end_1
cmp ah, cl
je end_2
mov bx, byte ptr [esi + 2]
cmp bl, cl
je end_3
cmp bh, cl
je end_4
add esi, 4
jmp lp

end_4:
inc esi

end_3:
inc esi

end_2:
inc esi

end_1:
inc esi

mov ecx, esi
sub ecx, edi

chiều dài là trong ecx. Điều này so sánh 4 ký tự cùng một lúc, vì vậy nó nhanh hơn 4 lần. Và hãy nghĩ rằng sử dụng từ bậc cao của eax và ebx, nó sẽ trở nên nhanh hơn 8 lần so với thói quen C trước đó!


3
Làm thế nào điều này so sánh với những người trong strchr.nfshost.com/optimized_strlen_feft ?
ninjalj

@ninjalj: chúng giống nhau :) Tôi không nghĩ nó có thể được thực hiện theo cách này trong C. Tôi nghĩ nó có thể được cải thiện đôi chút
BlackBear

Vẫn còn một thao tác AND bit trước mỗi so sánh trong mã C. Có thể trình biên dịch sẽ đủ thông minh để giảm mức đó xuống các so sánh byte cao và thấp, nhưng tôi sẽ không đặt cược tiền vào nó. Thực sự có một thuật toán vòng lặp nhanh hơn dựa trên đặc tính (word & 0xFEFEFEFF) & (~word + 0x80808080)bằng 0 nếu tất cả các byte trong từ đều khác không.
dùng2310967

@MichaWiedenmann đúng, tôi nên tải bx sau khi so sánh hai ký tự trong ax. Cảm ơn bạn
BlackBear

14

Các hoạt động ma trận sử dụng các hướng dẫn SIMD có thể nhanh hơn mã trình biên dịch.


Một số trình biên dịch (VectorC, nếu tôi nhớ chính xác) tạo mã SIMD, do đó, thậm chí đó có thể không còn là đối số để sử dụng mã lắp ráp.
OregonGhost

Trình biên dịch tạo mã nhận biết SSE, do đó, đối số đó không đúng
vartec

5
Đối với nhiều tình huống, bạn có thể sử dụng SSE intrisics thay vì lắp ráp. Điều này sẽ làm cho mã của bạn dễ di chuyển hơn (gcc visual c ++, 64bit, 32bit, v.v.) và bạn không phải thực hiện phân bổ đăng ký.
Laserallan

1
Chắc chắn bạn sẽ làm thế, nhưng câu hỏi không hỏi tôi nên sử dụng lắp ráp ở đâu thay vì C. Nó nói khi trình biên dịch C không tạo ra mã tốt hơn. Tôi giả sử một nguồn C không sử dụng các cuộc gọi SSE trực tiếp hoặc lắp ráp nội tuyến.
Mehrdad Afshari

9
Mehrdad là đúng, mặc dù. Bắt SSE đúng là khá khó đối với trình biên dịch và thậm chí trong các tình huống rõ ràng (đối với con người, đó là) hầu hết các trình biên dịch không sử dụng nó.
Konrad Rudolph

13

Tôi không thể đưa ra các ví dụ cụ thể vì nó đã quá nhiều năm trước, nhưng có rất nhiều trường hợp trình biên dịch viết tay có thể thực hiện bất kỳ trình biên dịch nào. Lý do tại sao:

  • Bạn có thể đi chệch khỏi các quy ước gọi, chuyển các đối số trong các thanh ghi.

  • Bạn có thể cân nhắc cẩn thận cách sử dụng các thanh ghi và tránh lưu trữ các biến trong bộ nhớ.

  • Đối với những thứ như bảng nhảy, bạn có thể tránh phải giới hạn - kiểm tra chỉ mục.

Về cơ bản, trình biên dịch thực hiện công việc tối ưu hóa khá tốt và gần như luôn luôn "đủ tốt", nhưng trong một số trường hợp (như kết xuất đồ họa) khi bạn trả giá đắt cho mỗi chu kỳ, bạn có thể sử dụng phím tắt vì bạn biết mã , nơi một trình biên dịch không thể bởi vì nó phải ở bên an toàn.

Trong thực tế, tôi đã nghe nói về một số mã kết xuất đồ họa trong đó một thói quen, như thói quen vẽ đường thẳng hoặc điền đa giác, thực sự tạo ra một khối mã máy nhỏ trên ngăn xếp và thực thi nó ở đó, để tránh việc ra quyết định liên tục về kiểu đường kẻ, chiều rộng, hoa văn, v.v.

Điều đó nói rằng, những gì tôi muốn một trình biên dịch làm là tạo mã lắp ráp tốt cho tôi nhưng không quá thông minh, và họ chủ yếu làm điều đó. Trên thực tế, một trong những điều tôi ghét ở Fortran là việc xáo trộn mã trong nỗ lực "tối ưu hóa" nó, thường không nhằm mục đích quan trọng.

Thông thường, khi các ứng dụng có vấn đề về hiệu suất, đó là do thiết kế lãng phí. Ngày nay, tôi sẽ không bao giờ đề xuất trình biên dịch cho hiệu suất trừ khi ứng dụng tổng thể đã được điều chỉnh trong vòng một inch, vẫn không đủ nhanh và dành toàn bộ thời gian cho các vòng lặp bên trong chặt chẽ.

Đã thêm: Tôi đã thấy rất nhiều ứng dụng được viết bằng ngôn ngữ hợp ngữ và lợi thế tốc độ chính so với ngôn ngữ như C, Pascal, Fortran, v.v. là do lập trình viên cẩn thận hơn rất nhiều khi mã hóa trình biên dịch. Anh ấy hoặc cô ấy sẽ viết khoảng 100 dòng mã mỗi ngày, bất kể ngôn ngữ và trong ngôn ngữ trình biên dịch sẽ bằng 3 hoặc 400 hướng dẫn.


8
+1: "Bạn có thể đi chệch khỏi các quy ước gọi". Trình biên dịch C / C ++ có xu hướng hút khi trả về nhiều giá trị. Họ thường sử dụng dạng sret trong đó ngăn xếp của người gọi phân bổ một khối liền kề cho một cấu trúc và chuyển một tham chiếu đến nó cho callee để điền vào nó. Trả lại nhiều giá trị trong các thanh ghi nhanh hơn nhiều lần.
Jon Harrop

1
@Jon: Trình biên dịch C / C ++ làm tốt điều đó khi hàm được nội tuyến (các hàm không nội tuyến phải tuân theo ABI, đây không phải là giới hạn của C và C ++ mà là mô hình liên kết)
Ben Voigt

@BenVoigt: Đây là một ví dụ truy cập Flyingfrogblog.blogspot.co.uk/2012/04/ Mạnh
Jon Harrop

2
Tôi không thấy bất kỳ cuộc gọi chức năng nào được đưa vào đó.
Ben Voigt

13

Một vài ví dụ từ kinh nghiệm của tôi:

  • Truy cập vào các hướng dẫn không thể truy cập từ C. Chẳng hạn, nhiều kiến ​​trúc (như x86-64, IA-64, DEC Alpha và MIPS 64 bit hoặc PowerPC) hỗ trợ nhân 64 bit với 64 bit tạo ra kết quả 128 bit. GCC gần đây đã thêm một tiện ích mở rộng cung cấp quyền truy cập vào các hướng dẫn như vậy, nhưng trước khi lắp ráp đó là bắt buộc. Và việc truy cập vào hướng dẫn này có thể tạo ra sự khác biệt lớn trên CPU 64 bit khi thực hiện một cái gì đó như RSA - đôi khi nhiều như một yếu tố cải thiện hiệu suất 4.

  • Truy cập vào các cờ dành riêng cho CPU. Người đã cắn tôi rất nhiều là cờ mang theo; khi thực hiện bổ sung đa độ chính xác, nếu bạn không có quyền truy cập vào CPU mang bit, thay vào đó, phải so sánh kết quả để xem liệu nó có bị tràn hay không, cần thêm 3-5 hướng dẫn cho mỗi chi; và tệ hơn, là khá nối tiếp về mặt truy cập dữ liệu, giết chết hiệu năng trên các bộ xử lý siêu thanh hiện đại. Khi xử lý hàng ngàn số nguyên như vậy liên tiếp, việc có thể sử dụng addc là một chiến thắng rất lớn (có những vấn đề siêu lớn với sự tranh chấp trên bit carry, nhưng CPU hiện đại xử lý khá tốt với nó).

  • SIMD. Ngay cả các trình biên dịch tự động chỉ có thể thực hiện các trường hợp tương đối đơn giản, vì vậy nếu bạn muốn có hiệu suất SIMD tốt, thật không may thường xuyên phải viết mã trực tiếp. Tất nhiên bạn có thể sử dụng nội tại thay vì lắp ráp nhưng một khi bạn ở cấp độ nội tại thì về cơ bản bạn vẫn đang viết lắp ráp, chỉ cần sử dụng trình biên dịch làm công cụ cấp phát đăng ký và lập lịch lệnh (trên danh nghĩa). (Tôi có xu hướng sử dụng nội tại cho SIMD đơn giản vì trình biên dịch có thể tạo ra các phần mở rộng chức năng và không có gì cho tôi để tôi có thể sử dụng cùng một mã trên Linux, OS X và Windows mà không phải xử lý các vấn đề ABI như các quy ước gọi hàm, nhưng khác hơn thế nữa, nội tại SSE thực sự không đẹp lắm - những người Altivec có vẻ tốt hơn mặc dù tôi không có nhiều kinh nghiệm với họ).bitlicing sửa lỗi AES hoặc SIMD - người ta có thể tưởng tượng một trình biên dịch có thể phân tích các thuật toán và tạo mã như vậy, nhưng đối với tôi như một trình biên dịch thông minh như vậy cách xa ít nhất 30 năm (tốt nhất).

Mặt khác, các máy đa lõi và các hệ thống phân tán đã thay đổi nhiều chiến thắng hiệu suất lớn nhất theo hướng khác - tăng thêm 20% tốc độ khi viết các vòng lặp bên trong của bạn trong lắp ráp, hoặc 300% bằng cách chạy chúng trên nhiều lõi, hoặc 10000% bằng cách chạy chúng trên một cụm máy móc. Và tất nhiên, tối ưu hóa ở mức độ cao (những thứ như tương lai, ghi nhớ, v.v.) thường dễ thực hiện hơn trong một ngôn ngữ cấp cao hơn như ML hoặc Scala so với C hoặc asm, và thường có thể mang lại chiến thắng hiệu suất lớn hơn nhiều. Vì vậy, như mọi khi, có những sự đánh đổi sẽ được thực hiện.


2
@Dennis đó là lý do tại sao tôi đã viết 'Tất nhiên bạn có thể sử dụng nội tại thay vì lắp ráp nhưng một khi bạn ở cấp độ nội tại thì về cơ bản bạn vẫn đang viết lắp ráp, chỉ cần sử dụng trình biên dịch làm công cụ cấp phát đăng ký và lập lịch trình hướng dẫn.
Jack Lloyd

Ngoài ra, thực chất đang SIMD dựa có xu hướng được ít đọc hơn cùng mã viết bằng assembler: Mã SIMD Phần lớn dựa trên tái diễn giải tiềm ẩn của dữ liệu trong các vectơ, đó là một Pita để làm với các kiểu dữ liệu intrinsics trình biên dịch cung cấp.
cmaster - phục hồi monica

10

Các vòng lặp chặt chẽ, như khi phát với hình ảnh, vì một hình ảnh có thể xuất hiện hàng triệu pixel. Ngồi xuống và tìm ra cách sử dụng tốt nhất số lượng đăng ký bộ xử lý hạn chế có thể tạo ra sự khác biệt. Đây là một mẫu thực tế:

http://danbystrom.se/2008/12/22/optimizing-away-ii/

Sau đó, các bộ xử lý thường có một số hướng dẫn bí truyền quá chuyên dụng để trình biên dịch bận tâm, nhưng đôi khi, một lập trình viên biên dịch có thể sử dụng chúng tốt. Lấy hướng dẫn XLAT làm ví dụ. Thực sự tuyệt vời nếu bạn cần thực hiện tra cứu bảng trong một vòng lặp bảng được giới hạn ở 256 byte!

Đã cập nhật: Ồ, hãy nghĩ về những gì quan trọng nhất khi chúng ta nói về các vòng lặp nói chung: trình biên dịch thường không có manh mối về số lần lặp sẽ là trường hợp phổ biến! Chỉ lập trình viên biết rằng một vòng lặp sẽ được lặp lại NHIỀU lần và do đó sẽ có ích khi chuẩn bị cho vòng lặp với một số công việc bổ sung hoặc nếu nó sẽ được lặp đi lặp lại vài lần thì việc thiết lập thực sự sẽ mất nhiều thời gian hơn các lần lặp hy vọng.


3
Tối ưu hóa theo hướng hồ sơ cung cấp cho thông tin trình biên dịch về tần suất sử dụng vòng lặp.
Zan Lynx

10

Thường xuyên hơn bạn nghĩ, C cần phải làm những việc dường như không cần thiết theo quan điểm của một lập trình viên hội đồng chỉ vì các tiêu chuẩn C nói như vậy.

Khuyến mãi số nguyên, ví dụ. Nếu bạn muốn thay đổi một biến char trong C, người ta thường mong đợi rằng mã thực tế sẽ làm điều đó, một sự thay đổi một bit.

Tuy nhiên, các tiêu chuẩn bắt buộc trình biên dịch thực hiện một dấu hiệu mở rộng đến int trước khi dịch chuyển và cắt kết quả thành char sau đó có thể làm phức tạp mã tùy thuộc vào kiến ​​trúc của bộ xử lý đích.


Trình biên dịch chất lượng cho các micrô nhỏ trong nhiều năm đã có thể tránh xử lý các phần trên của các giá trị trong trường hợp làm như vậy không bao giờ có thể ảnh hưởng đến kết quả một cách có ý nghĩa. Các quy tắc quảng cáo gây ra vấn đề, nhưng thường nhất là trong trường hợp trình biên dịch không có cách nào biết trường hợp góc nào và không liên quan.
supercat

9

Bạn thực sự không biết liệu mã C được viết tốt của mình có thực sự nhanh hay không nếu bạn chưa xem xét việc phân tách những gì trình biên dịch tạo ra. Nhiều lần bạn nhìn vào nó và thấy rằng "được viết tốt" là chủ quan.

Vì vậy, không cần thiết phải viết trong trình biên dịch chương trình để có được mã nhanh nhất từ ​​trước đến nay, nhưng chắc chắn đáng để biết trình biên dịch vì lý do rất giống nhau.


2
"Vì vậy, không cần thiết phải viết bằng trình biên dịch để có được mã nhanh nhất từ ​​trước đến nay" Chà, tôi chưa thấy trình biên dịch làm điều tối ưu trong mọi trường hợp không tầm thường. Một người có kinh nghiệm có thể làm tốt hơn trình biên dịch trong hầu hết các trường hợp. Vì vậy, hoàn toàn cần thiết phải viết trong trình biên dịch chương trình để có được "mã nhanh nhất từ ​​trước đến nay".
cmaster - phục hồi monica

@cmaster Trong kinh nghiệm trình biên dịch đầu ra của tôi là tốt, ngẫu nhiên. Đôi khi nó thực sự tốt và tối ưu và đôi khi là "làm thế nào rác này có thể được phát ra".
sharptooth

9

Tôi đã đọc tất cả các câu trả lời (hơn 30) và không tìm thấy lý do đơn giản: trình biên dịch nhanh hơn C nếu bạn đã đọc và thực hành Hướng dẫn tham khảo tối ưu hóa kiến ​​trúc Intel® 64 và IA-32 , vì vậy lý do tại sao lắp ráp có thể chậm hơn là những người viết lắp ráp chậm như vậy đã không đọc Hướng dẫn tối ưu hóa .

Vào thời kỳ cũ của Intel 80286, mỗi lệnh được thực hiện ở một số chu kỳ CPU cố định, nhưng kể từ Pentium Pro, được phát hành năm 1995, bộ xử lý Intel đã trở thành siêu khối, sử dụng Pipelining phức tạp: Thực hiện & Đăng ký đổi tên không theo thứ tự. Trước đó, trên Pentium, được sản xuất năm 1993, có các đường ống U và V: các đường ống kép có thể thực hiện hai hướng dẫn đơn giản tại một chu kỳ đồng hồ nếu chúng không phụ thuộc vào nhau; nhưng điều này không có gì để so sánh với việc Đổi tên & Thực hiện Đăng ký không theo thứ tự xuất hiện trong Pentium Pro và ngày nay hầu như không thay đổi.

Để giải thích bằng một vài từ, mã nhanh nhất là nơi các hướng dẫn không phụ thuộc vào kết quả trước đó, ví dụ: bạn phải luôn xóa toàn bộ thanh ghi (bằng Movzx) hoặc sử dụng add rax, 1thay thế hoặcinc rax để loại bỏ sự phụ thuộc vào trạng thái cờ trước đó, v.v.

Bạn có thể đọc thêm về Thực hiện & Đăng ký Đổi tên ngoài đơn hàng nếu thời gian cho phép, có rất nhiều thông tin có sẵn trên Internet.

Ngoài ra còn có các vấn đề quan trọng khác như dự đoán chi nhánh, số lượng đơn vị tải và lưu trữ, số cổng thực thi vi lệnh, v.v., nhưng điều quan trọng nhất cần xem xét là cụ thể là Thực thi ngoài đơn hàng.

Hầu hết mọi người chỉ đơn giản là không biết về Thực thi không theo thứ tự, vì vậy họ viết các chương trình lắp ráp của họ như cho 80286, hy vọng hướng dẫn của họ sẽ mất một thời gian cố định để thực hiện bất kể bối cảnh; trong khi trình biên dịch C nhận thức được Thực thi ngoài đơn hàng và tạo mã chính xác. Đó là lý do tại sao mã của những người không biết như vậy chậm hơn, nhưng nếu bạn nhận thức được, mã của bạn sẽ nhanh hơn.


8

Tôi nghĩ trường hợp chung khi trình biên dịch nhanh hơn là khi một lập trình viên lắp ráp thông minh nhìn vào đầu ra của trình biên dịch và nói rằng "đây là một đường dẫn quan trọng cho hiệu năng và tôi có thể viết nó để hiệu quả hơn" và sau đó người đó điều chỉnh trình biên dịch hoặc viết lại nó từ đầu.


7

Tất cả phụ thuộc vào khối lượng công việc của bạn.

Đối với các hoạt động hàng ngày, C và C ++ chỉ hoạt động tốt, nhưng có một số khối lượng công việc nhất định (bất kỳ biến đổi nào liên quan đến video (nén, giải nén, hiệu ứng hình ảnh, v.v.) mà khá nhiều yêu cầu lắp ráp phải được thực hiện.

Chúng cũng thường liên quan đến việc sử dụng các phần mở rộng chipset cụ thể của CPU (MME / MMX / SSE / bất cứ thứ gì) được điều chỉnh cho các loại hoạt động đó.


6

Tôi có một hoạt động hoán vị các bit cần được thực hiện, trên 192 hoặc 256 bit mỗi lần ngắt, điều đó xảy ra cứ sau 50 micro giây.

Nó xảy ra bởi một bản đồ cố định (ràng buộc phần cứng). Sử dụng C, phải mất khoảng 10 micro giây để thực hiện. Khi tôi dịch nó sang Trình biên dịch, có tính đến các tính năng cụ thể của bản đồ này, bộ đệm ẩn đăng ký cụ thể và sử dụng các hoạt động định hướng bit; phải mất ít hơn 3,5 micro giây để thực hiện.


6

Có thể đáng để xem Tối ưu hóa bất biến và độ tinh khiết của Walter Bright, đây không phải là một thử nghiệm định hình nhưng cho bạn thấy một ví dụ điển hình về sự khác biệt giữa ASM viết tay và trình biên dịch được tạo ra. Walter Bright viết các trình biên dịch tối ưu hóa để có thể đáng xem các bài đăng trên blog khác của mình.



5

Câu trả lời đơn giản ... Một người biết lắp ráp tốt (còn có tài liệu tham khảo bên cạnh anh ta, và đang tận dụng mọi bộ đệm của bộ xử lý nhỏ và tính năng đường ống, v.v.) được đảm bảo có khả năng tạo mã nhanh hơn nhiều so với bất kỳ trình biên dịch.

Tuy nhiên, sự khác biệt ngày nay không quan trọng trong ứng dụng thông thường.


1
Bạn đã quên nói "đã dành rất nhiều thời gian và công sức" và "tạo ra một cơn ác mộng bảo trì". Một đồng nghiệp của tôi đã làm việc để tối ưu hóa một phần quan trọng về hiệu năng của mã hệ điều hành và anh ta làm việc ở C nhiều hơn là lắp ráp, vì nó cho phép anh ta điều tra tác động hiệu suất của các thay đổi cấp cao trong một khung thời gian hợp lý.
Artelius

Tôi đồng ý. Đôi khi bạn sử dụng macro và tập lệnh để tạo mã lắp ráp để tiết kiệm thời gian và phát triển nhanh chóng. Hầu hết các nhà lắp ráp ngày nay có macro; nếu không, bạn có thể tạo bộ xử lý trước macro (đơn giản) bằng cách sử dụng tập lệnh Perl (khá đơn giản RegEx).

Điều này. Đúng. Trình biên dịch để đánh bại các chuyên gia tên miền chưa được phát minh.
cmaster - phục hồi monica

4

Một trong những điểm tích cực đối với phiên bản CP / M-86 của PolyPascal (anh chị em với Turbo Pascal) là thay thế tiện ích "use-bios-to-output-character-to-the-screen" bằng một thói quen ngôn ngữ máy. đã được đưa ra x, và y và chuỗi để đặt ở đó.

Điều này cho phép cập nhật màn hình nhiều, nhanh hơn nhiều so với trước đây!

Có chỗ trong mã nhị phân để nhúng mã máy (vài trăm byte) và cũng có những thứ khác ở đó, vì vậy điều cần thiết là phải nén càng nhiều càng tốt.

Hóa ra vì màn hình có kích thước 80x25 nên cả hai tọa độ có thể vừa với một byte, vì vậy cả hai đều có thể khớp với một từ hai byte. Điều này cho phép thực hiện các phép tính cần thiết trong ít byte hơn vì một lần thêm có thể thao tác đồng thời cả hai giá trị.

Theo hiểu biết của tôi, không có trình biên dịch C nào có thể hợp nhất nhiều giá trị trong một thanh ghi, hãy thực hiện các hướng dẫn SIMD trên chúng và tách chúng ra sau (và tôi không nghĩ rằng các hướng dẫn máy sẽ ngắn hơn).


4

Một trong những đoạn lắp ráp nổi tiếng khác là từ vòng lặp ánh xạ kết cấu của Michael Abrash (được trình bày chi tiết tại đây ):

add edx,[DeltaVFrac] ; add in dVFrac
sbb ebp,ebp ; store carry
mov [edi],al ; write pixel n
mov al,[esi] ; fetch pixel n+1
add ecx,ebx ; add in dUFrac
adc esi,[4*ebp + UVStepVCarry]; add in steps

Ngày nay, hầu hết các trình biên dịch thể hiện các hướng dẫn cụ thể của CPU là nội tại, tức là các hàm được biên dịch theo hướng dẫn thực tế. MS Visual C ++ hỗ trợ nội tại cho MMX, SSE, SSE2, SSE3 và SSE4, vì vậy bạn phải bớt lo lắng về việc thả xuống để lắp ráp để tận dụng các hướng dẫn cụ thể của nền tảng. Visual C ++ cũng có thể tận dụng kiến ​​trúc thực tế mà bạn đang nhắm mục tiêu với cài đặt / ARCH thích hợp.


Thậm chí tốt hơn, những nội tại SSE đó được Intel chỉ định nên chúng thực sự khá di động.
James

4

Với các lập trình viên phù hợp, các chương trình Trình biên dịch mã luôn có thể được thực hiện nhanh hơn so với các đối tác C của chúng (ít nhất là một chút). Sẽ rất khó để tạo một chương trình C trong đó bạn không thể thực hiện ít nhất một hướng dẫn của Trình biên dịch.


Điều này sẽ đúng hơn một chút: "Thật khó để tạo ra một chương trình C không cần thiết trong đó ..." Ngoài ra, bạn có thể nói: "Thật khó để tìm thấy một chương trình C trong thế giới thực trong đó ..." Điểm là , có các vòng lặp nhỏ mà trình biên dịch tạo ra đầu ra tối ưu. Tuy nhiên, câu trả lời tốt.
cmaster - phục hồi monica


4

gcc đã trở thành một trình biên dịch được sử dụng rộng rãi. Tối ưu hóa của nó nói chung là không tốt. Tốt hơn nhiều so với trình biên dịch viết chương trình trung bình, nhưng cho hiệu năng thực sự, không phải là tốt. Có những trình biên dịch đơn giản đến khó tin trong mã mà họ tạo ra. Vì vậy, như một câu trả lời chung, sẽ có nhiều nơi bạn có thể đi vào đầu ra của trình biên dịch và điều chỉnh trình biên dịch để thực hiện, và / hoặc đơn giản là viết lại thường trình từ đầu.


8
GCC thực hiện tối ưu hóa "độc lập với nền tảng" cực kỳ thông minh. Tuy nhiên, nó không tốt lắm trong việc sử dụng các bộ hướng dẫn cụ thể đến mức tối đa của chúng. Đối với một trình biên dịch di động như vậy nó làm một công việc rất tốt.
Artelius

2
đã đồng ý. Tính di động của nó, ngôn ngữ đến và mục tiêu đi ra là tuyệt vời. Là thiết bị cầm tay đó có thể và có được cách trở nên thực sự tốt ở một ngôn ngữ hoặc mục tiêu. Vì vậy, cơ hội để con người làm tốt hơn là có một sự tối ưu hóa cụ thể trên một mục tiêu cụ thể.
old_timer

+1: GCC chắc chắn không cạnh tranh trong việc tạo mã nhanh nhưng tôi không chắc đó là vì nó có thể mang theo được. LLVM là di động và tôi đã thấy nó tạo mã nhanh gấp 4 lần GCC.
Jon Harrop

Tôi thích GCC hơn, vì nó đã hoạt động ổn định trong nhiều năm, cộng với hầu hết mọi nền tảng có thể chạy trình biên dịch di động hiện đại. Thật không may, tôi không thể xây dựng LLVM (Mac OS X / PPC), vì vậy tôi có thể sẽ không thể chuyển sang nó. Một trong những điều tốt về GCC là nếu bạn viết mã xây dựng trong GCC, rất có thể bạn sẽ tuân thủ các tiêu chuẩn và bạn sẽ chắc chắn rằng nó có thể được xây dựng cho hầu hết mọi nền tảng.

4

Longpoke, chỉ có một giới hạn: thời gian. Khi bạn không có tài nguyên để tối ưu hóa mỗi thay đổi mã và dành thời gian phân bổ các thanh ghi, tối ưu hóa một vài sự cố và không, trình biên dịch sẽ giành chiến thắng mỗi lần. Bạn thực hiện sửa đổi mã, biên dịch lại và đo lường. Lặp lại nếu cần thiết.

Ngoài ra, bạn có thể làm rất nhiều ở phía cấp cao. Ngoài ra, việc kiểm tra lắp ráp kết quả có thể cung cấp cho IMPRESSION rằng mã là tào lao, nhưng trong thực tế, nó sẽ chạy nhanh hơn những gì bạn nghĩ sẽ nhanh hơn. Thí dụ:

int y = dữ liệu [i]; // làm một số thứ ở đây .. call_feft (y, ...);

Trình biên dịch sẽ đọc dữ liệu, đẩy nó vào stack (tràn) và sau đó đọc từ stack và truyền dưới dạng đối số. Âm thanh shite? Nó thực sự có thể là bù độ trễ rất hiệu quả và dẫn đến thời gian chạy nhanh hơn.

// phiên bản tối ưu hóa call_function (data [i], ...); // không được tối ưu hóa cho tất cả ..

Ý tưởng với phiên bản tối ưu hóa là chúng tôi đã giảm áp lực đăng ký và tránh làm đổ. Nhưng sự thật, phiên bản "shitty" đã nhanh hơn!

Nhìn vào mã lắp ráp, chỉ cần nhìn vào các hướng dẫn và kết luận: nhiều hướng dẫn hơn, chậm hơn, sẽ là một đánh giá sai.

Điều cần chú ý ở đây là: nhiều chuyên gia lắp ráp nghĩ rằng họ biết rất nhiều, nhưng biết rất ít. Các quy tắc thay đổi từ kiến ​​trúc để tiếp theo, quá. Chẳng hạn, không có mã x86 đạn bạc, luôn luôn là mã nhanh nhất. Những ngày này là tốt hơn để đi theo quy tắc:

  • bộ nhớ chậm
  • bộ nhớ cache nhanh
  • cố gắng sử dụng bộ nhớ cache tốt hơn
  • bao lâu bạn sẽ bỏ lỡ? Bạn có chiến lược bồi thường độ trễ?
  • bạn có thể thực hiện 10-100 lệnh ALU / FPU / SSE cho một lần bỏ lỡ bộ đệm
  • kiến trúc ứng dụng rất quan trọng ..
  • .. nhưng nó không giúp ích gì khi vấn đề không nằm trong kiến ​​trúc

Ngoài ra, tin tưởng quá nhiều vào trình biên dịch biến đổi mã C / C ++ được suy nghĩ kém thành mã "tối ưu về mặt lý thuyết" là điều đáng suy nghĩ. Bạn phải biết trình biên dịch và chuỗi công cụ bạn sử dụng nếu bạn quan tâm đến "hiệu suất" ở mức độ thấp này.

Trình biên dịch trong C / C ++ thường không tốt trong việc sắp xếp lại các biểu thức con vì các hàm có tác dụng phụ, cho người mới bắt đầu. Các ngôn ngữ chức năng không phải chịu cảnh báo này nhưng cũng không phù hợp với hệ sinh thái hiện tại. Có các tùy chọn trình biên dịch để cho phép các quy tắc chính xác thoải mái cho phép thay đổi thứ tự các hoạt động của trình biên dịch / trình liên kết / trình tạo mã.

Chủ đề này là một chút của một ngõ cụt; Đối với hầu hết nó không liên quan, và phần còn lại, họ biết họ đang làm gì.

Tất cả tập trung vào điều này: "để hiểu những gì bạn đang làm", nó hơi khác so với việc bạn đang làm gì.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.