Là phép nhân và chia sử dụng toán tử shift trong C có thực sự nhanh hơn không?


288

Nhân và chia có thể đạt được bằng cách sử dụng các toán tử bit, ví dụ

i*2 = i<<1
i*3 = (i<<1) + i;
i*10 = (i<<3) + (i<<1)

và như thế.

Có thực sự nhanh hơn để sử dụng nói (i<<3)+(i<<1)để nhân với 10 so với sử dụng i*10trực tiếp? Có bất kỳ loại đầu vào không thể được nhân hoặc chia theo cách này?


8
Trên thực tế, phân chia giá rẻ bằng một hằng số không phải là sức mạnh của hai là có thể, nhưng một máy bay con khó khăn mà bạn không thực hiện công bằng với "/ Division Division / chia" trong câu hỏi của bạn. Xem ví dụ hackersdelight.org/divcMore.pdf (hoặc lấy cuốn sách "Hacker vui lòng" nếu bạn có thể).
Pascal Cuoq

46
Nghe có vẻ như một cái gì đó có thể dễ dàng được kiểm tra.
juanchopanza

25
Như thường lệ - nó phụ thuộc. Có lần tôi đã thử điều này trong trình biên dịch chương trình trên Intel 8088 (IBM PC / XT) trong đó một phép nhân đã tạo ra một đồng hồ trị giá hàng tỷ đồng. Thay đổi và thêm thực hiện nhanh hơn rất nhiều, vì vậy nó có vẻ như là một ý tưởng tốt. Tuy nhiên, trong khi nhân đơn vị xe buýt được tự do điền vào hàng đợi lệnh và lệnh tiếp theo có thể bắt đầu ngay lập tức. Sau một loạt các ca làm việc và thêm hàng đợi lệnh sẽ trống và CPU sẽ phải đợi lệnh tiếp theo được lấy từ bộ nhớ (mỗi lần một byte!). Đo lường, đo lường, đo lường!
Bo Persson

19
Ngoài ra, hãy cẩn thận khi dịch chuyển sang phải chỉ được xác định rõ cho các số nguyên không dấu . Nếu bạn có một số nguyên đã ký, nó không được xác định là 0 hay bit cao nhất được đệm từ bên trái. (Và đừng quên thời gian để người khác (thậm chí chính bạn) đọc mã một năm sau đó!)
Kerrek SB

29
Trên thực tế, một trình biên dịch tối ưu hóa tốt sẽ thực hiện phép nhân và chia với các ca khi chúng nhanh hơn.
Peter G.

Câu trả lời:


487

Câu trả lời ngắn gọn: Không có khả năng.

Câu trả lời dài: Trình biên dịch của bạn có trình tối ưu hóa trong đó biết cách nhân nhanh như kiến ​​trúc bộ xử lý đích của bạn có khả năng. Đặt cược tốt nhất của bạn là nói cho trình biên dịch biết ý định của bạn một cách rõ ràng (tức là i * 2 chứ không phải i << 1) và để nó quyết định chuỗi lắp ráp / mã máy nhanh nhất là gì. Thậm chí có khả năng chính bộ xử lý đã thực hiện lệnh nhân như một chuỗi các thay đổi và thêm vào vi mã.

Điểm mấu chốt - đừng dành nhiều thời gian để lo lắng về điều này. Nếu bạn có nghĩa là thay đổi, thay đổi. Nếu bạn có nghĩa là nhân lên, nhân lên. Làm những gì rõ ràng nhất về mặt ngữ nghĩa - đồng nghiệp của bạn sẽ cảm ơn bạn sau này. Hoặc, nhiều khả năng, nguyền rủa bạn sau này nếu bạn làm khác.


31
Đúng, như đã nói, mức tăng có thể có cho hầu hết mọi ứng dụng sẽ hoàn toàn vượt xa mức tối nghĩa được giới thiệu. Đừng lo lắng về loại tối ưu hóa này sớm. Xây dựng những gì rõ ràng về mặt ngữ nghĩa, xác định các tắc nghẽn và tối ưu hóa từ đó ...
Dave

4
Đồng ý, tối ưu hóa cho khả năng đọc và bảo trì có thể sẽ khiến bạn mất nhiều thời gian hơn để thực sự tối ưu hóa những thứ mà trình hồ sơ nói là các đường dẫn mã nóng.
doug65536

5
Những nhận xét này nghe có vẻ như bạn đang từ bỏ hiệu suất tiềm năng từ việc nói cho trình biên dịch biết cách thực hiện công việc của mình. Đây không phải là trường hợp. Bạn thực sự nhận được mã tốt hơn từ gcc -O3trên x86 return i*10so với từ phiên bản thay đổi . Là một người nhìn vào đầu ra trình biên dịch rất nhiều (xem nhiều câu trả lời asm / tối ưu hóa của tôi), tôi không ngạc nhiên. Đôi khi nó có thể giúp giữ trình biên dịch thành một cách để làm việc , nhưng đây không phải là một trong số đó. gcc giỏi toán số nguyên, vì nó quan trọng.
Peter Cordes

Chỉ cần tải về một bản phác thảo arduino có millis() >> 2; Nó sẽ là quá nhiều để yêu cầu chỉ chia?
Paul Wieland

1
Tôi đã thử nghiệm i / 32vs i >> 5i / 4vs i >> 2trên gcc cho cortex-a9 (không có phân chia phần cứng) với tối ưu hóa -O3 và kết quả lắp ráp hoàn toàn giống nhau. Tôi không thích sử dụng các bộ phận trước nhưng nó mô tả ý định của tôi và đầu ra là như nhau.
cướp

91

Chỉ là một điểm đo lường cụ thể: nhiều năm trở lại đây, tôi đã điểm chuẩn hai phiên bản thuật toán băm của mình:

unsigned
hash( char const* s )
{
    unsigned h = 0;
    while ( *s != '\0' ) {
        h = 127 * h + (unsigned char)*s;
        ++ s;
    }
    return h;
}

unsigned
hash( char const* s )
{
    unsigned h = 0;
    while ( *s != '\0' ) {
        h = (h << 7) - h + (unsigned char)*s;
        ++ s;
    }
    return h;
}

Trên mỗi máy tôi đã điểm chuẩn trên đó, máy đầu tiên ít nhất nhanh bằng máy thứ hai. Thật đáng ngạc nhiên, đôi khi nó nhanh hơn (ví dụ trên Sun Sparc). Khi phần cứng không hỗ trợ nhân nhanh (và hầu hết không quay lại sau đó), trình biên dịch sẽ chuyển đổi phép nhân thành các tổ hợp ca và add / sub thích hợp. Và bởi vì nó biết mục tiêu cuối cùng, đôi khi nó có thể thực hiện theo hướng dẫn ít hơn so với khi bạn viết rõ ràng các ca và add / subs.

