Lựa chọn nào tốt hơn để sử dụng để chia số nguyên cho 2?


406

Kỹ thuật nào sau đây là lựa chọn tốt nhất để chia số nguyên cho 2 và tại sao?

Kỹ thuật 1:

x = x >> 1;

Kỹ thuật 2:

x = x / 2;

Đây xlà một số nguyên.


75
Nếu bạn thực sự muốn gán kết quả cho xmột lần nữa, thì không phù hợp theo cách này: nó phải là x >>= 1hoặc x /= 2, tùy thuộc vào những gì bạn định thể hiện với thao tác. Không phải vì nó nhanh hơn (bất kỳ trình biên dịch hiện đại nào cũng sẽ biên dịch tất cả các biến thể tương đương thành giống hệt nhau, lắp ráp nhanh) nhưng vì nó ít gây nhầm lẫn hơn.
leftaroundabout

33
Tôi không đồng ý với leftaroundabout. - Nhưng tôi nghĩ đáng chú ý rằng có một hoạt động được gọi là dịch chuyển số học trong nhiều ngôn ngữ lập trình giữ bit dấu hiệu tại chỗ và do đó hoạt động cho các giá trị đã ký như mong đợi. Cú pháp có thể giống như x = x >>> 1. Cũng lưu ý rằng tùy thuộc vào nền tảng và trình biên dịch, có thể khá hợp lý để tối ưu hóa thủ công các phép chia và phép nhân bằng cách sử dụng ca. - Suy nghĩ về các bộ điều khiển vi mô, ví dụ, hỗ trợ ALU trực tiếp cho phép nhân.
JimmyB

36
Tôi thích x /= 2bởi vì x >>= 1trông quá giống như liên kết đơn âm;)
dòng chảy

19
@leftaroundabout - Tôi chỉ thấy nó dễ đọc hơn rất nhiều để viết x = x / 2thay vì x /= 2. Có thể ưu tiên chủ quan :)
JimmyB

8
@HannoBinder: chắc chắn chủ quan, đặc biệt rất nhiều thói quen. IMO, trong một ngôn ngữ mà tất cả các toán tử số học có các ⬜=kết hợp, chúng nên được sử dụng bất cứ khi nào có thể. Nó loại bỏ nhiễu và nhấn mạnh vào thực tế đã xđược sửa đổi , trong khi =toán tử chung cho thấy rằng nó có một giá trị hoàn toàn mới độc lập với giá trị cũ. - Luôn tránh các nhà khai thác kết hợp (để nó có thể đọc được để một người chỉ biết khai thác toán học) có thể có quan điểm của mình là tốt, nhưng sau đó bạn sẽ cần phải từ bỏ những cực kỳ hữu ích ++, --, +=, quá.
leftaroundabout

Câu trả lời:


847

Sử dụng các hoạt động mô tả tốt nhất những gì bạn đang cố gắng làm.

  • Nếu bạn đang coi số đó là một chuỗi bit, hãy sử dụng bitshift.
  • Nếu bạn đang coi nó như một giá trị số, hãy sử dụng phép chia.

Lưu ý rằng chúng không chính xác tương đương. Họ có thể cho kết quả khác nhau cho số nguyên âm. Ví dụ:

-5 / 2  = -2
-5 >> 1 = -3

(nhàn rỗi)


20
Câu hỏi ban đầu cũng mơ hồ về thuật ngữ 'tốt nhất'. 'Tốt nhất' về tốc độ, khả năng đọc, đề thi để lừa học sinh, v.v ... Trong trường hợp không có lời giải thích về 'tốt nhất' nghĩa là gì, đây dường như là câu trả lời đúng nhất.
Ray

47
Trong C ++ 03, cả hai đều được xác định cho các số âm và có thể cho kết quả giống nhau. Trong C ++ 11, phép chia được xác định rõ cho các số âm, nhưng việc dịch chuyển vẫn được xác định.
James Kanze

2
Trong khi định nghĩa của / là thực hiện (thực hiện nếu làm tròn lên hoặc xuống cho số âm) được xác định trong các tiêu chuẩn C sớm. Nó phải luôn phù hợp với% (toán tử modulo / phần dư).
ctrl-alt-delor

