lib/GCD.c: use binary GCD algorithm instead of Euclidean
authorZhaoxiu Zeng <zhaoxiu.zeng@gmail.com>
Sat, 21 May 2016 00:03:57 +0000 (17:03 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 21 May 2016 00:58:30 +0000 (17:58 -0700)
The binary GCD algorithm is based on the following facts:
1. If a and b are all evens, then gcd(a,b) = 2 * gcd(a/2, b/2)
2. If a is even and b is odd, then gcd(a,b) = gcd(a/2, b)
3. If a and b are all odds, then gcd(a,b) = gcd((a-b)/2, b) = gcd((a+b)/2, b)

Even on x86 machines with reasonable division hardware, the binary
algorithm runs about 25% faster (80% the execution time) than the
division-based Euclidian algorithm.

On platforms like Alpha and ARMv6 where division is a function call to
emulation code, it's even more significant.

There are two variants of the code here, depending on whether a fast
__ffs (find least significant set bit) instruction is available.  This
allows the unpredictable branches in the bit-at-a-time shifting loop to
be eliminated.

If fast __ffs is not available, the "even/odd" GCD variant is used.

I use the following code to benchmark:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define swap(a, b) \
do { \
a ^= b; \
b ^= a; \
a ^= b; \
} while (0)

unsigned long gcd0(unsigned long a, unsigned long b)
{
unsigned long r;

if (a < b) {
swap(a, b);
}

if (b == 0)
return a;

while ((r = a % b) != 0) {
a = b;
b = r;
}

return b;
}

unsigned long gcd1(unsigned long a, unsigned long b)
{
unsigned long r = a | b;

if (!a || !b)
return r;

b >>= __builtin_ctzl(b);

for (;;) {
a >>= __builtin_ctzl(a);
if (a == b)
return a << __builtin_ctzl(r);

if (a < b)
swap(a, b);
a -= b;
}
}

unsigned long gcd2(unsigned long a, unsigned long b)
{
unsigned long r = a | b;

if (!a || !b)
return r;

r &= -r;

while (!(b & r))
b >>= 1;

for (;;) {
while (!(a & r))
a >>= 1;
if (a == b)
return a;

if (a < b)
swap(a, b);
a -= b;
a >>= 1;
if (a & r)
a += b;
a >>= 1;
}
}

unsigned long gcd3(unsigned long a, unsigned long b)
{
unsigned long r = a | b;

if (!a || !b)
return r;

b >>= __builtin_ctzl(b);
if (b == 1)
return r & -r;

for (;;) {
a >>= __builtin_ctzl(a);
if (a == 1)
return r & -r;
if (a == b)
return a << __builtin_ctzl(r);

if (a < b)
swap(a, b);
a -= b;
}
}

unsigned long gcd4(unsigned long a, unsigned long b)
{
unsigned long r = a | b;

if (!a || !b)
return r;

r &= -r;

while (!(b & r))
b >>= 1;
if (b == r)
return r;

for (;;) {
while (!(a & r))
a >>= 1;
if (a == r)
return r;
if (a == b)
return a;

if (a < b)
swap(a, b);
a -= b;
a >>= 1;
if (a & r)
a += b;
a >>= 1;
}
}

static unsigned long (*gcd_func[])(unsigned long a, unsigned long b) = {
gcd0, gcd1, gcd2, gcd3, gcd4,
};

#define TEST_ENTRIES (sizeof(gcd_func) / sizeof(gcd_func[0]))

#if defined(__x86_64__)

#define rdtscll(val) do { \
unsigned long __a,__d; \
__asm__ __volatile__("rdtsc" : "=a" (__a), "=d" (__d)); \
(val) = ((unsigned long long)__a) | (((unsigned long long)__d)<<32); \
} while(0)

static unsigned long long benchmark_gcd_func(unsigned long (*gcd)(unsigned long, unsigned long),
unsigned long a, unsigned long b, unsigned long *res)
{
unsigned long long start, end;
unsigned long long ret;
unsigned long gcd_res;

rdtscll(start);
gcd_res = gcd(a, b);
rdtscll(end);

if (end >= start)
ret = end - start;
else
ret = ~0ULL - start + 1 + end;

*res = gcd_res;
return ret;
}

#else

static inline struct timespec read_time(void)
{
struct timespec time;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time);
return time;
}