Lưu ý rằng đây là một cái gì đó giống như 15 năm trước. Hy vọng rằng, trình biên dịch chỉ trở nên tốt hơn kể từ đó, vì vậy bạn có thể tin tưởng vào trình biên dịch làm việc đúng, có thể tốt hơn bạn có thể. (Ngoài ra, lý do mã trông rất C'ish là vì nó đã hơn 15 năm trước. Rõ ràng tôi sẽ sử dụng std::stringvà các trình lặp ngày hôm nay.)


5
Bạn có thể quan tâm đến bài đăng trên blog sau đây, trong đó tác giả lưu ý rằng các trình biên dịch tối ưu hóa hiện đại dường như đảo ngược các mẫu phổ biến mà các lập trình viên có thể sử dụng để suy nghĩ chúng hiệu quả hơn trong các dạng toán học của họ để thực sự tạo ra chuỗi hướng dẫn hiệu quả nhất cho chúng . shape-of-code.coding-guferences.com/2009/06/30/ Cách
Pascal Cuoq

@PascalCuoq Không có gì thực sự mới về điều này. Tôi phát hiện ra khá nhiều điều tương tự cho Sun CC gần 20 năm trước.
James Kanze

67

Ngoài tất cả các câu trả lời hay khác ở đây, hãy để tôi chỉ ra một lý do khác để không sử dụng dịch chuyển khi bạn có nghĩa là chia hoặc nhân. Tôi chưa bao giờ thấy ai đó giới thiệu một lỗi bằng cách quên đi ưu tiên tương đối của phép nhân và phép cộng. Tôi đã thấy lỗi đưa ra khi các lập trình viên bảo trì quên rằng "nhân" qua một sự thay đổi là hợp lý một phép nhân nhưng không cú pháp của ưu tiên tương tự như phép nhân. x * 2 + zx << 1 + zrất khác nhau!

Nếu bạn đang làm việc trên các số thì hãy sử dụng các toán tử số học như thế nào + - * / %. Nếu bạn đang làm việc trên các mảng bit, hãy sử dụng các toán tử xoay vòng bit như thế nào & ^ | >>. Đừng trộn chúng; một biểu thức có cả hai bit và số học là một lỗi đang chờ xảy ra.


5
Có thể tránh với dấu ngoặc đơn giản?
Joel B

21
@Joel: Chắc chắn rồi. Nếu bạn nhớ rằng bạn cần chúng. Quan điểm của tôi là rất dễ quên rằng bạn làm. Những người có thói quen tinh thần đọc "x << 1" như thể đó là "x * 2" có thói quen tinh thần khi nghĩ rằng << là cùng một ưu tiên như phép nhân, điều đó không phải.
Eric Lippert

1
Chà, tôi thấy biểu hiện "(hi << 8) + lo" tiết lộ nhiều ý định hơn "hi * 256 + lo". Có lẽ đó là một vấn đề của hương vị, nhưng đôi khi nó rõ ràng hơn để viết twiddling. Trong hầu hết các trường hợp mặc dù tôi hoàn toàn đồng ý với quan điểm của bạn.
Ivan Danilov

32
@Ivan: Và "(hi << 8) | lo" thậm chí còn rõ ràng hơn. Đặt các bit thấp của mảng bit không phảisố nguyên . Đó là thiết lập bit , vì vậy hãy viết mã đặt bit.
Eric Lippert

1
Ồ Không nghĩ về nó theo cách này trước đây. Cảm ơn.
Ivan Danilov

50

Điều này phụ thuộc vào bộ xử lý và trình biên dịch. Một số trình biên dịch đã tối ưu hóa mã theo cách này, một số khác thì không. Vì vậy, bạn cần kiểm tra mỗi lần mã của bạn cần được tối ưu hóa theo cách này.

Trừ khi bạn rất cần tối ưu hóa, tôi sẽ không tranh giành mã nguồn của tôi chỉ để lưu một lệnh lắp ráp hoặc chu trình xử lý.


3
Chỉ cần thêm một ước tính sơ bộ: Trên bộ xử lý 16 bit thông thường (80C166), thêm hai số nguyên đến 1-2 chu kỳ, nhân với 10 chu kỳ và phân chia ở 20 chu kỳ. Cộng với một số thao tác di chuyển nếu bạn tối ưu hóa i * 10 thành nhiều op (mỗi lần di chuyển chu kỳ +1 khác). Các trình biên dịch phổ biến nhất (Keil / Nhiệm vụ) không tối ưu hóa trừ khi nhân / chia với công suất là 2.
Jens

55
Và nói chung, trình biên dịch tối ưu hóa mã tốt hơn bạn.
dùng703016

Tôi đồng ý rằng khi nhân "số lượng", toán tử nhân nói chung tốt hơn, nhưng khi chia giá trị đã ký cho lũy thừa bằng 2, >>toán tử sẽ nhanh hơn /và, nếu các giá trị đã ký có thể âm, thì nó cũng thường vượt trội về mặt ngữ nghĩa. Nếu một người cần giá trị x>>4sẽ tạo ra, điều đó rõ ràng hơn nhiều x < 0 ? -((-1-x)/16)-1 : x/16;và tôi không thể tưởng tượng làm thế nào một trình biên dịch có thể tối ưu hóa biểu thức sau này thành một cái gì đó tốt đẹp.
supercat

38

Có thực sự nhanh hơn khi sử dụng say (i << 3) + (i << 1) để nhân với 10 so với sử dụng i * 10 trực tiếp không?

Nó có thể có hoặc không có trên máy của bạn - nếu bạn quan tâm, hãy đo lường mức độ sử dụng trong thế giới thực của bạn.

Một nghiên cứu trường hợp - từ 486 đến lõi i7

Điểm chuẩn là rất khó để làm có ý nghĩa, nhưng chúng ta có thể xem xét một vài sự kiện. Từ http://www.penguin.cz/~literakl/intel/s.html#SALhttp://www.penguin.cz/~literakl/intel/i.html#IMUL chúng tôi có ý tưởng về chu kỳ đồng hồ x86 cần thiết cho sự thay đổi số học và nhân. Giả sử chúng tôi dính vào "486" (cái mới nhất được liệt kê), thanh ghi 32 bit và ngay lập tức, IMUL thực hiện 13-42 chu kỳ và IDIV 44. Mỗi SAL mất 2 và thêm 1, do đó, ngay cả với một vài trong số chúng cùng thay đổi bề ngoài như một người chiến thắng

Những ngày này, với lõi i7:

(từ http://software.intel.com/en-us/forums/showthread.php?t=61481 )

Độ trễ là 1 chu kỳ cho phép cộng số nguyên và 3 chu kỳ cho phép nhân số nguyên . Bạn có thể tìm thấy độ trễ và thông số trong Phụ lục C của "Hướng dẫn tham khảo tối ưu hóa kiến ​​trúc Intel® 64 và IA-32", được đặt trên http://www.intel.com/products/ Processor /mans / .

(từ một số Intel blurb)

Sử dụng SSE, Core i7 có thể ban hành các lệnh cộng và nhân đồng thời, dẫn đến tốc độ cao nhất là 8 thao tác dấu phẩy động (FLOP) trên mỗi chu kỳ đồng hồ

Điều đó cho bạn một ý tưởng về những điều đã đến. Các câu đố tối ưu hóa - như thay đổi bit so với* - đã được thực hiện nghiêm túc ngay cả vào những năm 90 chỉ là lỗi thời. Dịch chuyển bit vẫn nhanh hơn, nhưng đối với mul / div không có công suất hai lần vào thời điểm bạn thực hiện tất cả các ca của mình và thêm kết quả thì nó lại chậm hơn. Sau đó, nhiều hướng dẫn hơn có nghĩa là lỗi bộ nhớ cache nhiều hơn, các vấn đề tiềm ẩn hơn trong đường ống, sử dụng nhiều thanh ghi tạm thời hơn có thể có nghĩa là tiết kiệm và khôi phục nội dung đăng ký từ ngăn xếp ... nó nhanh chóng trở nên quá phức tạp để định lượng tất cả các tác động một cách dứt khoát nhưng chúng chủ yếu là tiêu cực.

chức năng trong mã nguồn so với thực hiện

Tổng quát hơn, câu hỏi của bạn được gắn thẻ C và C ++. Là ngôn ngữ thế hệ thứ 3, chúng được thiết kế đặc biệt để ẩn chi tiết của tập lệnh CPU bên dưới. Để đáp ứng Tiêu chuẩn ngôn ngữ của họ, họ phải hỗ trợ các hoạt động nhân và dịch chuyển (và nhiều hoạt động khác) ngay cả khi phần cứng cơ bản không hoạt động . Trong những trường hợp như vậy, họ phải tổng hợp kết quả cần thiết bằng nhiều hướng dẫn khác. Tương tự, họ phải cung cấp phần mềm hỗ trợ cho các hoạt động của dấu phẩy động nếu CPU thiếu nó và không có FPU. CPU hiện đại đều hỗ trợ *<<, do đó, điều này có vẻ vô lý về mặt lý thuyết và lịch sử, nhưng điều quan trọng là quyền tự do lựa chọn thực hiện theo cả hai cách: ngay cả khi CPU có một lệnh thực hiện thao tác được yêu cầu trong mã nguồn trong trường hợp chung, trình biên dịch miễn phí chọn một cái gì đó khác mà nó thích bởi vì nó tốt hơn cho trường hợp cụ thể mà người biên soạn phải đối mặt.

Ví dụ (với ngôn ngữ lắp ráp giả thuyết)

source           literal approach         optimised approach
#define N 0
int x;           .word x                xor registerA, registerA
x *= N;          move x -> registerA
                 move x -> registerB
                 A = B * immediate(0)
                 store registerA -> x
  ...............do something more with x...............

Các hướng dẫn như độc quyền hoặc ( xor) không có mối quan hệ với mã nguồn, nhưng xor-ing bất cứ thứ gì với chính nó sẽ xóa tất cả các bit, do đó, nó có thể được sử dụng để đặt một cái gì đó thành 0. Mã nguồn ngụ ý địa chỉ bộ nhớ có thể không được sử dụng.

Những loại hack này đã được sử dụng miễn là có máy tính xung quanh. Trong những ngày đầu của 3GL, để đảm bảo nhà phát triển thu được đầu ra của trình biên dịch phải đáp ứng các nhà phát triển ngôn ngữ lắp ráp tối ưu hóa tay cứng hiện có. cộng đồng rằng mã được sản xuất không chậm hơn, dài dòng hơn hoặc tệ hơn. Trình biên dịch nhanh chóng chấp nhận rất nhiều tối ưu hóa - chúng trở thành một kho lưu trữ tập trung tốt hơn bất kỳ lập trình viên ngôn ngữ lắp ráp riêng lẻ nào, mặc dù luôn có khả năng họ bỏ lỡ một tối ưu hóa cụ thể xảy ra rất quan trọng trong một trường hợp cụ thể - đôi khi con người có thể hãy tìm ra và tìm kiếm thứ gì đó tốt hơn trong khi các trình biên dịch chỉ làm như họ đã nói cho đến khi ai đó cung cấp trải nghiệm trở lại cho họ.

Vì vậy, ngay cả khi dịch chuyển và thêm vẫn nhanh hơn trên một số phần cứng cụ thể, thì trình biên dịch có khả năng đã hoạt động chính xác khi nó vừa an toàn vừa có lợi.

Bảo trì

Nếu phần cứng của bạn thay đổi, bạn có thể biên dịch lại và nó sẽ xem xét CPU mục tiêu và đưa ra lựa chọn tốt nhất khác, trong khi bạn không bao giờ muốn xem lại "tối ưu hóa" của mình hoặc liệt kê môi trường biên dịch nào nên sử dụng phép nhân và nên thay đổi. Hãy nghĩ về tất cả các "tối ưu hóa" không thay đổi hai bit được viết cách đây hơn 10 năm, hiện đang làm chậm mã mà chúng đang chạy khi chạy trên các bộ xử lý hiện đại ...!

Rất may, các trình biên dịch tốt như GCC thường có thể thay thế một loạt các bit và số học bằng phép nhân trực tiếp khi bất kỳ tối ưu hóa nào được bật (tức là ...main(...) { return (argc << 4) + (argc << 2) + argc; }-> imull $21, 8(%ebp), %eax) để việc biên dịch lại có thể giúp ngay cả khi không sửa mã, nhưng điều đó không được đảm bảo.

Mã bẻ khóa kỳ lạ thực hiện phép nhân hoặc chia ít biểu hiện hơn nhiều so với những gì bạn đang cố gắng đạt được về mặt khái niệm, vì vậy các nhà phát triển khác sẽ bối rối vì điều đó, và một lập trình viên bối rối sẽ giới thiệu các lỗi hoặc loại bỏ thứ gì đó thiết yếu trong nỗ lực khôi phục sự tỉnh táo. Nếu bạn chỉ làm những việc không rõ ràng khi chúng thực sự có lợi, và sau đó ghi lại chúng thật tốt (nhưng đừng ghi lại những thứ khác trực quan), mọi người sẽ hạnh phúc hơn.

Giải pháp chung so với giải pháp từng phần

Nếu bạn có thêm kiến ​​thức, chẳng hạn như bạn intsẽ thực sự chỉ lưu trữ các giá trị x, yz, sau đó bạn có thể tìm ra một số hướng dẫn phù hợp với các giá trị đó và giúp bạn nhận được kết quả nhanh hơn so với khi trình biên dịch không có cái nhìn sâu sắc và cần một triển khai hoạt động cho tất cả các intgiá trị. Ví dụ, hãy xem xét câu hỏi của bạn:

Nhân và chia có thể đạt được bằng cách sử dụng các toán tử bit ...

Bạn minh họa phép nhân, nhưng làm thế nào về phân chia?

int x;
x >> 1;   // divide by 2?

Theo tiêu chuẩn C ++ 5,8:

-3- Giá trị của E1 >> E2 là vị trí bit E2 dịch chuyển phải. Nếu E1 có loại không dấu hoặc nếu E1 có loại đã ký và giá trị không âm, thì giá trị của kết quả là phần không thể tách rời của thương số của E1 chia cho đại lượng 2 được nâng lên công suất E2. Nếu E1 có loại đã ký và giá trị âm, giá trị kết quả được xác định theo thực hiện.

Vì vậy, sự thay đổi bit của bạn có kết quả xác định khi thực hiện xlà âm: nó có thể không hoạt động theo cùng một cách trên các máy khác nhau. Nhưng, /công trình dự đoán xa hơn. (Nó cũng có thể không hoàn toàn nhất quán, vì các máy khác nhau có thể có các cách biểu diễn khác nhau về số âm và do đó các phạm vi khác nhau ngay cả khi có cùng số bit tạo thành biểu diễn.)

Bạn có thể nói "Tôi không quan tâm ... đó intlà lưu trữ tuổi của nhân viên, điều đó không bao giờ có thể là tiêu cực". Nếu bạn có loại hiểu biết đặc biệt đó, thì có - >>tối ưu hóa an toàn của bạn có thể được trình biên dịch chuyển qua trừ khi bạn thực hiện nó một cách rõ ràng trong mã của bạn. Tuy nhiên, nó rất rủi ro và hiếm khi hữu ích vì phần lớn thời gian bạn sẽ không có loại hiểu biết này và các lập trình viên khác làm việc với cùng một mã sẽ không biết rằng bạn đặt cược cho nhà cái một số kỳ vọng bất thường về dữ liệu bạn ' sẽ xử lý ... những gì dường như là một thay đổi hoàn toàn an toàn đối với họ có thể phản tác dụng vì "tối ưu hóa" của bạn.

Có bất kỳ loại đầu vào không thể được nhân hoặc chia theo cách này?

Có ... như đã đề cập ở trên, các số âm có hành vi được xác định khi thực hiện "chia" cho dịch chuyển bit.


2
Câu trả lời rất hay. So sánh Core i7 so với 486 đang khai sáng!
Hội trường Drew

Trên tất cả các kiến ​​trúc phổ biến, intVal>>1sẽ có cùng một ngữ nghĩa khác với các kiến trúc intVal/2đôi khi hữu ích. Nếu người ta cần tính toán theo kiểu di động, giá trị mà các kiến ​​trúc phổ biến sẽ mang lại intVal >> 1, biểu thức sẽ cần phức tạp hơn và khó đọc hơn, và có khả năng tạo ra mã kém hơn so với mã được tạo ra intVal >> 1.
supercat

35

Chỉ cần thử trên máy của tôi biên dịch này:

int a = ...;
int b = a * 10;

Khi tháo rời nó tạo ra đầu ra:

MOV EAX,DWORD PTR SS:[ESP+1C] ; Move a into EAX
LEA EAX,DWORD PTR DS:[EAX+EAX*4] ; Multiply by 5 without shift !
SHL EAX, 1 ; Multiply by 2 using shift

Phiên bản này nhanh hơn mã được tối ưu hóa bằng tay của bạn với sự thay đổi và bổ sung thuần túy.

Bạn thực sự không bao giờ biết trình biên dịch sẽ ra sao, vì vậy tốt hơn hết là bạn nên viết một phép nhân bình thường và để anh ta tối ưu hóa cách anh ta muốn, ngoại trừ trong những trường hợp rất chính xác mà bạn biết trình biên dịch không thể tối ưu hóa.


1
Bạn sẽ nhận được một upvote lớn cho điều này nếu bạn đã bỏ qua phần về vector. Nếu trình biên dịch có thể sửa lỗi nhân, nó cũng có thể thấy rằng vectơ không thay đổi.
Bo Persson

Làm thế nào một trình biên dịch có thể biết kích thước vectơ sẽ không thay đổi mà không đưa ra một số giả định thực sự nguy hiểm? Hoặc bạn chưa bao giờ nghe nói về sự tương tranh ...
Charles Goodwin

1
Ok, vì vậy bạn lặp qua một vectơ toàn cầu không có khóa? Và tôi lặp qua một vectơ cục bộ mà địa chỉ chưa được lấy và chỉ gọi các hàm thành viên. Ít nhất trình biên dịch của tôi nhận ra rằng kích thước vectơ sẽ không thay đổi. (và sẽ sớm có người sẽ gắn cờ chúng tôi để trò chuyện :-).
Bo Persson

1
@BoPersson Cuối cùng, sau tất cả thời gian này, tôi đã xóa tuyên bố của mình về trình biên dịch không thể tối ưu hóa đi vector<T>::size(). Trình biên dịch của tôi khá cổ xưa! :)
user703016

21

Dịch chuyển nói chung nhanh hơn rất nhiều so với nhân ở cấp độ hướng dẫn nhưng bạn có thể đang lãng phí thời gian để thực hiện tối ưu hóa sớm. Trình biên dịch có thể thực hiện tốt những tối ưu hóa này tại compXLime. Tự làm nó sẽ ảnh hưởng đến khả năng đọc và có thể không ảnh hưởng đến hiệu suất. Có lẽ nó chỉ đáng để làm những việc như thế này nếu bạn đã lập hồ sơ và thấy đây là một nút cổ chai.

Trên thực tế, thủ thuật phân chia, được gọi là 'phân chia ma thuật' thực sự có thể mang lại những khoản tiền lớn. Một lần nữa bạn nên lập hồ sơ trước để xem có cần thiết không. Nhưng nếu bạn sử dụng nó, có những chương trình hữu ích xung quanh để giúp bạn tìm ra những hướng dẫn cần thiết cho cùng một ngữ nghĩa phân chia. Dưới đây là một ví dụ: http://www.masm32.com/board/index.php?topic=12421.0

Một ví dụ mà tôi đã nâng lên từ luồng của OP trên MASM32:

include ConstDiv.inc
...
mov eax,9999999
; divide eax by 100000
cdiv 100000
; edx = quotient

Sẽ tạo ra:

mov eax,9999999
mov edx,0A7C5AC47h
add eax,1
.if !CARRY?
    mul edx
.endif
shr edx,16

7
@Drew vì một số lý do bình luận của bạn làm tôi cười và làm đổ cà phê của tôi. cảm ơn.
asawyer

30
Không có chủ đề diễn đàn ngẫu nhiên về thích toán học. Bất cứ ai thích toán học đều biết rằng thật khó để tạo ra một chủ đề diễn đàn "ngẫu nhiên" thực sự.
Joel B

1
Có lẽ chỉ đáng để làm những việc như thế này nếu bạn đã lập hồ sơ và thấy đây là một nút cổ chai và thực hiện lại các giải pháp thay thế và hồ sơ và nhận được lợi thế hiệu suất ít nhất 10 lần .
Nói dối Ryan

12

Các lệnh nhân số nguyên và thay đổi có hiệu suất tương tự trên hầu hết các CPU hiện đại - các hướng dẫn nhân số nguyên tương đối chậm trong những năm 1980 nhưng nói chung điều này không còn đúng nữa. Hướng dẫn nhân số nguyên có thể có độ trễ cao hơn , do đó vẫn có thể có trường hợp thay đổi là thích hợp hơn. Ditto cho các trường hợp bạn có thể giữ nhiều đơn vị thực thi bận hơn (mặc dù điều này có thể cắt cả hai cách).

Việc phân chia số nguyên vẫn còn tương đối chậm, do đó, sử dụng dịch chuyển thay vì chia cho lũy thừa 2 vẫn là một chiến thắng và hầu hết các trình biên dịch sẽ thực hiện điều này như một sự tối ưu hóa. Tuy nhiên, lưu ý rằng để tối ưu hóa này có hiệu lực, cổ tức cần phải được bỏ dấu hoặc phải được biết là dương. Đối với cổ tức âm, sự thay đổi và chia không tương đương!

#include <stdio.h>

int main(void)
{
    int i;

    for (i = 5; i >= -5; --i)
    {
        printf("%d / 2 = %d, %d >> 1 = %d\n", i, i / 2, i, i >> 1);
    }
    return 0;
}

Đầu ra:

5 / 2 = 2, 5 >> 1 = 2
4 / 2 = 2, 4 >> 1 = 2
3 / 2 = 1, 3 >> 1 = 1
2 / 2 = 1, 2 >> 1 = 1
1 / 2 = 0, 1 >> 1 = 0
0 / 2 = 0, 0 >> 1 = 0
-1 / 2 = 0, -1 >> 1 = -1
-2 / 2 = -1, -2 >> 1 = -1
-3 / 2 = -1, -3 >> 1 = -2
-4 / 2 = -2, -4 >> 1 = -2
-5 / 2 = -2, -5 >> 1 = -3

Vì vậy, nếu bạn muốn giúp trình biên dịch thì hãy chắc chắn rằng biến hoặc biểu thức trong cổ tức không được ký rõ ràng.


4
Số nhân của số nguyên được mã hóa bằng ví dụ trên PPU của PlayStation 3 và làm tắc nghẽn toàn bộ đường ống. Bạn nên tránh nhân số nguyên trên một số nền tảng :)
Maister