7
"Xác định thực hiện" có nghĩa là người triển khai trình biên dịch phải chọn trong số một số lựa chọn thực hiện, thường có các ràng buộc đáng kể. Ở đây, một hạn chế là các toán tử %/toán tử phải nhất quán cho cả hai toán hạng dương và âm sao cho (a/b)*b+(a%b)==ađúng, bất kể dấu hiệu của ab. Thông thường, tác giả sẽ đưa ra các lựa chọn có được hiệu năng tốt nhất có thể từ CPU.
RBerteig

6
Vì vậy, tất cả những người nói rằng "trình biên dịch sẽ chuyển đổi nó thành một ca làm việc" là sai, phải không? Trừ khi trình biên dịch có thể đảm bảo rằng bạn đang xử lý một số nguyên không âm (có thể là hằng số hoặc là một số nguyên không dấu), nó không thể thay đổi nó thành một ca
Kip

225

Liệu cái đầu tiên trông giống như chia? Không. Nếu bạn muốn chia, sử dụng x / 2. Trình biên dịch có thể tối ưu hóa nó để sử dụng dịch chuyển bit nếu có thể (được gọi là giảm cường độ), điều này làm cho nó tối ưu hóa vi mô vô dụng nếu bạn tự làm.


15
Nhiều trình biên dịch sẽ không biến sự phân chia theo sức mạnh của hai thành một bithift. Đó sẽ là một tối ưu hóa không chính xác cho các số nguyên đã ký. Bạn nên thử nhìn vào đầu ra lắp ráp từ trình biên dịch của bạn và tự mình xem.
exDM69

1
IIRC Tôi đã sử dụng điều đó để làm giảm song song nhanh hơn trên CUDA (tránh div số nguyên). Tuy nhiên, điều này đã được hơn một năm trước, tôi tự hỏi ngày nay trình biên dịch CUDA thông minh như thế nào.
Nils

9
@ exDM69: Nhiều trình biên dịch sẽ làm điều đó ngay cả đối với các số nguyên đã ký và chỉ cần điều chỉnh chúng theo độ đã ký. Một công cụ tuyệt vời để chơi với những thứ này là: tinyurl.com/6uww253
PlasmaHH

19
@ exDM69: Và điều đó có liên quan, làm thế nào? Tôi nói "nếu có thể", không phải "luôn luôn". Nếu tối ưu hóa không chính xác, thì thực hiện thủ công sẽ không làm cho nó chính xác (cộng với như đã đề cập, GCC đủ thông minh để tìm ra sự thay thế phù hợp cho các số nguyên đã ký).
Cat Plus Plus

4
Nhìn vào trang WikiPedia, điều này rõ ràng gây tranh cãi, nhưng tôi sẽ không gọi đó là giảm sức mạnh. Việc giảm sức mạnh là khi, trong một vòng lặp, bạn giảm từ, nhân sang phép cộng, bằng cách thêm vào các giá trị trước đó trong vòng lặp. Đây là nhiều hơn một tối ưu hóa lổ nhìn trộm, mà trình biên dịch có thể làm khá đáng tin cậy.
Một sốCallMeTim

189

Để giải quyết: có rất nhiều lý do để ưu tiên sử dụng x = x / 2; Dưới đây là một số:

  • nó thể hiện ý định của bạn rõ ràng hơn (giả sử bạn không xử lý các bit đăng ký vặn bit hoặc một cái gì đó)

  • trình biên dịch sẽ giảm điều này thành một hoạt động thay đổi

  • ngay cả khi trình biên dịch không giảm nó và chọn thao tác chậm hơn ca, khả năng điều này sẽ ảnh hưởng đến hiệu suất chương trình của bạn theo cách có thể đo lường được là rất nhỏ (và nếu nó ảnh hưởng đến nó thì bạn có thể thực tế lý do để sử dụng ca)

  • nếu phép chia sẽ là một phần của biểu thức lớn hơn, bạn có nhiều khả năng có quyền ưu tiên hơn nếu bạn sử dụng toán tử chia:

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
  • số học đã ký có thể làm phức tạp mọi thứ thậm chí nhiều hơn vấn đề ưu tiên được đề cập ở trên

  • để nhắc lại - trình biên dịch sẽ thực hiện điều này cho bạn bằng mọi cách. Trên thực tế, nó sẽ chuyển đổi phép chia theo hằng số thành một loạt các ca, cộng và nhân cho tất cả các loại số, không chỉ là lũy thừa của hai. Xem câu hỏi này để biết các liên kết đến nhiều thông tin hơn về điều này.