static inline unsigned long long diff_time(struct timespec start, struct timespec end)
{
struct timespec temp;

if ((end.tv_nsec - start.tv_nsec) < 0) {
temp.tv_sec = end.tv_sec - start.tv_sec - 1;
temp.tv_nsec = 1000000000ULL + end.tv_nsec - start.tv_nsec;
} else {
temp.tv_sec = end.tv_sec - start.tv_sec;
temp.tv_nsec = end.tv_nsec - start.tv_nsec;
}

return temp.tv_sec * 1000000000ULL + temp.tv_nsec;
}

static unsigned long long benchmark_gcd_func(unsigned long (*gcd)(unsigned long, unsigned long),
unsigned long a, unsigned long b, unsigned long *res)
{
struct timespec start, end;
unsigned long gcd_res;

start = read_time();
gcd_res = gcd(a, b);
end = read_time();

*res = gcd_res;
return diff_time(start, end);
}

#endif

static inline unsigned long get_rand()
{
if (sizeof(long) == 8)
return (unsigned long)rand() << 32 | rand();
else
return rand();
}

int main(int argc, char **argv)
{
unsigned int seed = time(0);
int loops = 100;
int repeats = 1000;
unsigned long (*res)[TEST_ENTRIES];
unsigned long long elapsed[TEST_ENTRIES];
int i, j, k;

for (;;) {
int opt = getopt(argc, argv, "n:r:s:");
/* End condition always first */
if (opt == -1)
break;

switch (opt) {
case 'n':
loops = atoi(optarg);
break;
case 'r':
repeats = atoi(optarg);
break;
case 's':
seed = strtoul(optarg, NULL, 10);
break;
default:
/* You won't actually get here. */
break;
}
}

res = malloc(sizeof(unsigned long) * TEST_ENTRIES * loops);
memset(elapsed, 0, sizeof(elapsed));

srand(seed);
for (j = 0; j < loops; j++) {
unsigned long a = get_rand();
/* Do we have args? */
unsigned long b = argc > optind ? strtoul(argv[optind], NULL, 10) : get_rand();
unsigned long long min_elapsed[TEST_ENTRIES];
for (k = 0; k < repeats; k++) {
for (i = 0; i < TEST_ENTRIES; i++) {
unsigned long long tmp = benchmark_gcd_func(gcd_func[i], a, b, &res[j][i]);
if (k == 0 || min_elapsed[i] > tmp)
min_elapsed[i] = tmp;
}
}
for (i = 0; i < TEST_ENTRIES; i++)
elapsed[i] += min_elapsed[i];
}

for (i = 0; i < TEST_ENTRIES; i++)
printf("gcd%d: elapsed %llu\n", i, elapsed[i]);

k = 0;
srand(seed);
for (j = 0; j < loops; j++) {
unsigned long a = get_rand();
unsigned long b = argc > optind ? strtoul(argv[optind], NULL, 10) : get_rand();
for (i = 1; i < TEST_ENTRIES; i++) {
if (res[j][i] != res[j][0])
break;
}
if (i < TEST_ENTRIES) {
if (k == 0) {
k = 1;
fprintf(stderr, "Error:\n");
}
fprintf(stderr, "gcd(%lu, %lu): ", a, b);
for (i = 0; i < TEST_ENTRIES; i++)
fprintf(stderr, "%ld%s", res[j][i], i < TEST_ENTRIES - 1 ? ", " : "\n");
}
}

if (k == 0)
fprintf(stderr, "PASS\n");

free(res);

return 0;
}

Compiled with "-O2", on "VirtualBox 4.4.0-22-generic #38-Ubuntu x86_64" got:

  zhaoxiuzeng@zhaoxiuzeng-VirtualBox:~/develop$ ./gcd -r 500000 -n 10
  gcd0: elapsed 10174
  gcd1: elapsed 2120
  gcd2: elapsed 2902
  gcd3: elapsed 2039
  gcd4: elapsed 2812
  PASS
  zhaoxiuzeng@zhaoxiuzeng-VirtualBox:~/develop$ ./gcd -r 500000 -n 10
  gcd0: elapsed 9309
  gcd1: elapsed 2280
  gcd2: elapsed 2822
  gcd3: elapsed 2217
  gcd4: elapsed 2710
  PASS
  zhaoxiuzeng@zhaoxiuzeng-VirtualBox:~/develop$ ./gcd -r 500000 -n 10
  gcd0: elapsed 9589
  gcd1: elapsed 2098
  gcd2: elapsed 2815
  gcd3: elapsed 2030
  gcd4: elapsed 2718
  PASS
  zhaoxiuzeng@zhaoxiuzeng-VirtualBox:~/develop$ ./gcd -r 500000 -n 10
  gcd0: elapsed 9914
  gcd1: elapsed 2309
  gcd2: elapsed 2779
  gcd3: elapsed 2228
  gcd4: elapsed 2709
  PASS

