Add bench and test for SkRefCnt.
authorbungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 14 May 2012 14:09:24 +0000 (14:09 +0000)
committerbungeman@google.com <bungeman@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Mon, 14 May 2012 14:09:24 +0000 (14:09 +0000)
http://codereview.appspot.com/6195071/

This also adds a cross platform SkThread for testing purposes.

git-svn-id: http://skia.googlecode.com/svn/trunk@3921 2bbb7eff-a529-9590-31e7-b0007b416f81

14 files changed:
bench/RefCntBench.cpp [new file with mode: 0644]
gyp/bench.gypi
gyp/tests.gyp
gyp/utils.gyp
src/ports/SkThread_none.cpp
src/utils/SkThreadUtils.h [new file with mode: 0644]
src/utils/SkThreadUtils_pthread.cpp [new file with mode: 0644]
src/utils/SkThreadUtils_pthread.h [new file with mode: 0644]
src/utils/SkThreadUtils_pthread_linux.cpp [new file with mode: 0644]
src/utils/SkThreadUtils_pthread_mach.cpp [new file with mode: 0644]
src/utils/SkThreadUtils_pthread_other.cpp [new file with mode: 0644]
src/utils/SkThreadUtils_win.cpp [new file with mode: 0644]
src/utils/SkThreadUtils_win.h [new file with mode: 0644]
tests/RefCntTest.cpp [new file with mode: 0644]

diff --git a/bench/RefCntBench.cpp b/bench/RefCntBench.cpp
new file mode 100644 (file)
index 0000000..44fb648
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkBenchmark.h"
+#include "SkThread.h"
+#include <memory>
+
+enum {
+    N = SkBENCHLOOP(1000000),
+    M = SkBENCHLOOP(2)
+};
+
+class RefCntBench_Stack : public SkBenchmark {
+public:
+    RefCntBench_Stack(void* param) : INHERITED(param) {
+    }
+protected:
+    virtual const char* onGetName() {
+        return "ref_cnt_stack";
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        for (int i = 0; i < N; ++i) {
+            SkRefCnt ref;
+            for (int j = 0; j < M; ++j) {
+                ref.ref();
+                ref.unref();
+            }
+        }
+    }
+
+private:
+    typedef SkBenchmark INHERITED;
+};
+
+class PlacedRefCnt : public SkRefCnt {
+public:
+    PlacedRefCnt() : SkRefCnt() { }
+    void operator delete(void *p) { }
+};
+
+class RefCntBench_Heap : public SkBenchmark {
+public:
+    RefCntBench_Heap(void* param) : INHERITED(param) {
+    }
+protected:
+    virtual const char* onGetName() {
+        return "ref_cnt_heap";
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        char memory[sizeof(PlacedRefCnt)];
+        for (int i = 0; i < N; ++i) {
+            PlacedRefCnt* ref = new (memory) PlacedRefCnt();
+            for (int j = 0; j < M; ++j) {
+                ref->ref();
+                ref->unref();
+            }
+            ref->unref();
+        }
+    }
+
+private:
+    typedef SkBenchmark INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static SkBenchmark* Fact0(void* p) { return new RefCntBench_Stack(p); }
+static SkBenchmark* Fact1(void* p) { return new RefCntBench_Heap(p); }
+
+static BenchRegistry gReg01(Fact0);
+static BenchRegistry gReg02(Fact1);
+
index 8499ca1..221faed 100644 (file)
@@ -33,6 +33,7 @@
     '../bench/PathIterBench.cpp',
     '../bench/PicturePlaybackBench.cpp',
     '../bench/RectBench.cpp',
+    '../bench/RefCntBench.cpp',
     '../bench/RegionBench.cpp',
     '../bench/RepeatTileBench.cpp',
     '../bench/ScalarBench.cpp',
index e7d5724..ec850a4 100644 (file)
@@ -61,6 +61,7 @@
         '../tests/Reader32Test.cpp',
         '../tests/ReadPixelsTest.cpp',
         '../tests/ReadWriteAlphaTest.cpp',
+        '../tests/RefCntTest.cpp',
         '../tests/RefDictTest.cpp',
         '../tests/RegionTest.cpp',
         '../tests/ScalarTest.cpp',
index 2fca63f..d2c413e 100644 (file)
         '../src/utils/SkParsePath.cpp',
         '../src/utils/SkProxyCanvas.cpp',
         '../src/utils/SkSfntUtils.cpp',
+        '../src/utils/SkThreadUtils.h',
+        '../src/utils/SkThreadUtils_pthread.cpp',
+        '../src/utils/SkThreadUtils_pthread.h',
+        '../src/utils/SkThreadUtils_pthread_linux.cpp',
+        '../src/utils/SkThreadUtils_pthread_mach.cpp',
+        '../src/utils/SkThreadUtils_pthread_other.cpp',
+        '../src/utils/SkThreadUtils_win.cpp',
+        '../src/utils/SkThreadUtils_win.h',
         '../src/utils/SkUnitMappers.cpp',
 
         #mac
@@ -87,6 +95,9 @@
               '../include/utils/mac',
             ],
           },