Nói tóm lại, bạn không mua gì bằng cách mã hóa một ca khi bạn thực sự có ý định nhân hoặc chia, ngoại trừ có thể tăng khả năng giới thiệu một lỗi. Đã là cả đời kể từ khi trình biên dịch không đủ thông minh để tối ưu hóa loại điều này thành sự thay đổi khi thích hợp.


5
Cũng đáng nói thêm rằng trong khi có các quy tắc ưu tiên, thì không có gì sai khi sử dụng dấu ngoặc đơn. Trong khi sửa đổi một số mã sản xuất, tôi thực sự đã thấy một cái gì đó có dạnga/b/c*d (trong đó a..dký hiệu là các biến số) thay vì dễ đọc hơn nhiều (a*d)/(b*c).

1
Hiệu suất và tối ưu hóa phụ thuộc vào trình biên dịch và mục tiêu. Ví dụ, tôi thực hiện một số công việc cho một vi điều khiển trong đó mọi thứ cao hơn -O0 đều bị vô hiệu hóa trừ khi bạn mua trình biên dịch thương mại, vì vậy trình biên dịch chắc chắn sẽ không chuyển phân chia thành bithifts. Hơn nữa, bithifts mất một chu kỳ và phân chia mất 18 chu kỳ cho mục tiêu này và vì tốc độ xung nhịp của vi điều khiển khá thấp, đây thực sự có thể là một điểm nhấn hiệu suất đáng chú ý (nhưng nó phụ thuộc vào mã của bạn - bạn chắc chắn nên sử dụng / cho đến khi hồ sơ cho bạn biết đó là một vấn đề!)

4
@JackManey, nếu có bất kỳ khả năng nào a*dhoặc b*csẽ tạo ra một tràn, hình thức ít đọc hơn không tương đương và có một lợi thế rõ ràng. PS Tôi đồng ý rằng dấu ngoặc đơn là người bạn tốt nhất của bạn.
Đánh dấu tiền chuộc

@MarkRansom - Một điểm công bằng (mặc dù tôi đã chạy vào a/b/c*dmã R - trong bối cảnh tràn tràn có nghĩa là có gì đó sai nghiêm trọng với dữ liệu - và không phải là một khối mã C quan trọng về hiệu năng).