[akpm@linux-foundation.org: avoid #defining a CONFIG_ variable]
Signed-off-by: Zhaoxiu Zeng <zhaoxiu.zeng@gmail.com>
Signed-off-by: George Spelvin <linux@horizon.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
18 files changed:
arch/Kconfig
arch/alpha/Kconfig
arch/arc/Kconfig
arch/arm/mm/Kconfig
arch/h8300/Kconfig
arch/m32r/Kconfig
arch/m68k/Kconfig.cpu
arch/metag/Kconfig
arch/microblaze/Kconfig
arch/mips/include/asm/cpu-features.h
arch/nios2/Kconfig
arch/openrisc/Kconfig
arch/parisc/Kconfig
arch/s390/Kconfig
arch/score/Kconfig
arch/sh/Kconfig
arch/sparc/Kconfig
lib/gcd.c

index 8f84fd2..b16e74e 100644 (file)
@@ -647,4 +647,7 @@ config COMPAT_OLD_SIGACTION
 config ARCH_NO_COHERENT_DMA_MMAP
        bool
 
+config CPU_NO_EFFICIENT_FFS
+       def_bool n
+
 source "kernel/gcov/Kconfig"
index fe99f89..7f312d8 100644 (file)
@@ -26,6 +26,7 @@ config ALPHA
        select MODULES_USE_ELF_RELA
        select ODD_RT_SIGACTION
        select OLD_SIGSUSPEND
+       select CPU_NO_EFFICIENT_FFS if !ALPHA_EV67
        help
          The Alpha is a 64-bit general-purpose processor designed and
          marketed by the Digital Equipment Corporation of blessed memory,
index 8894f7e..0dcbacf 100644 (file)
@@ -107,6 +107,7 @@ choice
 
 config ISA_ARCOMPACT
        bool "ARCompact ISA"
+       select CPU_NO_EFFICIENT_FFS
        help
          The original ARC ISA of ARC600/700 cores
 
index 5534766..cb569b6 100644 (file)
@@ -421,18 +421,21 @@ config CPU_32v3
        select CPU_USE_DOMAINS if MMU
        select NEED_KUSER_HELPERS
        select TLS_REG_EMUL if SMP || !MMU
+       select CPU_NO_EFFICIENT_FFS
 
 config CPU_32v4
        bool
        select CPU_USE_DOMAINS if MMU
        select NEED_KUSER_HELPERS
        select TLS_REG_EMUL if SMP || !MMU
+       select CPU_NO_EFFICIENT_FFS
 
 config CPU_32v4T
        bool
        select CPU_USE_DOMAINS if MMU
        select NEED_KUSER_HELPERS
        select TLS_REG_EMUL if SMP || !MMU
+       select CPU_NO_EFFICIENT_FFS
 
 config CPU_32v5
        bool
index 986ea84..aa232de 100644 (file)
@@ -20,6 +20,7 @@ config H8300
        select HAVE_KERNEL_GZIP
        select HAVE_KERNEL_LZO
        select HAVE_ARCH_KGDB
+       select CPU_NO_EFFICIENT_FFS
 
 config RWSEM_GENERIC_SPINLOCK
        def_bool y
index c82b292..3cc8498 100644 (file)
@@ -17,6 +17,7 @@ config M32R
        select ARCH_USES_GETTIMEOFFSET
        select MODULES_USE_ELF_RELA
        select HAVE_DEBUG_STACKOVERFLOW
+       select CPU_NO_EFFICIENT_FFS
 
 config SBUS
        bool
index c1beb5a..8ace920 100644 (file)
@@ -40,6 +40,7 @@ config M68000
        select CPU_HAS_NO_MULDIV64
        select CPU_HAS_NO_UNALIGNED
        select GENERIC_CSUM
+       select CPU_NO_EFFICIENT_FFS
        help
          The Freescale (was Motorola) 68000 CPU is the first generation of
          the well known M68K family of processors. The CPU core as well as
@@ -51,6 +52,7 @@ config MCPU32
        bool
        select CPU_HAS_NO_BITFIELDS
        select CPU_HAS_NO_UNALIGNED
+       select CPU_NO_EFFICIENT_FFS
        help
          The Freescale (was then Motorola) CPU32 is a CPU core that is
          based on the 68020 processor. For the most part it is used in
@@ -130,6 +132,7 @@ config M5206
        depends on !MMU
        select COLDFIRE_SW_A7
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Motorola ColdFire 5206 processor support.
 
@@ -138,6 +141,7 @@ config M5206e
        depends on !MMU
        select COLDFIRE_SW_A7
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Motorola ColdFire 5206e processor support.
 
@@ -163,6 +167,7 @@ config M5249
        depends on !MMU
        select COLDFIRE_SW_A7
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Motorola ColdFire 5249 processor support.
 
@@ -171,6 +176,7 @@ config M525x
        depends on !MMU
        select COLDFIRE_SW_A7
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Freescale (Motorola) Coldfire 5251/5253 processor support.
 
@@ -189,6 +195,7 @@ config M5272
        depends on !MMU
        select COLDFIRE_SW_A7
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Motorola ColdFire 5272 processor support.
 
@@ -217,6 +224,7 @@ config M5307
        select COLDFIRE_SW_A7
        select HAVE_CACHE_CB
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Motorola ColdFire 5307 processor support.
 
@@ -242,6 +250,7 @@ config M5407
        select COLDFIRE_SW_A7
        select HAVE_CACHE_CB
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Motorola ColdFire 5407 processor support.
 
@@ -251,6 +260,7 @@ config M547x
        select MMU_COLDFIRE if MMU
        select HAVE_CACHE_CB
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Freescale ColdFire 5470/5471/5472/5473/5474/5475 processor support.
 
@@ -260,6 +270,7 @@ config M548x
        select M54xx
        select HAVE_CACHE_CB
        select HAVE_MBAR
+       select CPU_NO_EFFICIENT_FFS
        help
          Freescale ColdFire 5480/5481/5482/5483/5484/5485 processor support.
 
index e47a08d..5b7a45d 100644 (file)
@@ -30,6 +30,7 @@ config METAG
        select OF
        select OF_EARLY_FLATTREE
        select SPARSE_IRQ
+       select CPU_NO_EFFICIENT_FFS
 
 config STACKTRACE_SUPPORT
        def_bool y
index 3d793b5..f17c3a4 100644 (file)
@@ -32,6 +32,7 @@ config MICROBLAZE
        select OF_EARLY_FLATTREE
        select TRACING_SUPPORT
        select VIRT_TO_BUS
+       select CPU_NO_EFFICIENT_FFS
 
 config SWAP
        def_bool n
index e6f19fc..e961c8a 100644 (file)
 #endif
 #endif
 
+/* __builtin_constant_p(cpu_has_mips_r) && cpu_has_mips_r */
+#if !((defined(cpu_has_mips32r1) && cpu_has_mips32r1) || \
+         (defined(cpu_has_mips32r2) && cpu_has_mips32r2) || \
+         (defined(cpu_has_mips32r6) && cpu_has_mips32r6) || \
+         (defined(cpu_has_mips64r1) && cpu_has_mips64r1) || \
+         (defined(cpu_has_mips64r2) && cpu_has_mips64r2) || \
+         (defined(cpu_has_mips64r6) && cpu_has_mips64r6))
+#define CPU_NO_EFFICIENT_FFS 1
+#endif
+
 #ifndef cpu_has_mips_1
 # define cpu_has_mips_1                (!cpu_has_mips_r6)
 #endif
