Tôi đang viết một số mã bằng Java, tại một số điểm, dòng chảy của chương trình được xác định bởi hai biến int, "a" và "b", có khác không (lưu ý: a và b không bao giờ âm và không bao giờ trong phạm vi tràn số nguyên).
Tôi có thể đánh giá nó với
if (a != 0 && b != 0) { /* Some code */ }
Hay cách khác
if (a*b != 0) { /* Some code */ }
Bởi vì tôi hy vọng đoạn mã đó sẽ chạy hàng triệu lần mỗi lần chạy, tôi đã tự hỏi cái nào sẽ nhanh hơn. Tôi đã thực hiện thử nghiệm bằng cách so sánh chúng trên một mảng được tạo ngẫu nhiên rất lớn và tôi cũng tò mò muốn xem mức độ thưa thớt của mảng (phần dữ liệu = 0) sẽ ảnh hưởng đến kết quả như thế nào:
long time;
final int len = 50000000;
int arbitrary = 0;
int[][] nums = new int[2][len];
for (double fraction = 0 ; fraction <= 0.9 ; fraction += 0.0078125) {
for(int i = 0 ; i < 2 ; i++) {
for(int j = 0 ; j < len ; j++) {
double random = Math.random();
if(random < fraction) nums[i][j] = 0;
else nums[i][j] = (int) (random*15 + 1);
}
}
time = System.currentTimeMillis();
for(int i = 0 ; i < len ; i++) {
if( /*insert nums[0][i]*nums[1][i]!=0 or nums[0][i]!=0 && nums[1][i]!=0*/ ) arbitrary++;
}
System.out.println(System.currentTimeMillis() - time);
}
Và kết quả cho thấy rằng nếu bạn mong đợi "a" hoặc "b" bằng 0 hơn ~ 3% thời gian, a*b != 0
thì nhanh hơn a!=0 && b!=0
:
Tôi tò mò muốn biết tại sao. Bất cứ ai có thể làm sáng tỏ? Nó là trình biên dịch hay nó ở cấp độ phần cứng?
Chỉnh sửa: Vì tò mò ... bây giờ tôi đã biết về dự đoán chi nhánh, tôi đã tự hỏi những gì so sánh tương tự sẽ hiển thị cho OR b là khác không:
Chúng ta thấy hiệu ứng tương tự của dự đoán nhánh như mong đợi, điều thú vị là đồ thị có phần bị lật dọc theo trục X.
Cập nhật
1- Tôi đã thêm vào !(a==0 || b==0)
phân tích để xem những gì xảy ra.
2- Tôi cũng bao gồm a != 0 || b != 0
, (a+b) != 0
và (a|b) != 0
vì tò mò, sau khi tìm hiểu về dự đoán chi nhánh. Nhưng chúng không tương đương về mặt logic với các biểu thức khác, vì chỉ một OR b cần phải khác không để trả về giá trị true, vì vậy chúng không có nghĩa là được so sánh về hiệu quả xử lý.
3- Tôi cũng đã thêm điểm chuẩn thực tế mà tôi đã sử dụng để phân tích, đó chỉ là lặp lại một biến int tùy ý.
4- Một số người đã đề nghị đưa a != 0 & b != 0
vào trái ngược a != 0 && b != 0
với dự đoán rằng nó sẽ hành xử chặt chẽ hơn a*b != 0
vì chúng tôi sẽ loại bỏ hiệu ứng dự đoán chi nhánh. Tôi không biết rằng nó &
có thể được sử dụng với các biến boolean, tôi nghĩ rằng nó chỉ được sử dụng cho các hoạt động nhị phân với số nguyên.
Lưu ý: Trong bối cảnh mà tôi đã xem xét tất cả những điều này, int overflow không phải là một vấn đề, nhưng đó chắc chắn là một sự cân nhắc quan trọng trong bối cảnh chung.
CPU: Intel Core i7-3610QM @ 2.3GHz
Phiên bản Java: 1.8.0_45
Môi trường thời gian chạy Java (TM) SE (bản dựng 1.8.0_45-b14)
Máy chủ 64-bit Java HotSpot (TM) (bản dựng 25,45-b02, chế độ hỗn hợp)
a != 0 & b != 0
.
a*b!=0
có một chi nhánh ít hơn
(1<<16) * (1<<16) == 0
nhưng cả hai đều khác không.
a*b
là 0 nếu một trong a
và b
bằng không; a|b
bằng không chỉ khi cả hai đều.
if (!(a == 0 || b == 0))
? Microbenchmark nổi tiếng là không đáng tin cậy, điều này dường như không thể đo lường được (~ 3% âm thanh giống như một lỗi sai đối với tôi).