x=x/2;chỉ "rõ ràng" hơn x>>=1nếu xkhông bao giờ là số âm lẻ hoặc người ta không quan tâm đến các lỗi khác nhau. Mặt khác x=x/2;x>>=1;có ý nghĩa khác nhau. Nếu cái mà người ta cần là giá trị được tính theo x>>=1, tôi sẽ coi nó rõ ràng hơn x = (x & ~1)/2hoặc x = (x < 0) ? (x-1)/2 : x/2, hoặc bất kỳ công thức nào khác mà tôi có thể nghĩ đến khi sử dụng phép chia cho hai. Tương tự như vậy nếu một người cần giá trị được tính bằng x/=2, thì rõ ràng hơn ((x + ((unsigned)x>>31)>>1).
supercat

62

Cái nào là lựa chọn tốt nhất và tại sao để chia số nguyên cho 2?

Phụ thuộc vào những gì bạn có nghĩa là tốt nhất .

Nếu bạn muốn đồng nghiệp ghét bạn hoặc làm cho mã của bạn khó đọc, tôi chắc chắn sẽ chọn tùy chọn đầu tiên.

Nếu bạn muốn chia một số cho 2, hãy đi với số thứ hai.

Hai cái này không tương đương nhau, chúng không hành xử giống nhau nếu số âm hoặc bên trong các biểu thức lớn hơn - bitshift có mức độ ưu tiên thấp hơn +hoặc -, phép chia có độ ưu tiên cao hơn.

Bạn nên viết mã của bạn để thể hiện ý định của nó là gì. Nếu hiệu suất là mối quan tâm của bạn, đừng lo lắng, trình tối ưu hóa thực hiện tốt công việc tối ưu hóa vi mô này.


58

Chỉ cần sử dụng split ( /), giả sử nó là rõ ràng hơn. Trình biên dịch sẽ tối ưu hóa cho phù hợp.


34
Trình biên dịch nên tối ưu hóa cho phù hợp.
Noctis Skytower

12
Nếu trình biên dịch không tối ưu hóa phù hợp, bạn nên sử dụng trình biên dịch tốt hơn.
David Stone

3
@DavidStone: Trình biên dịch nào có thể tối ưu hóa việc phân chia số nguyên có thể âm có thể bằng bất kỳ hằng số nào ngoài 1 để có hiệu quả như một ca làm việc?
supercat

1
@supercat: Đó là một điểm tốt. Tất nhiên bạn có thể lưu trữ giá trị trong một số nguyên không dấu (mà tôi cảm thấy có tiếng xấu hơn nhiều so với khi kết hợp với các cảnh báo không khớp được ký / không dấu), và hầu hết các trình biên dịch cũng có cách bảo họ giả sử điều gì đó là đúng khi tối ưu hóa . Tôi muốn gói trong một macro tương thích và có một cái gì đó giống như ASSUME(x >= 0); x /= 2;trên x >>= 1;, nhưng đó vẫn là một điểm quan trọng để đưa lên.
David Stone

39

Tôi đồng ý với các câu trả lời khác mà bạn nên ủng hộ x / 2vì mục đích của nó rõ ràng hơn và trình biên dịch sẽ tối ưu hóa nó cho bạn.

Tuy nhiên, một lý do khác để thích x / 2hơn x >> 1là hành vi của >>phụ thuộc vào việc thực hiện nếu xlà một chữ ký intvà là tiêu cực.

Từ mục 6.5.7, gạch đầu dòng 5 của tiêu chuẩn ISO C99:

Kết quả E1 >> E2là vị trí bit E1dịch chuyển phải E2. Nếu E1có loại không dấu hoặc nếu E1có loại đã ký và giá trị không âm, giá trị của kết quả là phần không thể thiếu của thương số E1/ 2 E2. Nếu E1có loại đã ký và giá trị âm, giá trị kết quả được xác định theo thực hiện.


3
Đáng lưu ý rằng hành vi mà nhiều triển khai xác định cho x>>scalepowersố âm sẽ chính xác là những gì cần thiết khi chia giá trị cho lũy thừa hai cho các mục đích như hiển thị màn hình, trong khi sử dụng x/scalefactorsẽ sai trừ khi áp dụng sửa cho giá trị âm.
supercat

32

x / 2rõ ràng hơn và x >> 1không nhanh hơn nhiều (theo điểm chuẩn vi mô, nhanh hơn khoảng 30% đối với JVM Java). Như những người khác đã lưu ý, đối với các số âm, làm tròn số hơi khác nhau, vì vậy bạn phải xem xét điều này khi bạn muốn xử lý các số âm. Một số trình biên dịch có thể tự động chuyển đổi x / 2thành x >> 1nếu họ biết số này không thể âm (thậm chí tôi nghĩ rằng tôi không thể xác minh điều này).

Thậm chí x / 2có thể không sử dụng lệnh CPU phân chia (chậm), bởi vì một số phím tắt là có thể , nhưng nó vẫn chậm hơn x >> 1.

(Đây là câu hỏi C / C ++, các ngôn ngữ lập trình khác có nhiều toán tử hơn. Đối với Java cũng có dịch chuyển phải không dấu x >>> 1, lại khác nhau. Nó cho phép tính chính xác giá trị trung bình (trung bình) của hai giá trị, do đó (a + b) >>> 1sẽ trả về giá trị trung bình ngay cả đối với các giá trị rất lớn của ab. Điều này được yêu cầu ví dụ cho tìm kiếm nhị phân nếu các chỉ số mảng có thể rất lớn. Có một lỗi trong nhiều phiên bản tìm kiếm nhị phân , vì chúng được sử dụng (a + b) / 2để tính trung bình. 't hoạt động chính xác. Giải pháp chính xác là sử dụng (a + b) >>> 1thay thế.)


1
Trình biên dịch không thể chuyển đổi x/2sang x>>1trong trường hợp xcó thể âm tính. Nếu cái mà người ta muốn là giá trị x>>1sẽ tính toán, thì điều đó gần như chắc chắn sẽ nhanh hơn bất kỳ biểu thức nào liên quan đến x/2việc tính toán cùng một giá trị.
supercat

Bạn đúng rồi. Trình biên dịch chỉ có thể chuyển đổi x/2thành x>>1nếu anh ta biết giá trị không âm. Tôi sẽ cố gắng cập nhật câu trả lời của tôi.
Thomas Mueller

trình biên dịch vẫn tránh một divlệnh mặc dù, bằng cách chuyển đổi x/2thành (x + (x<0?1:0)) >> 1(trong đó >> là một dịch chuyển sang phải số học, dịch chuyển theo các bit ký hiệu). Điều này có 4 hướng dẫn: sao chép giá trị, shr (để chỉ lấy bit dấu trong reg), add, sar. goo.gl/4F8Ms4
Peter Cordes

Câu hỏi được gắn thẻ là C và C ++.
Josh Sanford

22

Knuth nói:

Tối ưu hóa sớm là gốc rễ của mọi tội lỗi.

Vì vậy, tôi đề nghị sử dụng x /= 2;

Theo cách này, mã rất dễ hiểu và tôi cũng nghĩ rằng việc tối ưu hóa thao tác này ở dạng đó, không có nghĩa là một sự khác biệt lớn cho bộ xử lý.


4
Bạn sẽ xem xét phương pháp ưa thích nào để giảm số lượng theo một lũy thừa bằng hai nếu một người muốn số nguyên duy trì tiên đề (áp dụng cho số tự nhiên và số thực) mà (n + d) / d = (n / d) + 1? Các vi phạm tiên đề khi mở rộng đồ họa sẽ gây ra các "đường nối" có thể nhìn thấy trong kết quả. Nếu ai đó muốn một cái gì đó đồng nhất và gần như đối xứng về số 0, hãy (n+8)>>4làm việc thật tốt. Bạn có thể đưa ra bất kỳ cách tiếp cận nào rõ ràng hoặc hiệu quả mà không cần sử dụng dịch chuyển đúng không?
supercat

19

Hãy xem đầu ra của trình biên dịch để giúp bạn quyết định. Tôi đã chạy thử nghiệm này trên x86-64 với
gcc (GCC) 4.2.1 20070719 [FreeBSD]

Cũng xem đầu ra trình biên dịch trực tuyến tại godbolt .

Những gì bạn thấy là trình biên dịch sử dụng một lệnh sarl(số học phải dịch chuyển) trong cả hai trường hợp, vì vậy nó nhận ra sự giống nhau giữa hai biểu thức. Nếu bạn sử dụng phép chia, trình biên dịch cũng cần điều chỉnh cho các số âm. Để làm điều đó, nó dịch chuyển bit dấu xuống bit thứ tự thấp nhất và thêm nó vào kết quả. Điều này khắc phục sự cố ngoài luồng khi dịch chuyển các số âm, so với những gì một phép chia sẽ làm.
Vì trường hợp chia làm 2 ca, trong khi trường hợp dịch chuyển rõ ràng chỉ làm một ca, giờ đây chúng ta có thể giải thích một số khác biệt về hiệu suất được đo bằng các câu trả lời khác ở đây.

Mã C với đầu ra lắp ráp:

Để phân chia, đầu vào của bạn sẽ là

int div2signed(int a) {
  return a / 2;
}

và điều này biên dịch thành

    movl    %edi, %eax
    shrl    $31, %eax
    addl    %edi, %eax
    sarl    %eax
    ret

tương tự cho ca

int shr2signed(int a) {
  return a >> 1;
}

với đầu ra:

    sarl    %edi
    movl    %edi, %eax
    ret

Tùy thuộc vào những gì người ta đang làm, nó có thể sửa lỗi từng lỗi một hoặc có thể gây ra lỗi do lỗi (so với những gì thực sự cần thiết) sẽ cần sử dụng thêm mã để sửa lỗi. Nếu những gì người ta muốn là một kết quả nổi, thì việc chuyển sang phải là nhanh hơn và dễ dàng hơn bất kỳ sự thay thế nào tôi biết.
supercat

Nếu bạn cần một sàn, không chắc bạn sẽ mô tả những gì bạn muốn là "chia cho 2"
Michael Donohue

Phân chia cả số tự nhiên và số thực duy trì tiên đề rằng (n + d) / d = (n / d) +1. Phân chia số thực cũng duy trì (-n) / d = - (n / d), một tiên đề vô nghĩa với số tự nhiên. Không thể có toán tử chia được đóng trên các số nguyên và duy trì cả hai tiên đề. Theo suy nghĩ của tôi, nói rằng tiên đề đầu tiên nên giữ cho tất cả các số và số thứ hai chỉ dành cho số thực có vẻ tự nhiên hơn so với việc nói rằng số thứ nhất nên giữ cho toàn bộ số hoặc số thực chứ không phải cho số nguyên. Hơn nữa, tôi tò mò trong trường hợp tiên đề thứ hai thực sự hữu ích .
supercat

1
Một phương pháp phân chia số nguyên thỏa mãn tiên đề đầu tiên sẽ phân chia dòng số thành các vùng có kích thước d. Phân vùng như vậy là hữu ích cho nhiều mục đích. Ngay cả khi người ta muốn có điểm dừng ở đâu đó ngoài 0 và -1, việc thêm phần bù sẽ di chuyển nó. Một phép chia số nguyên thỏa mãn tiên đề thứ hai sẽ phân chia dòng số thành các vùng có kích thước chủ yếu d, nhưng một trong số đó có kích thước 2*d-1. Không chính xác các phân chia "bằng nhau". Bạn có thể đưa ra gợi ý khi phân vùng lẻ bóng thực sự hữu ích?
supercat

Đầu ra trình biên dịch của bạn cho shr2sign là sai. gcc trên x86 chọn triển khai >> số nguyên đã ký với dịch chuyển số học ( sar). goo.gl/KRgIkb . Bài đăng danh sách gửi thư này ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) xác nhận rằng gcc trong lịch sử sử dụng dịch chuyển số học cho các số nguyên đã ký, do đó, rất khó để FreeBSD gcc 4.2.1 sử dụng dịch chuyển không dấu. Tôi đã cập nhật bài viết của bạn để sửa lỗi đó và đoạn đầu nói cả hai đã sử dụng shr, khi đó thực sự là SAR mà cả hai đều sử dụng. SHR là cách nó trích xuất bit dấu cho /trường hợp. Cũng bao gồm một liên kết godbolt.
Peter Cordes

15

Chỉ cần một ghi chú thêm -

x * = 0,5 thường sẽ nhanh hơn trong một số ngôn ngữ dựa trên VM - đáng chú ý là mô tả hành động, vì biến sẽ không phải được kiểm tra để chia cho 0.


2
@minitech: Đó là một thử nghiệm tồi. Tất cả các mã trong thử nghiệm là không đổi. Trước khi mã được JITed, nó sẽ loại bỏ tất cả các hằng số.

@ M28: Tôi khá chắc chắn rằng nội bộ của jsPerf (nghĩa là eval) đã khiến điều đó xảy ra mỗi lần. Bất kể, vâng, đó là một thử nghiệm khá tệ, bởi vì đó là một tối ưu hóa rất ngớ ngẩn.
Ry-

13

Sử dụng x = x / 2; HOẶC x /= 2;Vì có thể một lập trình viên mới sẽ làm việc với nó trong tương lai. Vì vậy, nó sẽ dễ dàng hơn cho anh ta để tìm hiểu những gì đang xảy ra trong dòng mã. Mọi người có thể không nhận thức được tối ưu hóa như vậy.


12

Tôi đang nói cho mục đích của các cuộc thi lập trình. Nói chung, họ có đầu vào rất lớn trong đó việc chia cho 2 diễn ra nhiều lần và được biết rằng đầu vào là dương hoặc âm.

x >> 1 sẽ tốt hơn x / 2. Tôi đã kiểm tra trên ideone.com bằng cách chạy một chương trình nơi diễn ra hơn 10 ^ 10 phân chia cho 2 thao tác. x / 2 mất gần 5,5 giây trong khi x >> 1 mất gần 2,6 giây cho cùng một chương trình.


1
Đối với các giá trị không dấu, một trình biên dịch nên tối ưu hóa x/2thành x>>1. Đối với các giá trị đã ký, gần như tất cả các triển khai xác định x>>1có ý nghĩa tương đương x/2nhưng có thể được tính nhanh hơn khi xcó giá trị dương và khác biệt hữu ích so với x/2khi xâm.
supercat

12

Tôi sẽ nói có một số điều cần xem xét.

  1. Bitshift nên nhanh hơn, vì không có tính toán đặc biệt nào thực sự cần thiết để dịch chuyển các bit, tuy nhiên như đã chỉ ra, có những vấn đề tiềm ẩn với số âm. Nếu bạn được đảm bảo có số dương, và đang tìm kiếm tốc độ thì tôi khuyên bạn nên dùng bithift.

  2. Toán tử phân chia rất dễ dàng cho con người đọc. Vì vậy, nếu bạn đang tìm kiếm khả năng đọc mã, bạn có thể sử dụng điều này. Lưu ý rằng lĩnh vực tối ưu hóa trình biên dịch đã đi một chặng đường dài, vì vậy làm cho mã dễ đọc và dễ hiểu là thực hành tốt.

  3. Tùy thuộc vào phần cứng cơ bản, hoạt động có thể có tốc độ khác nhau. Luật của Amdal là làm cho trường hợp phổ biến nhanh chóng. Vì vậy, bạn có thể có phần cứng có thể thực hiện các hoạt động khác nhau nhanh hơn những người khác. Ví dụ: nhân với 0,5 có thể nhanh hơn chia cho 2. (Cấp cho bạn có thể cần lấy sàn của phép nhân nếu bạn muốn thực thi phép chia số nguyên).

Nếu bạn theo đuổi hiệu suất thuần túy, tôi khuyên bạn nên tạo một số thử nghiệm có thể thực hiện các hoạt động hàng triệu lần. Lấy mẫu thực thi nhiều lần (cỡ mẫu của bạn) để xác định cái nào là tốt nhất về mặt thống kê với HĐH / Phần cứng / Trình biên dịch / Mã của bạn.


2
"Bitshift nên nhanh hơn". trình biên dịch sẽ tối ưu hóa sự phân chia thành bithifts
Trevor Hickey

Tôi hy vọng họ sẽ làm như vậy, nhưng trừ khi bạn có quyền truy cập vào nguồn của trình biên dịch, bạn không thể chắc chắn :)
James Oravec