2
Nhiều bộ phận không dấu là - giả sử trình biên dịch biết cách - thực hiện bằng cách sử dụng bội số không dấu. Một hoặc hai bội số @ một vài chu kỳ đồng hồ, mỗi chu kỳ có thể thực hiện cùng một công việc như một phép chia @ 40 chu kỳ mỗi lần trở lên.
Olof Forshell

1
@Olof: đúng, nhưng chỉ hợp lệ cho phép chia theo hằng số thời gian biên dịch
Paul R

4

Nó hoàn toàn phụ thuộc vào thiết bị mục tiêu, ngôn ngữ, mục đích, v.v.

Pixel crunching trong một trình điều khiển card màn hình? Rất có thể, vâng!

Ứng dụng kinh doanh .NET cho bộ phận của bạn? Hoàn toàn không có lý do để thậm chí nhìn vào nó.

Đối với một trò chơi hiệu năng cao cho thiết bị di động, nó có thể đáng để xem xét, nhưng chỉ sau khi tối ưu hóa dễ dàng hơn đã được thực hiện.


2

Đừng làm trừ khi bạn thực sự cần và mục đích mã của bạn yêu cầu thay đổi thay vì nhân / chia.

Trong một ngày thông thường - bạn có thể tiết kiệm được vài chu kỳ máy (hoặc lỏng lẻo, vì trình biên dịch biết rõ hơn những gì cần tối ưu hóa), nhưng chi phí không đáng - bạn dành thời gian cho các chi tiết nhỏ thay vì công việc thực tế, việc duy trì mã trở nên khó khăn hơn và đồng nghiệp của bạn sẽ nguyền rủa bạn.

