Date: Mon, 16 Jun 2008 15:47:47 +0200
From: Andi Kleen <andi@firstfloor.org>
To: Cliff Wickman <cpw@sgi.com>
Subject: Re: Various fixes to numactl 2.0.0

3/15 should have been clearcache (attached)

Clear caches between numademo runs

This will hopefully make numademo results a little more stable.

On x86 this uses CLFLUSH if available, otherwise it uses a cheesy
fallback.
---
 Makefile     |    8 +++---
 clearcache.c |   70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 clearcache.h |    1 
 numademo.c   |   12 ++++++++++
 4 files changed, 87 insertions(+), 4 deletions(-)

Index: numactl-dev/clearcache.c
===================================================================
--- /dev/null
+++ numactl-dev/clearcache.c
@@ -0,0 +1,70 @@
+/* Clear the CPU cache for benchmark purposes. Pretty simple minded.
+ * Might not work in some complex cache topologies.
+ * When you switch CPUs it's a good idea to clear the cache after testing
+ * too.
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "clearcache.h"
+
+unsigned cache_size(void)
+{
+	unsigned cs = 0;
+#ifdef _SC_LEVEL1_DCACHE_SIZE
+	cs += sysconf(_SC_LEVEL1_DCACHE_SIZE);
+#endif
+#ifdef _SC_LEVEL2_DCACHE_SIZE
+	cs += sysconf(_SC_LEVEL2_DCACHE_SIZE);
+#endif
+#ifdef _SC_LEVEL3_DCACHE_SIZE
+	cs += sysconf(_SC_LEVEL3_DCACHE_SIZE);
+#endif
+#ifdef _SC_LEVEL4_DCACHE_SIZE
+	cs += sysconf(_SC_LEVEL4_DCACHE_SIZE);
+#endif
+	if (cs == 0) {
+		static int warned;
+		if (!warned) {
+			printf("Cannot determine CPU cache size\n");
+			warned = 1;
+		}
+		cs = 64*1024*1024;
+	}
+	cs *= 2; /* safety factor */
+
+	return cs;
+}
+
+void fallback_clearcache(void)
+{
+	static unsigned char *clearmem;
+	unsigned cs = cache_size();
+	unsigned i;
+
+	if (!clearmem)
+		clearmem = malloc(cs);
+	if (!clearmem) {
+		printf("Warning: cannot allocate %u bytes of clear cache buffer\n", cs);
+		return;
+	}
+	for (i = 0; i < cs; i += 32)
+		clearmem[i] = 1;
+}
+
+void clearcache(unsigned char *mem, unsigned size)
+{
+#if defined(__i386__) || defined(__x86_64__)
+	unsigned i, cl, eax, feat;
+	/* get clflush unit and feature */
+	asm("cpuid" : "=a" (eax), "=b" (cl), "=d" (feat) : "0" (1) : "cx");
+	if (!(feat & (1 << 19)))
+		fallback_clearcache();
+	cl = ((cl >> 8) & 0xff) * 8;
+	for (i = 0; i < size; i += cl)
+		asm("clflush %0" :: "m" (mem[i]));
+#else
+#warning "Consider adding a clearcache implementation for your architecture"
+	fallback_clearcache(mem, size);
+#endif
+}
Index: numactl-dev/clearcache.h
===================================================================
--- /dev/null
+++ numactl-dev/clearcache.h
@@ -0,0 +1 @@
+void clearcache(unsigned char *mem, unsigned size);
Index: numactl-dev/numademo.c
===================================================================
--- numactl-dev.orig/numademo.c
+++ numactl-dev/numademo.c
@@ -30,6 +30,11 @@
 #ifdef HAVE_MT
 #include "mt.h"
 #endif
+#ifdef HAVE_CLEAR_CACHE
+#include "clearcache.h"
+#else
+static inline void clearcache(void *a, unsigned size) {}
+#endif
 
 unsigned long msize; 
 
@@ -82,6 +87,7 @@ void do_stream(char *name, unsigned char
 	char title[100], buf[100];
 	double res[STREAM_NRESULTS];
 	stream_verbose = 0;
+	clearcache(mem, msize);
 	stream_init(mem); 
 	stream_test(res);
 	sprintf(title, "%s%s%s", name, delim, "STREAM");
@@ -93,6 +99,7 @@ void do_stream(char *name, unsigned char
 			stream_names[i], delim, res[i], delim);
 	}
 	output(title, buf);
+	clearcache(mem, msize);
 }
 #endif
 
@@ -156,6 +163,7 @@ void memtest(char *name, unsigned char *
 	min = ~0UL; 
 	sum = 0;
 	for (i = 0; i < LOOPS; i++) { 
+		clearcache(mem, msize);
 		switch (thistest) { 
 		case PTRCHASE:
 		{
@@ -253,6 +261,10 @@ void memtest(char *name, unsigned char *
 #ifdef HAVE_STREAM_LIB
  out:
 #endif
+	/* Just to make sure that when we switch CPUs that the old guy
+	   doesn't still keep it around. */
+	clearcache(mem, msize);
+
 	numa_free(mem, msize); 
 } 
 
Index: numactl-dev/Makefile
===================================================================
--- numactl-dev.orig/Makefile
+++ numactl-dev/Makefile
@@ -17,7 +17,7 @@ endif
 
 CLEANFILES := numactl.o libnuma.o numactl numademo numademo.o distance.o \
 	      memhog libnuma.so libnuma.so.1 numamon numamon.o syscall.o bitops.o \
-	      memhog.o util.o stream_main.o stream_lib.o shm.o stream \
+	      memhog.o util.o stream_main.o stream_lib.o shm.o stream clearcache.o \
 	      test/pagesize test/tshared test/mynode.o test/tshared.o mt.o \
 	      test/mynode test/ftok test/prefered test/randmap \
 	      .depend .depend.X test/nodemap test/distance test/tbitmap \
@@ -26,7 +26,7 @@ CLEANFILES := numactl.o libnuma.o numact
 	      migratepages migspeed migspeed.o libnuma.a
 SOURCES := bitops.c libnuma.c distance.c memhog.c numactl.c numademo.c \
 	numamon.c shm.c stream_lib.c stream_main.c syscall.c util.c mt.c \
-	test/*.c
+	clearcache.c test/*.c
 
 prefix := /usr
 libdir := ${prefix}/$(shell ./getlibdir)
@@ -52,11 +52,11 @@ numactl.o: numactl.c
 
 numademo: override LDFLAGS += -lm
 # GNU make 3.80 appends BENCH_CFLAGS twice. Bug? It's harmless though.
-numademo: CFLAGS += -DHAVE_STREAM_LIB -DHAVE_MT ${BENCH_CFLAGS} 
+numademo: CFLAGS += -DHAVE_STREAM_LIB -DHAVE_MT -DHAVE_CLEAR_CACHE ${BENCH_CFLAGS}
 stream_lib.o: CFLAGS += ${BENCH_CFLAGS}
 mt.o: CFLAGS += ${BENCH_CFLAGS} 
 mt.o: mt.c
-numademo: numademo.o stream_lib.o mt.o libnuma.so
+numademo: numademo.o stream_lib.o mt.o libnuma.so clearcache.o
 
 numademo.o: numademo.c libnuma.so	
 