1
Tôi cũng sẽ đề xuất bithift nếu việc triển khai của một người xử lý nó theo cách phổ biến nhất và cách người ta muốn xử lý các số âm khớp với những gì >>không và không khớp với những gì /.
supercat

12

Đối với CPU, các hoạt động dịch chuyển bit nhanh hơn các hoạt động phân chia. Tuy nhiên, trình biên dịch biết điều này và sẽ tối ưu hóa phù hợp đến mức có thể, vì vậy bạn có thể viết mã theo cách có ý nghĩa nhất và yên tâm khi biết rằng mã của bạn đang chạy hiệu quả. Nhưng hãy nhớ rằng một unsigned inthộp (trong một số trường hợp) có thể được tối ưu hóa tốt hơn một intlý do đã được chỉ ra trước đây. Nếu bạn không cần ký hiệu, thì không bao gồm bit dấu.


11

x = x / 2; là mã phù hợp để sử dụng .. nhưng một thao tác phụ thuộc vào chương trình của riêng bạn về cách sản lượng bạn muốn sản xuất.


11

Làm cho ý định của bạn rõ ràng hơn ... ví dụ, nếu bạn muốn chia, sử dụng x / 2 và để trình biên dịch tối ưu hóa nó để thay đổi toán tử (hoặc bất cứ điều gì khác).