Bạn có thể cần phải làm điều đó cho các tính toán tải cao, trong đó mỗi chu kỳ được lưu có nghĩa là vài phút thời gian chạy. Tuy nhiên, bạn nên tối ưu hóa một nơi tại một thời điểm và thực hiện kiểm tra hiệu suất mỗi lần để xem liệu bạn có thực sự làm cho trình biên dịch nhanh hơn hoặc phá vỡ logic trình biên dịch hay không.


1

Theo tôi biết trong một số máy nhân có thể cần tới 16 đến 32 chu kỳ máy. Vì vậy, , tùy thuộc vào loại máy, toán tử bithift nhanh hơn phép nhân / chia.

Tuy nhiên, một số máy nhất định có bộ xử lý toán học của chúng, chứa các hướng dẫn đặc biệt cho phép nhân / chia.


7
Những người viết trình biên dịch cho các máy đó cũng có khả năng đọc Hackers Delight và tối ưu hóa cho phù hợp.
Bo Persson

1

Tôi đồng ý với câu trả lời được đánh dấu của Drew Hall. Câu trả lời có thể sử dụng một số ghi chú bổ sung mặc dù.

Đối với đại đa số các nhà phát triển phần mềm, bộ xử lý và trình biên dịch không còn phù hợp với câu hỏi nữa. Hầu hết chúng ta đều vượt xa 8088 và MS-DOS. Nó có lẽ chỉ phù hợp với những người vẫn đang phát triển cho bộ xử lý nhúng ...