index 87ca653..51a56c8 100644 (file)
@@ -15,6 +15,7 @@ config NIOS2
        select SOC_BUS
        select SPARSE_IRQ
        select USB_ARCH_HAS_HCD if USB_SUPPORT
+       select CPU_NO_EFFICIENT_FFS
 
 config GENERIC_CSUM
        def_bool y
index e118c02..142cb05 100644 (file)
@@ -25,6 +25,7 @@ config OPENRISC
        select MODULES_USE_ELF_RELA
        select HAVE_DEBUG_STACKOVERFLOW
        select OR1K_PIC
+       select CPU_NO_EFFICIENT_FFS if !OPENRISC_HAVE_INST_FF1
 
 config MMU
        def_bool y
index 88cfaa8..3d498a6 100644 (file)
@@ -32,6 +32,7 @@ config PARISC
        select HAVE_ARCH_AUDITSYSCALL
        select HAVE_ARCH_SECCOMP_FILTER
        select ARCH_NO_COHERENT_DMA_MMAP
+       select CPU_NO_EFFICIENT_FFS
 
        help
          The PA-RISC microprocessor is designed by Hewlett-Packard and used
index 1c3c43d..a8c2590 100644 (file)
@@ -123,6 +123,7 @@ config S390
        select HAVE_ARCH_AUDITSYSCALL
        select HAVE_ARCH_EARLY_PFN_TO_NID
        select HAVE_ARCH_JUMP_LABEL