Các bộ xử lý ngày nay sẽ không để những tối ưu hóa này ảnh hưởng đến hiệu suất của các chương trình của bạn.


10

Câu trả lời cho điều này sẽ phụ thuộc vào môi trường bạn đang làm việc.

  • Nếu bạn đang làm việc trên một vi điều khiển 8 bit hoặc bất cứ thứ gì không có phần cứng hỗ trợ cho phép nhân, việc dịch chuyển bit được mong đợi và phổ biến, và trong khi trình biên dịch gần như chắc chắn sẽ biến x /= 2thànhx >>= 1 , sự hiện diện của biểu tượng phân chia sẽ làm tăng thêm lông mày trong môi trường đó sử dụng một sự thay đổi để thực hiện một bộ phận.
  • Nếu bạn đang làm việc trong môi trường hoặc phần mã quan trọng về hiệu năng, hoặc mã của bạn có thể được biên dịch với tối ưu hóa trình biên dịch, x >>= 1 với một bình luận giải thích lý do của nó có lẽ là tốt nhất cho mục đích rõ ràng.
  • Nếu bạn không thuộc một trong các điều kiện trên, hãy làm cho mã của bạn dễ đọc hơn bằng cách sử dụng đơn giản x /= 2. Tốt hơn để lưu lập trình viên tiếp theo tình cờ xem mã của bạn trong 10 giây thực hiện thao tác thay đổi của bạn hơn là không cần thiết phải chứng minh rằng bạn biết sự thay đổi là tối ưu hóa trình biên dịch sans hiệu quả hơn.