Tại công ty phần mềm của tôi, Math (add / sub / mul / div) nên được sử dụng cho tất cả toán học. Trong khi Shift nên được sử dụng khi chuyển đổi giữa các loại dữ liệu, vd. sử dụng byte theo n >> 8 chứ không phải n / 256.


Tôi đồng ý với bạn quá. Tôi theo cùng một hướng dẫn trong tiềm thức, mặc dù tôi chưa bao giờ có yêu cầu chính thức để làm như vậy.
Hội trường Drew

0

Trong trường hợp số nguyên đã ký và dịch chuyển phải so với phép chia, nó có thể tạo ra sự khác biệt. Đối với các số âm, các vòng làm tròn làm tròn về phía vô cực âm trong khi các vòng chia về 0. Tất nhiên trình biên dịch sẽ thay đổi phép chia thành một thứ rẻ hơn, nhưng nó thường sẽ thay đổi nó thành một thứ có hành vi làm tròn giống như phép chia, bởi vì nó không thể chứng minh rằng biến sẽ không âm hoặc đơn giản là nó không quan tâm. Vì vậy, nếu bạn có thể chứng minh rằng một số sẽ không âm hoặc nếu bạn không quan tâm đến cách nó sẽ làm tròn, bạn có thể thực hiện tối ưu hóa đó theo cách có nhiều khả năng tạo ra sự khác biệt.