+       select CPU_NO_EFFICIENT_FFS if !HAVE_MARCH_Z9_109_FEATURES
        select HAVE_ARCH_SECCOMP_FILTER
        select HAVE_ARCH_SOFT_DIRTY
        select HAVE_ARCH_TRACEHOOK
index 366e1b5..507d631 100644 (file)
@@ -14,6 +14,7 @@ config SCORE
        select VIRT_TO_BUS
        select MODULES_USE_ELF_REL
        select CLONE_BACKWARDS
+       select CPU_NO_EFFICIENT_FFS
 
 choice
        prompt "System type"
index f625434..e803a83 100644 (file)
@@ -20,6 +20,7 @@ config SUPERH
        select PERF_USE_VMALLOC
        select HAVE_DEBUG_KMEMLEAK
        select HAVE_KERNEL_GZIP
+       select CPU_NO_EFFICIENT_FFS
        select HAVE_KERNEL_BZIP2
        select HAVE_KERNEL_LZMA
        select HAVE_KERNEL_XZ
index 1012f7f..546293d 100644 (file)
@@ -42,6 +42,7 @@ config SPARC
        select ODD_RT_SIGACTION
        select OLD_SIGSUSPEND
        select ARCH_HAS_SG_CHAIN
+       select CPU_NO_EFFICIENT_FFS
 
 config SPARC32
        def_bool !64BIT
index 3657f12..135ee64 100644 (file)
--- a/lib/gcd.c
+++ b/lib/gcd.c
@@ -2,20 +2,77 @@
 #include <linux/gcd.h>
 #include <linux/export.h>
 
-/* Greatest common divisor */
+/*
+ * This implements the binary GCD algorithm. (Often attributed to Stein,
+ * but as Knuth has noted, appears in a first-century Chinese math text.)
+ *
+ * This is faster than the division-based algorithm even on x86, which
+ * has decent hardware division.
+ */
+
+#if !defined(CONFIG_CPU_NO_EFFICIENT_FFS) && !defined(CPU_NO_EFFICIENT_FFS)
+
+/* If __ffs is available, the even/odd algorithm benchmarks slower. */
 unsigned long gcd(unsigned long a, unsigned long b)
 {
-       unsigned long r;
+       unsigned long r = a | b;
+
+       if (!a || !b)
+               return r;
 
-       if (a < b)
-               swap(a, b);
+       b >>= __ffs(b);
+       if (b == 1)
+               return r & -r;
 
-       if (!b)
-               return a;
-       while ((r = a % b) != 0) {
-               a = b;
-               b = r;
+       for (;;) {
+               a >>= __ffs(a);
+               if (a == 1)
+                       return r & -r;
+               if (a == b)
+                       return a << __ffs(r);
+
+               if (a < b)
+                       swap(a, b);
+               a -= b;
        }
-       return b;
 }
+
+#else
+
+/* If normalization is done by loops, the even/odd algorithm is a win. */
+unsigned long gcd(unsigned long a, unsigned long b)
+{
+       unsigned long r = a | b;
+
+       if (!a || !b)
+               return r;
+
+       /* Isolate lsbit of r */
+       r &= -r;
+
+       while (!(b & r))
+               b >>= 1;
+       if (b == r)
+               return r;
+
+       for (;;) {
+               while (!(a & r))
+                       a >>= 1;
+               if (a == r)
+                       return r;
+               if (a == b)
+                       return a;
+
+               if (a < b)
+                       swap(a, b);
+               a -= b;
+               a >>= 1;
+               if (a & r)
+                       a += b;
+               a >>= 1;
+       }
+}
+
+#endif
+
 EXPORT_SYMBOL_GPL(gcd);