+          'sources!': [
+            '../src/utils/SkThreadUtils_pthread_other.cpp',
+          ],
         },{ #else if 'skia_os != "mac"'
           'include_dirs!': [
             '../include/utils/mac',
           'sources!': [
             '../include/utils/mac/SkCGUtils.h',
             '../src/utils/mac/SkCreateCGImageRef.cpp',
+            '../src/utils/SkThreadUtils_pthread_mach.cpp',
           ],
         }],
         [ 'skia_os in ["linux", "freebsd", "openbsd", "solaris"]', {
               '-lGLU',
             ],
           },
+          'sources!': [
+            '../src/utils/SkThreadUtils_pthread_other.cpp',
+          ],
         },{ #else if 'skia_os not in ["linux", "freebsd", "openbsd", "solaris"]'
           'include_dirs!': [
             '../include/utils/unix',
           ],
+          'sources!': [
+            '../src/utils/SkThreadUtils_pthread_linux.cpp',
+          ],
         }],
         [ 'skia_os == "win"', {
           'direct_dependent_settings': {
               '../include/utils/win',
             ],
           },
+          'sources!': [
+            '../src/utils/SkThreadUtils_pthread.cpp',
+            '../src/utils/SkThreadUtils_pthread.h',
+            '../src/utils/SkThreadUtils_pthread_other.cpp',
+          ],
         },{ #else if 'skia_os != "win"'
           'include_dirs!': [
             '../include/utils/win',
index 9e170cf..a948a54 100644 (file)
@@ -8,6 +8,7 @@
 
 
 #include "SkThread.h"
+#include "SkTLS.h"
 
 int32_t sk_atomic_inc(int32_t* addr) {
     int32_t value = *addr;
@@ -25,9 +26,10 @@ SkMutex::SkMutex() {}
 
 SkMutex::~SkMutex() {}
 
+#ifndef SK_USE_POSIX_THREADS
 void SkMutex::acquire() {}
-
 void SkMutex::release() {}
+#endif
 
 //////////////////////////////////////////////////////////////////////////
 
diff --git a/src/utils/SkThreadUtils.h b/src/utils/SkThreadUtils.h
new file mode 100644 (file)
index 0000000..b0c5044
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkThreadUtils_DEFINED
+#define SkThreadUtils_DEFINED
+
+#include "SkTypes.h"
+
+class SkThread : SkNoncopyable {
+public:
+    typedef void (*entryPointProc)(void*);
+    
+    SkThread(entryPointProc entryPoint, void* data = NULL);
+    
+    /**
+     * Non-virtual, do not subclass.
+     */
+    ~SkThread();
+    
+    /**
+     * Starts the thread. Returns false if the thread could not be started.
+     */
+    bool start();
+    
+    /**
+     * Waits for the thread to finish.
+     * If the thread has not started, returns immediately.
+     */
+    void join();
+    
+    /**
+     * SkThreads with an affinity for the same processor will attempt to run cache
+     * locally with each other. SkThreads with an affinity for different processors
+     * will attempt to run on different cores. Returns false if the request failed.
+     */
+    bool setProcessorAffinity(unsigned int processor);
+    
+private:
+    void* fData;
+};
+
+#endif
diff --git a/src/utils/SkThreadUtils_pthread.cpp b/src/utils/SkThreadUtils_pthread.cpp
new file mode 100644 (file)
index 0000000..17a2075
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_pthread.h"
+
+#include <pthread.h>
+#include <signal.h>
+
+SkThread_PThreadData::SkThread_PThreadData(SkThread::entryPointProc entryPoint, void* data)
+    : fPThread()
+    , fValidPThread(false)
+    , fParam(data)
+    , fEntryPoint(entryPoint)
+    , fStarted(false)
+{
+    pthread_mutex_init(&fStartMutex, NULL);
+
+    pthread_cond_init(&fStartCondition, NULL);
+
+    pthread_attr_init(&fAttr);
+    pthread_attr_setdetachstate(&fAttr, PTHREAD_CREATE_JOINABLE);
+}
+SkThread_PThreadData::~SkThread_PThreadData() {
+    pthread_attr_destroy(&fAttr);
+    pthread_cond_destroy(&fStartCondition);
+    pthread_mutex_destroy(&fStartMutex);
+}
+
+static void* thread_start(void* arg) {
+    SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(arg);
+    //Wait for start signal
+    pthread_mutex_lock(&(pthreadData->fStartMutex));
+    while (!pthreadData->fStarted) {
+        pthread_cond_wait(&(pthreadData->fStartCondition), &(pthreadData->fStartMutex));
+    }
+    pthread_mutex_unlock(&(pthreadData->fStartMutex));
+
+    //See if this thread was canceled before starting.
+    pthread_testcancel();
+
+    pthreadData->fEntryPoint(pthreadData->fParam);
+    return NULL;
+}
+
+SkThread::SkThread(entryPointProc entryPoint, void* data) {
+    SkThread_PThreadData* pthreadData = new SkThread_PThreadData(entryPoint, data);
+    fData = pthreadData;
+
+    int ret = pthread_create(&(pthreadData->fPThread),
+                             &(pthreadData->fAttr),
+                             thread_start,
+                             pthreadData);
+
+    pthreadData->fValidPThread = (0 == ret);
+}
+
+SkThread::~SkThread() {
+    if (fData != NULL) {
+        SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+        // If created thread but start was never called, kill the thread.
+        if (pthreadData->fValidPThread && !pthreadData->fStarted) {
+            if (pthread_cancel(pthreadData->fPThread) == 0) {
+                if (this->start()) {
+                    this->join();
+                }
+            } else {
+                //kill with prejudice
+                pthread_kill(pthreadData->fPThread, SIGKILL);
+            }
+        }
+        delete pthreadData;
+    }
+}
+
+bool SkThread::start() {
+    SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+    if (!pthreadData->fValidPThread) {
+        return false;
+    }
+
+    if (pthreadData->fStarted) {
+        return false;
+    }
+    pthreadData->fStarted = true;
+    pthread_mutex_lock(&(pthreadData->fStartMutex));
+    pthread_cond_signal(&(pthreadData->fStartCondition));
+    pthread_mutex_unlock(&(pthreadData->fStartMutex));
+    return true;
+}
+
+void SkThread::join() {
+    SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+    if (!pthreadData->fValidPThread || !pthreadData->fStarted) {
+        return;
+    }
+
+    pthread_join(pthreadData->fPThread, NULL);
+}
diff --git a/src/utils/SkThreadUtils_pthread.h b/src/utils/SkThreadUtils_pthread.h
new file mode 100644 (file)
index 0000000..52b398c
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkThreadUtils_PThreadData_DEFINED
+#define SkThreadUtils_PThreadData_DEFINED
+
+#include "SkThreadUtils.h"
+#include <pthread.h>
+
+class SkThread_PThreadData {
+public:
+    SkThread_PThreadData(SkThread::entryPointProc entryPoint, void* data);
+    ~SkThread_PThreadData();
+    pthread_t fPThread;
+    bool fValidPThread;
+    pthread_mutex_t fStartMutex;
+    pthread_cond_t fStartCondition;
+    pthread_attr_t fAttr;
+
+    void* fParam;
+    SkThread::entryPointProc fEntryPoint;
+    bool fStarted;
+};
+
+#endif
diff --git a/src/utils/SkThreadUtils_pthread_linux.cpp b/src/utils/SkThreadUtils_pthread_linux.cpp
new file mode 100644 (file)
index 0000000..4a03cb8
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE //for pthread_setaffinity_np
+#endif
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_pthread.h"
+
+#include <pthread.h>
+
+static int nth_set_cpu(unsigned int n, cpu_set_t* cpuSet) {
+    n %= CPU_COUNT(cpuSet);
+    for (unsigned int setCpusSeen = 0, currentCpu = 0; true; ++currentCpu) {
+        if (CPU_ISSET(currentCpu, cpuSet)) {
+            ++setCpusSeen;
+            if (setCpusSeen > n) {
+                return currentCpu;
+            }
+        }
+    }
+}
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+    SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+    if (!pthreadData->fValidPThread) {
+        return false;
+    }
+
+    cpu_set_t parentCpuset;
+    if (0 != pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &parentCpuset)) {
+        return false;
+    }
+
+    cpu_set_t cpuset;
+    CPU_ZERO(&cpuset);
+    CPU_SET(nth_set_cpu(processor, &parentCpuset), &cpuset);
+    return 0 == pthread_setaffinity_np(pthreadData->fPThread,
+                                       sizeof(cpu_set_t),
+                                       &cpuset);
+}
diff --git a/src/utils/SkThreadUtils_pthread_mach.cpp b/src/utils/SkThreadUtils_pthread_mach.cpp
new file mode 100644 (file)
index 0000000..0f6e263
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_pthread.h"
+
+#include <mach/mach.h>
+#include <mach/thread_policy.h>
+#include <pthread.h>
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+    SkThread_PThreadData* pthreadData = static_cast<SkThread_PThreadData*>(fData);
+    if (!pthreadData->fValidPThread) {
+        return false;
+    }
+
+    mach_port_t tid = pthread_mach_thread_np(pthreadData->fPThread);
+
+    thread_affinity_policy_data_t policy;
+    policy.affinity_tag = processor;
+
+    return 0 == thread_policy_set(tid,
+                                  THREAD_AFFINITY_POLICY,
+                                  (thread_policy_t) &policy,
+                                  THREAD_AFFINITY_POLICY_COUNT);
+}
diff --git a/src/utils/SkThreadUtils_pthread_other.cpp b/src/utils/SkThreadUtils_pthread_other.cpp
new file mode 100644 (file)
index 0000000..a3973f1
--- /dev/null
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkThreadUtils.h"
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+    return false;
+}
diff --git a/src/utils/SkThreadUtils_win.cpp b/src/utils/SkThreadUtils_win.cpp
new file mode 100644 (file)
index 0000000..208ffde
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+
+#include "SkThreadUtils.h"
+#include "SkThreadUtils_win.h"
+
+SkThread_WinData::SkThread_WinData(SkThread::entryPointProc entryPoint, void* data)
+    : fHandle(NULL)
+    , fParam(data)
+    , fThreadId(0)
+    , fEntryPoint(entryPoint)
+    , fStarted(false)
+{
+    fCancelEvent = CreateEvent(
+        NULL,  // default security attributes
+        false, //auto reset
+        false, //not signaled
+        NULL); //no name
+}
+
+SkThread_WinData::~SkThread_WinData() {
+    CloseHandle(fCancelEvent);
+}
+
+static DWORD WINAPI thread_start(LPVOID data) {
+    SkThread_WinData* winData = static_cast<SkThread_WinData*>(data);
+
+    //See if this thread was canceled before starting.
+    if (WaitForSingleObject(winData->fCancelEvent, 0) == WAIT_OBJECT_0) {
+        return 0;
+    }
+
+    winData->fEntryPoint(winData->fParam);
+    return 0;
+}
+
+SkThread::SkThread(entryPointProc entryPoint, void* data) {
+    SkThread_WinData* winData = new SkThread_WinData(entryPoint, data);
+    fData = winData;
+
+    if (NULL == winData->fCancelEvent) {
+        return;
+    }
+
+    winData->fHandle = CreateThread(
+        NULL,                   // default security attributes
+        0,                      // use default stack size
+        thread_start,           // thread function name (proxy)
+        winData,                // argument to thread function (proxy args)
+        CREATE_SUSPENDED,       // create suspended so affinity can be set
+        &winData->fThreadId);   // returns the thread identifier
+}
+
+SkThread::~SkThread() {
+    if (fData != NULL) {
+        SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+        // If created thread but start was never called, kill the thread.
+        if (winData->fHandle != NULL && !winData->fStarted) {
+            if (SetEvent(winData->fCancelEvent) != 0) {
+                if (this->start()) {
+                    this->join();
+                }
+            } else {
+                //kill with prejudice
+                TerminateThread(winData->fHandle, -1);
+            }
+        }
+        delete winData;
+    }
+}
+
+bool SkThread::start() {
+    SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+    if (NULL == winData->fHandle) {
+        return false;
+    }
+
+    if (winData->fStarted) {
+        return false;
+    }
+    winData->fStarted = -1 != ResumeThread(winData->fHandle);
+    return winData->fStarted;
+}
+
+void SkThread::join() {
+    SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+    if (NULL == winData->fHandle || !winData->fStarted) {
+        return;
+    }
+
+    WaitForSingleObject(winData->fHandle, INFINITE);
+}
+
+static unsigned int num_bits_set(DWORD_PTR mask) {
+    unsigned int count;
+    for (count = 0; mask; ++count) {
+        mask &= mask - 1;
+    }
+    return count;
+}
+
+static unsigned int nth_set_bit(unsigned int n, DWORD_PTR mask) {
+    n %= num_bits_set(mask);
+    for (unsigned int setBitsSeen = 0, currentBit = 0; true; ++currentBit) {
+        if (mask & (1 << currentBit)) {
+            ++setBitsSeen;
+            if (setBitsSeen > n) {
+                return currentBit;
+            }
+        }
+    }
+}
+
+bool SkThread::setProcessorAffinity(unsigned int processor) {
+    SkThread_WinData* winData = static_cast<SkThread_WinData*>(fData);
+    if (NULL == winData->fHandle) {
+        return false;
+    }
+
+    DWORD_PTR processAffinityMask;
+    DWORD_PTR systemAffinityMask;
+    if (0 == GetProcessAffinityMask(GetCurrentProcess(),
+                                    &processAffinityMask,
+                                    &systemAffinityMask)) {
+        return false;
+    }
+
+    DWORD_PTR threadAffinityMask = 1 << nth_set_bit(processor, processAffinityMask);
+    return 0 != SetThreadAffinityMask(winData->fHandle, threadAffinityMask);
+}
diff --git a/src/utils/SkThreadUtils_win.h b/src/utils/SkThreadUtils_win.h
new file mode 100644 (file)
index 0000000..5861e5d
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkThreadUtils_WinData_DEFINED
+#define SkThreadUtils_WinData_DEFINED
+
+#include "SkTypes.h"
+
+#include "SkThreadUtils.h"
+
+class SkThread_WinData {
+public:
+    SkThread_WinData(SkThread::entryPointProc entryPoint, void* data);
+    ~SkThread_WinData();
+    HANDLE fHandle;
+    HANDLE fCancelEvent;
+
+    LPVOID fParam;
+    DWORD fThreadId;
+    SkThread::entryPointProc fEntryPoint;
+    bool fStarted;
+};
+
+#endif
diff --git a/tests/RefCntTest.cpp b/tests/RefCntTest.cpp
new file mode 100644 (file)
index 0000000..e48fd8a
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+#include "Test.h"
+
+#include "SkRefCnt.h"
+#include "SkThreadUtils.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void bounce_ref(void* data) {
+    SkRefCnt* ref = static_cast<SkRefCnt*>(data);
+    for (int i = 0; i < 100000; ++i) {
+        ref->ref();
+        ref->unref();
+    }
+}
+
+static void test_refCnt(skiatest::Reporter* reporter) {
+    SkRefCnt* ref = new SkRefCnt();
+
+    SkThread thing1(bounce_ref, ref);
+    SkThread thing2(bounce_ref, ref);
+
+    thing1.setProcessorAffinity(0);
+    thing2.setProcessorAffinity(23);
+
+    SkASSERT(thing1.start());
+    SkASSERT(thing2.start());
+
+    thing1.join();
+    thing2.join();
+
+    REPORTER_ASSERT(reporter, ref->getRefCnt() == 1);
+    ref->unref();
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("ref_cnt", RefCntTestClass, test_refCnt)