hoặc chuyển số sangunsigned
Lie Ryan

4
Bạn có chắc chắn rằng hành vi thay đổi được tiêu chuẩn hóa? Tôi đã có ấn tượng rằng sự thay đổi bên phải trên ints âm được xác định theo triển khai.
Kerrek SB

1
Mặc dù có lẽ bạn nên đề cập rằng mã dựa trên bất kỳ hành vi cụ thể nào đối với các số âm chuyển sang phải nên ghi lại yêu cầu đó, nhưng lợi thế của dịch chuyển phải là rất lớn trong trường hợp nó tự nhiên mang lại giá trị đúng và toán tử phân chia sẽ tạo ra mã để lãng phí thời gian tính toán một giá trị không mong muốn mà mã người dùng sau đó sẽ phải lãng phí thêm thời gian để điều chỉnh để mang lại những gì mà sự thay đổi sẽ đưa ra ngay từ đầu. Trên thực tế, nếu tôi có máy khoan của mình, trình biên dịch sẽ có tùy chọn để vặn vẹo khi cố gắng thực hiện phân chia đã ký, kể từ ...
supercat

1
... mã biết toán hạng là dương có thể cải thiện tối ưu hóa nếu nó được chuyển thành không dấu trước khi phân chia (có thể chuyển trở lại ký sau đó) và mã biết rằng toán hạng có thể âm tính thường xử lý trường hợp đó một cách rõ ràng (trong trường hợp nào đó người ta cũng có thể cho rằng họ là tích cực).
supercat

0

Kiểm tra Python thực hiện cùng một phép nhân 100 triệu lần so với cùng một số ngẫu nhiên.

>>> from timeit import timeit
>>> setup_str = 'import scipy; from scipy import random; scipy.random.seed(0)'
>>> N = 10*1000*1000
>>> timeit('x=random.randint(65536);', setup=setup_str, number=N)
1.894096851348877 # Time from generating the random #s and no opperati

>>> timeit('x=random.randint(65536); x*2', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); x << 1', setup=setup_str, number=N)
2.2616429328918457

>>> timeit('x=random.randint(65536); x*10', setup=setup_str, number=N)
2.2799630165100098
>>> timeit('x=random.randint(65536); (x << 3) + (x<<1)', setup=setup_str, number=N)
2.9485139846801758

>>> timeit('x=random.randint(65536); x // 2', setup=setup_str, number=N)
2.490908145904541
>>> timeit('x=random.randint(65536); x / 2', setup=setup_str, number=N)
2.4757170677185059
>>> timeit('x=random.randint(65536); x >> 1', setup=setup_str, number=N)
2.2316000461578369

Vì vậy, khi thực hiện một ca thay vì nhân / chia với sức mạnh bằng hai trong trăn, sẽ có một sự cải thiện nhẹ (~ 10% cho phép chia; ~ 1% cho phép nhân). Nếu nó không phải là sức mạnh của hai, có khả năng sẽ có một sự chậm lại đáng kể.

Một lần nữa, các # này sẽ thay đổi tùy thuộc vào bộ xử lý, trình biên dịch của bạn (hoặc trình thông dịch - đã thực hiện bằng python để đơn giản).

Cũng như mọi người khác, đừng tối ưu hóa sớm. Viết mã rất dễ đọc, hồ sơ nếu nó không đủ nhanh, và sau đó cố gắng tối ưu hóa các phần chậm. Hãy nhớ rằng, trình biên dịch của bạn là tối ưu hóa tốt hơn nhiều so với bạn.


0

Có những tối ưu hóa trình biên dịch không thể thực hiện được vì chúng chỉ hoạt động với một bộ đầu vào giảm.

Bên dưới có mã mẫu c ++ có thể thực hiện phép chia nhanh hơn khi thực hiện phép nhân 64 bit "Nhân với đối ứng". Cả tử số và mẫu số phải dưới ngưỡng nhất định. Lưu ý rằng nó phải được biên dịch để sử dụng các lệnh 64 bit để thực sự nhanh hơn phân chia bình thường.

#include <stdio.h>
#include <chrono>

static const unsigned s_bc = 32;
static const unsigned long long s_p = 1ULL << s_bc;
static const unsigned long long s_hp = s_p / 2;

static unsigned long long s_f;
static unsigned long long s_fr;

