Tôi nhận thấy một điều tò mò trên máy tính của tôi. * Bài kiểm tra chia nhỏ viết tay nhanh hơn đáng kể so với người %
vận hành. Hãy xem xét ví dụ tối thiểu:
* Máy xâu chuỗi AMD Ryzen 2990WX, GCC 9.2.0
static int divisible_ui_p(unsigned int m, unsigned int a)
{
if (m <= a) {
if (m == a) {
return 1;
}
return 0;
}
m += a;
m >>= __builtin_ctz(m);
return divisible_ui_p(m, a);
}
Ví dụ được giới hạn bởi lẻ a
và m > 0
. Tuy nhiên, nó có thể dễ dàng khái quát cho tất cả a
và m
. Mã chỉ chuyển đổi phân chia thành một loạt các bổ sung.
Bây giờ hãy xem xét chương trình thử nghiệm được biên dịch với -std=c99 -march=native -O3
:
for (unsigned int a = 1; a < 100000; a += 2) {
for (unsigned int m = 1; m < 100000; m += 1) {
#if 1
volatile int r = divisible_ui_p(m, a);
#else
volatile int r = (m % a == 0);
#endif
}
}
... và kết quả trên máy tính của tôi:
| implementation | time [secs] |
|--------------------|-------------|
| divisible_ui_p | 8.52user |
| builtin % operator | 17.61user |
Do đó nhanh hơn 2 lần.
Câu hỏi: Bạn có thể cho tôi biết mã hoạt động trên máy của bạn như thế nào không? Có bỏ lỡ cơ hội tối ưu hóa trong GCC không? Bạn có thể làm bài kiểm tra này thậm chí nhanh hơn?
CẬP NHẬT: Theo yêu cầu, đây là một ví dụ tái sản xuất tối thiểu:
#include <assert.h>
static int divisible_ui_p(unsigned int m, unsigned int a)
{
if (m <= a) {
if (m == a) {
return 1;
}
return 0;
}
m += a;
m >>= __builtin_ctz(m);
return divisible_ui_p(m, a);
}
int main()
{
for (unsigned int a = 1; a < 100000; a += 2) {
for (unsigned int m = 1; m < 100000; m += 1) {
assert(divisible_ui_p(m, a) == (m % a == 0));
#if 1
volatile int r = divisible_ui_p(m, a);
#else
volatile int r = (m % a == 0);
#endif
}
}
return 0;
}
được biên dịch với gcc -std=c99 -march=native -O3 -DNDEBUG
trên AMD Ryzen Threadripper 2990WX với
gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0
CẬP NHẬT2: Theo yêu cầu, phiên bản có thể xử lý bất kỳ a
và m
(nếu bạn cũng muốn tránh tràn số nguyên, thử nghiệm phải được thực hiện với loại số nguyên dài gấp đôi số nguyên đầu vào):
int divisible_ui_p(unsigned int m, unsigned int a)
{
#if 1
/* handles even a */
int alpha = __builtin_ctz(a);
if (alpha) {
if (__builtin_ctz(m) < alpha) {
return 0;
}
a >>= alpha;
}
#endif
while (m > a) {
m += a;
m >>= __builtin_ctz(m);
}
if (m == a) {
return 1;
}
#if 1
/* ensures that 0 is divisible by anything */
if (m == 0) {
return 1;
}
#endif
return 0;
}
r
s mà bạn tính toán thực sự bằng nhau.
a % b
có b
nhỏ hơn nhiều a
. Thông qua hầu hết các lần lặp trong trường hợp thử nghiệm của bạn, chúng có kích thước tương tự hoặc b
lớn hơn và phiên bản của bạn có thể nhanh hơn trên nhiều CPU trong các tình huống đó.