Tất cả những giả định số nguyên không dấu. Sự thay đổi đơn giản có lẽ không phải là những gì bạn muốn ký. Ngoài ra, DanielH đưa ra một điểm tốt về việc sử dụng x *= 0.5cho một số ngôn ngữ nhất định như ActionScript.


8

mod 2, kiểm tra = 1. dunno cú pháp trong c. nhưng điều này có thể là nhanh nhất.


7

tạo ra sự thay đổi bên phải:

q = i >> n; is the same as: q = i / 2**n;

điều này đôi khi được sử dụng để tăng tốc các chương trình với chi phí rõ ràng. Tôi không nghĩ bạn nên làm điều đó. Trình biên dịch đủ thông minh để thực hiện tăng tốc tự động. Điều này có nghĩa là việc thay đổi sẽ giúp bạn không phải trả giá bằng sự rõ ràng .

Hãy xem trang này từ Lập trình C ++ thực tế.


Nếu người ta muốn tính giá trị, ví dụ như (x+128)>>8sẽ tính toán cho các giá trị xkhông gần mức tối đa, làm thế nào người ta có thể làm chính xác mà không cần thay đổi? Lưu ý rằng (x+128)/256sẽ không hoạt động. Bạn có biết bất kỳ biểu hiện tốt đẹp nào không?
supercat