static void fastDivInitialize(const unsigned d)
{
    s_f = s_p / d;
    s_fr = s_f * (s_p - (s_f * d));
}

static unsigned fastDiv(const unsigned n)
{
    return (s_f * n + ((s_fr * n + s_hp) >> s_bc)) >> s_bc;
}

static bool fastDivCheck(const unsigned n, const unsigned d)
{
    // 32 to 64 cycles latency on modern cpus
    const unsigned expected = n / d;

    // At least 10 cycles latency on modern cpus
    const unsigned result = fastDiv(n);

    if (result != expected)
    {
        printf("Failed for: %u/%u != %u\n", n, d, expected);
        return false;
    }

    return true;
}

int main()
{
    unsigned result = 0;

    // Make sure to verify it works for your expected set of inputs
    const unsigned MAX_N = 65535;
    const unsigned MAX_D = 40000;

    const double ONE_SECOND_COUNT = 1000000000.0;

    auto t0 = std::chrono::steady_clock::now();
    unsigned count = 0;
    printf("Verifying...\n");
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        fastDivInitialize(d);
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            count += !fastDivCheck(n, d);
        }
    }
    auto t1 = std::chrono::steady_clock::now();
    printf("Errors: %u / %u (%.4fs)\n", count, MAX_D * (MAX_N + 1), (t1 - t0).count() / ONE_SECOND_COUNT);

    t0 = t1;
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        fastDivInitialize(d);
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            result += fastDiv(n);
        }
    }
    t1 = std::chrono::steady_clock::now();
    printf("Fast division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);

    t0 = t1;
    count = 0;
    for (unsigned d = 1; d <= MAX_D; ++d)
    {
        for (unsigned n = 0; n <= MAX_N; ++n)
        {
            result += n / d;
        }
    }
    t1 = std::chrono::steady_clock::now();
    printf("Normal division time: %.4fs\n", (t1 - t0).count() / ONE_SECOND_COUNT);

    getchar();
    return result;
}

0

Tôi nghĩ trong trường hợp bạn muốn nhân hoặc chia cho lũy thừa hai lần, bạn không thể sai khi sử dụng toán tử bithift, ngay cả khi trình biên dịch chuyển đổi chúng thành MUL / DIV, bởi vì một số vi xử lý (thực sự, một macro) dù sao đi nữa, vì vậy đối với những trường hợp đó bạn sẽ đạt được sự cải thiện, đặc biệt là nếu độ dịch chuyển lớn hơn 1. Hoặc rõ ràng hơn, nếu CPU không có toán tử bẻ khóa, dù sao nó cũng sẽ là MUL / DIV, nhưng nếu CPU có Toán tử bithift, bạn tránh một nhánh vi mã và đây là một vài hướng dẫn ít hơn.

Tôi đang viết một số mã ngay bây giờ đòi hỏi nhiều thao tác nhân đôi / giảm một nửa vì nó đang hoạt động trên cây nhị phân dày đặc, và có một thao tác nữa mà tôi nghi ngờ có thể tối ưu hơn một phép cộng - một bên trái (sức mạnh của hai nhân ) thay đổi với một bổ sung. Điều này có thể được thay thế bằng dịch chuyển trái và xor nếu độ dịch chuyển rộng hơn số bit bạn muốn thêm, ví dụ đơn giản là (i << 1) ^ 1, thêm một vào giá trị nhân đôi. Điều này tất nhiên không áp dụng cho một sự thay đổi bên phải (sức mạnh của hai lần phân chia) bởi vì chỉ một sự thay đổi bên trái (chút cuối) lấp đầy khoảng trống với các số không.

Trong mã của tôi, các phép nhân / chia cho hai và lũy thừa của hai phép toán được sử dụng rất mạnh và vì các công thức đã khá ngắn, mỗi lệnh có thể được loại bỏ có thể là một mức tăng đáng kể. Nếu bộ xử lý không hỗ trợ các toán tử bẻ khóa này, sẽ không có mức tăng nào xảy ra nhưng cũng không bị mất.

Ngoài ra, trong các thuật toán tôi đang viết, chúng đại diện trực quan cho các chuyển động xảy ra để theo nghĩa đó chúng thực sự rõ ràng hơn. Phía bên trái của cây nhị phân lớn hơn và bên phải nhỏ hơn. Cũng như vậy, trong mã của tôi, số lẻ và số chẵn có một ý nghĩa đặc biệt và tất cả trẻ em thuận tay trái trong cây đều là số lẻ và tất cả trẻ em tay phải, và gốc, đều chẵn. Trong một số trường hợp, điều mà tôi chưa gặp phải, nhưng thực sự, tôi thậm chí không nghĩ về điều này, x & 1 có thể là một hoạt động tối ưu hơn so với x% 2. x & 1 trên số chẵn sẽ tạo ra số 0, nhưng sẽ tạo 1 cho số lẻ.

Đi xa hơn một chút so với chỉ nhận dạng lẻ / chẵn, nếu tôi nhận được số 0 cho x & 3 tôi biết rằng 4 là một yếu tố của số của chúng tôi, và tương tự cho x% 7 cho 8, v.v. Tôi biết rằng những trường hợp này có thể có tiện ích hạn chế nhưng thật tuyệt khi biết rằng bạn có thể tránh hoạt động mô đun và sử dụng thao tác logic bitwise, bởi vì hoạt động bitwise hầu như luôn luôn nhanh nhất và ít có khả năng mơ hồ nhất đối với trình biên dịch.

Tôi phát minh ra khá nhiều lĩnh vực cây nhị phân dày đặc vì vậy tôi hy vọng rằng mọi người có thể không nắm bắt được giá trị của nhận xét này, vì rất hiếm khi mọi người chỉ muốn thực hiện các yếu tố chỉ dựa trên sức mạnh của hai hoặc chỉ nhân / chia sức mạnh của hai.


0

Cho dù nó thực sự nhanh hơn phụ thuộc vào phần cứng và trình biên dịch thực sự được sử dụng.


0

Nếu bạn so sánh đầu ra cho cú pháp x + x, x * 2 và x << 1 trên trình biên dịch gcc, thì bạn sẽ nhận được kết quả tương tự trong tập hợp x86: https://godbolt.org/z/JLpp0j

        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        mov     eax, DWORD PTR [rbp-4]
        add     eax, eax
        pop     rbp
        ret

Vì vậy, bạn có thể coi gcc là đủ thông minh để xác định giải pháp tốt nhất của riêng mình độc lập với những gì bạn đã nhập.


0