7

Rõ ràng, nếu bạn đang viết mã cho người tiếp theo đọc nó, hãy tìm hiểu rõ ràng về "x / 2".

Tuy nhiên, nếu tốc độ là mục tiêu của bạn, hãy thử cả hai cách và thời gian kết quả.Vài tháng trước tôi đã làm việc với thói quen tích chập bitmap liên quan đến việc bước qua một mảng các số nguyên và chia mỗi phần tử cho 2. Tôi đã làm tất cả các cách để tối ưu hóa nó bao gồm cả thủ thuật cũ thay thế "x >> 1" cho "x / 2 ".

Khi tôi thực sự hẹn giờ cả hai cách tôi đã phát hiện ra điều ngạc nhiên là x / 2 nhanh hơn x >> 1

Điều này đã sử dụng Microsoft VS2008 C ++ với bật tối ưu hóa mặc định.


4

Về mặt hiệu suất. Hoạt động thay đổi của CPU nhanh hơn đáng kể so với phân chia mã op. Vì vậy, chia cho hai hoặc nhân với 2 vv tất cả đều được hưởng lợi từ các hoạt động thay đổi.

Như nhìn và cảm nhận. Là kỹ sư khi chúng ta trở nên gắn bó với mỹ phẩm mà ngay cả những quý cô xinh đẹp cũng không sử dụng! :)


3

X / Y là một toán tử dịch chuyển đúng ... và ">> "..nếu chúng ta muốn chia hai số nguyên, chúng ta có thể sử dụng toán tử cổ tức (/). toán tử shift được sử dụng để dịch chuyển các bit ..

x = x / 2; x / = 2; chúng ta có thể sử dụng như thế này ..


0

Trong khi x >> 1 nhanh hơn x / 2, việc sử dụng >> khi xử lý các giá trị âm sẽ phức tạp hơn một chút. Nó đòi hỏi một cái gì đó tương tự như sau:

// Extension Method
public static class Global {
    public static int ShiftDivBy2(this int x) {
        return (x < 0 ? x + 1 : x) >> 1;
    }
}
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.