Tôi cũng muốn xem liệu tôi có thể đánh bại Nhà không. đây là một bit tổng quát hơn cho bất kỳ số nào bằng bất kỳ số nhân nào. các macro tôi đã thực hiện chậm hơn khoảng 25% đến hai lần so với phép nhân * bình thường. như đã nói bởi những người khác nếu nó gần với bội số của 2 hoặc được tạo thành từ một số bội số của 2 bạn có thể giành chiến thắng. như X * 23 được tạo thành từ (X << 4) + (X << 2) + (X << 1) + X sẽ chậm hơn thì X * 65 được tạo thành từ (X << 6) + X.

#include <stdio.h>
#include <time.h>

#define MULTIPLYINTBYMINUS(X,Y) (-((X >> 30) & 1)&(Y<<30))+(-((X >> 29) & 1)&(Y<<29))+(-((X >> 28) & 1)&(Y<<28))+(-((X >> 27) & 1)&(Y<<27))+(-((X >> 26) & 1)&(Y<<26))+(-((X >> 25) & 1)&(Y<<25))+(-((X >> 24) & 1)&(Y<<24))+(-((X >> 23) & 1)&(Y<<23))+(-((X >> 22) & 1)&(Y<<22))+(-((X >> 21) & 1)&(Y<<21))+(-((X >> 20) & 1)&(Y<<20))+(-((X >> 19) & 1)&(Y<<19))+(-((X >> 18) & 1)&(Y<<18))+(-((X >> 17) & 1)&(Y<<17))+(-((X >> 16) & 1)&(Y<<16))+(-((X >> 15) & 1)&(Y<<15))+(-((X >> 14) & 1)&(Y<<14))+(-((X >> 13) & 1)&(Y<<13))+(-((X >> 12) & 1)&(Y<<12))+(-((X >> 11) & 1)&(Y<<11))+(-((X >> 10) & 1)&(Y<<10))+(-((X >> 9) & 1)&(Y<<9))+(-((X >> 8) & 1)&(Y<<8))+(-((X >> 7) & 1)&(Y<<7))+(-((X >> 6) & 1)&(Y<<6))+(-((X >> 5) & 1)&(Y<<5))+(-((X >> 4) & 1)&(Y<<4))+(-((X >> 3) & 1)&(Y<<3))+(-((X >> 2) & 1)&(Y<<2))+(-((X >> 1) & 1)&(Y<<1))+(-((X >> 0) & 1)&(Y<<0))
#define MULTIPLYINTBYSHIFT(X,Y) (((((X >> 30) & 1)<<31)>>31)&(Y<<30))+(((((X >> 29) & 1)<<31)>>31)&(Y<<29))+(((((X >> 28) & 1)<<31)>>31)&(Y<<28))+(((((X >> 27) & 1)<<31)>>31)&(Y<<27))+(((((X >> 26) & 1)<<31)>>31)&(Y<<26))+(((((X >> 25) & 1)<<31)>>31)&(Y<<25))+(((((X >> 24) & 1)<<31)>>31)&(Y<<24))+(((((X >> 23) & 1)<<31)>>31)&(Y<<23))+(((((X >> 22) & 1)<<31)>>31)&(Y<<22))+(((((X >> 21) & 1)<<31)>>31)&(Y<<21))+(((((X >> 20) & 1)<<31)>>31)&(Y<<20))+(((((X >> 19) & 1)<<31)>>31)&(Y<<19))+(((((X >> 18) & 1)<<31)>>31)&(Y<<18))+(((((X >> 17) & 1)<<31)>>31)&(Y<<17))+(((((X >> 16) & 1)<<31)>>31)&(Y<<16))+(((((X >> 15) & 1)<<31)>>31)&(Y<<15))+(((((X >> 14) & 1)<<31)>>31)&(Y<<14))+(((((X >> 13) & 1)<<31)>>31)&(Y<<13))+(((((X >> 12) & 1)<<31)>>31)&(Y<<12))+(((((X >> 11) & 1)<<31)>>31)&(Y<<11))+(((((X >> 10) & 1)<<31)>>31)&(Y<<10))+(((((X >> 9) & 1)<<31)>>31)&(Y<<9))+(((((X >> 8) & 1)<<31)>>31)&(Y<<8))+(((((X >> 7) & 1)<<31)>>31)&(Y<<7))+(((((X >> 6) & 1)<<31)>>31)&(Y<<6))+(((((X >> 5) & 1)<<31)>>31)&(Y<<5))+(((((X >> 4) & 1)<<31)>>31)&(Y<<4))+(((((X >> 3) & 1)<<31)>>31)&(Y<<3))+(((((X >> 2) & 1)<<31)>>31)&(Y<<2))+(((((X >> 1) & 1)<<31)>>31)&(Y<<1))+(((((X >> 0) & 1)<<31)>>31)&(Y<<0))
int main()
{
    int randomnumber=23;
    int randomnumber2=23;
    int checknum=23;
    clock_t start, diff;
    srand(time(0));
    start = clock();
    for(int i=0;i<1000000;i++)
    {
        randomnumber = rand() % 10000;
        randomnumber2 = rand() % 10000;
        checknum=MULTIPLYINTBYMINUS(randomnumber,randomnumber2);
        if (checknum!=randomnumber*randomnumber2)
        {
            printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
        }
    }
    diff = clock() - start;
    int msec = diff * 1000 / CLOCKS_PER_SEC;
    printf("MULTIPLYINTBYMINUS Time %d milliseconds", msec);
    start = clock();
    for(int i=0;i<1000000;i++)
    {
        randomnumber = rand() % 10000;
        randomnumber2 = rand() % 10000;
        checknum=MULTIPLYINTBYSHIFT(randomnumber,randomnumber2);
        if (checknum!=randomnumber*randomnumber2)
        {
            printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
        }
    }
    diff = clock() - start;
    msec = diff * 1000 / CLOCKS_PER_SEC;
    printf("MULTIPLYINTBYSHIFT Time %d milliseconds", msec);
    start = clock();
    for(int i=0;i<1000000;i++)
    {
        randomnumber = rand() % 10000;
        randomnumber2 = rand() % 10000;
        checknum= randomnumber*randomnumber2;
        if (checknum!=randomnumber*randomnumber2)
        {
            printf("s %i and %i and %i",checknum,randomnumber,randomnumber2);
        }
    }
    diff = clock() - start;
    msec = diff * 1000 / CLOCKS_PER_SEC;
    printf("normal * Time %d milliseconds", msec);
    return 0;
}
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.