From 24954c793bca436d612851de3a460c72ef1f7b6d Mon Sep 17 00:00:00 2001 From: Apple Date: Mon, 5 Oct 2009 20:50:20 +0000 Subject: [PATCH] libdispatch-84.5.3.tar.gz --- EXPORT.APPLE | 21 ++ install | 81 +++++ install_Libsystem_pieces | 48 +++ src/legacy.h | 2 +- src/queue.c | 60 +++- src/queue_internal.h | 6 +- testing/Makefile | 127 +++++++ testing/apply_strtoull.c | 64 ++++ testing/bench.mm | 488 +++++++++++++++++++++++++++ testing/conc.c | 33 ++ testing/dispatch_after.c | 59 ++++ testing/dispatch_api.c | 20 ++ testing/dispatch_apply.c | 29 ++ testing/dispatch_c99.c | 21 ++ testing/dispatch_cascade.c | 94 ++++++ testing/dispatch_cffd.c | 139 ++++++++ testing/dispatch_deadname.c | 44 +++ testing/dispatch_debug.c | 30 ++ testing/dispatch_drift.c | 61 ++++ testing/dispatch_group.c | 89 +++++ testing/dispatch_overcommit.c | 45 +++ testing/dispatch_pingpong.c | 37 ++ testing/dispatch_plusplus.cpp | 17 + testing/dispatch_priority.c | 126 +++++++ testing/dispatch_proc.c | 89 +++++ testing/dispatch_rdlock_bmark.c | 140 ++++++++ testing/dispatch_read.c | 84 +++++ testing/dispatch_read2.c | 69 ++++ testing/dispatch_readsync.c | 59 ++++ testing/dispatch_sema.c | 33 ++ testing/dispatch_starfish.c | 141 ++++++++ testing/dispatch_suspend_timer.c | 76 +++++ testing/dispatch_test.c | 148 ++++++++ testing/dispatch_test.h | 33 ++ testing/dispatch_test_sync_on_main.c | 46 +++ testing/dispatch_timer.c | 65 ++++ testing/dispatch_timer_bit31.c | 56 +++ testing/dispatch_timer_bit63.c | 42 +++ testing/dispatch_timer_oneshot.c | 43 +++ testing/dispatch_timer_set_time.c | 60 ++++ testing/fast_apply_bench.c | 98 ++++++ testing/fd_stress.c | 457 +++++++++++++++++++++++++ testing/float_parsing.c | 136 ++++++++ testing/fork-join.c | 40 +++ testing/func.c | 9 + testing/harness.c | 83 +++++ testing/leaks-wrapper | 10 + testing/mach_server.c | 131 +++++++ testing/mig/Makefile | 17 + testing/mig/client.c | 34 ++ testing/mig/hello_logger.defs | 12 + testing/mig/hello_logger_types.h | 8 + testing/mig/server.c | 48 +++ testing/nsoperation.m | 52 +++ testing/queue_finalizer.c | 46 +++ testing/slice_benchmarks.c | 445 ++++++++++++++++++++++++ testing/summarize.c | 76 +++++ testing/test.c | 45 +++ testing/yet-another-apply-test.c | 101 ++++++ 59 files changed, 4756 insertions(+), 17 deletions(-) create mode 100755 EXPORT.APPLE create mode 100755 install create mode 100755 install_Libsystem_pieces create mode 100644 testing/Makefile create mode 100644 testing/apply_strtoull.c create mode 100644 testing/bench.mm create mode 100644 testing/conc.c create mode 100644 testing/dispatch_after.c create mode 100644 testing/dispatch_api.c create mode 100644 testing/dispatch_apply.c create mode 100644 testing/dispatch_c99.c create mode 100644 testing/dispatch_cascade.c create mode 100644 testing/dispatch_cffd.c create mode 100644 testing/dispatch_deadname.c create mode 100644 testing/dispatch_debug.c create mode 100644 testing/dispatch_drift.c create mode 100644 testing/dispatch_group.c create mode 100644 testing/dispatch_overcommit.c create mode 100644 testing/dispatch_pingpong.c create mode 100644 testing/dispatch_plusplus.cpp create mode 100644 testing/dispatch_priority.c create mode 100644 testing/dispatch_proc.c create mode 100644 testing/dispatch_rdlock_bmark.c create mode 100644 testing/dispatch_read.c create mode 100644 testing/dispatch_read2.c create mode 100644 testing/dispatch_readsync.c create mode 100644 testing/dispatch_sema.c create mode 100644 testing/dispatch_starfish.c create mode 100644 testing/dispatch_suspend_timer.c create mode 100644 testing/dispatch_test.c create mode 100644 testing/dispatch_test.h create mode 100644 testing/dispatch_test_sync_on_main.c create mode 100644 testing/dispatch_timer.c create mode 100644 testing/dispatch_timer_bit31.c create mode 100644 testing/dispatch_timer_bit63.c create mode 100644 testing/dispatch_timer_oneshot.c create mode 100644 testing/dispatch_timer_set_time.c create mode 100644 testing/fast_apply_bench.c create mode 100644 testing/fd_stress.c create mode 100644 testing/float_parsing.c create mode 100644 testing/fork-join.c create mode 100644 testing/func.c create mode 100644 testing/harness.c create mode 100755 testing/leaks-wrapper create mode 100644 testing/mach_server.c create mode 100644 testing/mig/Makefile create mode 100644 testing/mig/client.c create mode 100644 testing/mig/hello_logger.defs create mode 100644 testing/mig/hello_logger_types.h create mode 100644 testing/mig/server.c create mode 100644 testing/nsoperation.m create mode 100644 testing/queue_finalizer.c create mode 100644 testing/slice_benchmarks.c create mode 100644 testing/summarize.c create mode 100644 testing/test.c create mode 100644 testing/yet-another-apply-test.c diff --git a/EXPORT.APPLE b/EXPORT.APPLE new file mode 100755 index 0000000..2af942a --- /dev/null +++ b/EXPORT.APPLE @@ -0,0 +1,21 @@ +#!/bin/sh -x + +# copy cwd to SRCROOT like installsrc + +if [ -z "$SRCROOT" ]; then + PWD=`pwd` + SRCROOT=/tmp/`basename "$PWD"` +fi + +echo Exporting to $SRCROOT +mkdir -p "$SRCROOT" + +gnutar cf - \ + --exclude=.svn \ + --exclude=CVS \ + --exclude=build \ + --exclude=testing \ + --exclude=\*.APPLE \ + --exclude=install\* \ + . | (cd "$SRCROOT"; gnutar xf - ) + diff --git a/install b/install new file mode 100755 index 0000000..221a93e --- /dev/null +++ b/install @@ -0,0 +1,81 @@ +#!/bin/bash + +if [ $EUID -ne 0 ]; then + echo "install script must be run as root" 2>&1 + exit 1 +fi + +## Copy Libsystem pieces from ~rc +copylibs=0 +## Install results in / +noinstall=0 +build=$(sw_vers -buildVersion) +train=$(~rc/bin/getTrainForBuild --quiet "$build") + +while [ $# -gt 0 ]; do + if [ "${1/=*/}" = "--build" ]; then + build="${1/*=/}" + elif [ "$1" = "--noinstall" ]; then + noinstall=1 + elif [ "$1" = "--copylibs" ]; then + copylibs=1 + else + echo "install: [--build=10A400] [--noinstall] [--copylibs]" 2>&1 + exit 1 + fi + shift +done + +ROOTS=/var/tmp/GCDRoots."$build" + +# Building for another version implies copylibs and noinstall +if [ "$build" != "$(sw_vers -buildVersion)" ]; then + copylibs=1 + noinstall=1 +fi + +set -ex +mkdir -p "$ROOTS" + +function BUILDIT() { + ~rc/bin/buildit -arch i386 -arch ppc -arch x86_64 -arch armv6 \ + -release "$train" -rootsDirectory "$ROOTS" "$@" . +} + +BUILDIT -project libdispatch -merge / -noverify + +if [ $copylibs = 1 ]; then + ALTUSRLOCALLIBSYSTEM="$ROOTS"/system + mkdir -p "$ALTUSRLOCALLIBSYSTEM" + export ALTUSRLOCALLIBSYSTEM + ./install_Libsystem_pieces "$build" + cp /usr/local/lib/system/libdispatch* "$ALTUSRLOCALLIBSYSTEM" +fi + +LIBSYSTEM=$(~rc/bin/getvers "$train$build" Libsystem) +if [ -z "$LIBSYSTEM" ]; then + exit 1 +fi +SRCROOT="/var/tmp/$LIBSYSTEM" +if [ ! -e "$SRCROOT" ]; then + cd $(dirname "$SRCROOT") + svn co http://src.apple.com/svn/BSD/Libsystem/tags/"$LIBSYSTEM" +fi +cd "$SRCROOT" +BUILDIT + +if [ $noinstall -eq 1 ]; then + exit 0 +fi + +if [ ! -e /usr/lib/libSystem.B.dylib.orig ]; then + cp /usr/lib/libSystem.B.dylib /usr/lib/libSystem.B.dylib.orig +fi +if [ ! -e /usr/lib/libSystem.B_debug.dylib.orig ] ; then + cp /usr/lib/libSystem.B_debug.dylib /usr/lib/libSystem.B_debug.dylib.orig +fi +if [ ! -e /usr/lib/libSystem.B_profile.dylib.orig ] ; then + cp /usr/lib/libSystem.B_profile.dylib /usr/lib/libSystem.B_profile.dylib.orig +fi +cp -R "$ROOTS"/"$LIBSYSTEM".roots/"$LIBSYSTEM"~sym/libSystem* /usr/lib/ +update_dyld_shared_cache diff --git a/install_Libsystem_pieces b/install_Libsystem_pieces new file mode 100755 index 0000000..39015d9 --- /dev/null +++ b/install_Libsystem_pieces @@ -0,0 +1,48 @@ +#!/bin/sh +dependencies="Libc \ + Libc_debug \ + Libc_profile \ + CommonCrypto \ + copyfile \ + mDNSResponderSystemLibraries \ + libdyld \ + Libinfo \ + keymgr \ + launchd_libs \ + Libm \ + cctools_ofiles \ + configd_libSystem \ + Libnotify \ + quarantine \ + removefile \ + Sandbox_libs \ + Seatbelt_libs \ + Libsyscall \ + libclosure \ + libcache \ + libdispatch \ + libunwind \ + Libcompiler_rt \ + UserNotification" + +function rcpath() { + dir="$1" + build="$2" + project="$3" + train=$(~rc/bin/getTrainForBuild --quiet "$build") + update="$train$build" + echo ~rc/Software/$train/Updates/$update/$dir/$project +} + +if [ $# -eq 1 ]; then build="$1" ; else build=$(sw_vers -buildVersion) ; fi + +DSTROOT=/usr/local/lib/system +if [ -n "$ALTUSRLOCALLIBSYSTEM" ]; then +DSTROOT="$ALTUSRLOCALLIBSYSTEM" +fi + +echo Copying in Libsystem dependencies for $build ... +for project in $dependencies ; do + echo ... $project + ditto $(rcpath Roots "$build" "$project")/usr/local/lib/system "$DSTROOT" +done diff --git a/src/legacy.h b/src/legacy.h index e6bffbc..fb154f8 100644 --- a/src/legacy.h +++ b/src/legacy.h @@ -28,7 +28,7 @@ * LEGACY: This header file describles LEGACY interfaces to libdispatch from an * earlier revision of the API. These interfaces WILL be removed in the future. */ - + #ifndef __DISPATCH_LEGACY__ #define __DISPATCH_LEGACY__ diff --git a/src/queue.c b/src/queue.c index a3e8936..9111a0f 100644 --- a/src/queue.c +++ b/src/queue.c @@ -594,6 +594,22 @@ _dispatch_queue_dispose(dispatch_queue_t dq) _dispatch_dispose(dq); } +DISPATCH_NOINLINE +void +_dispatch_queue_push_list_slow(dispatch_queue_t dq, struct dispatch_object_s *obj) +{ + // The queue must be retained before dq_items_head is written in order + // to ensure that the reference is still valid when _dispatch_wakeup is + // called. Otherwise, if preempted between the assignment to + // dq_items_head and _dispatch_wakeup, the blocks submitted to the + // queue may release the last reference to the queue when invoked by + // _dispatch_queue_drain. + _dispatch_retain(dq); + dq->dq_items_head = obj; + _dispatch_wakeup(dq); + _dispatch_release(dq); +} + DISPATCH_NOINLINE static void _dispatch_barrier_async_f_slow(dispatch_queue_t dq, void *context, dispatch_function_t func) @@ -674,8 +690,9 @@ dispatch_async_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) } struct dispatch_barrier_sync_slow2_s { + dispatch_queue_t dbss2_dq; dispatch_function_t dbss2_func; - dispatch_function_t dbss2_ctxt; + dispatch_function_t dbss2_ctxt; dispatch_semaphore_t dbss2_sema; }; @@ -684,7 +701,13 @@ _dispatch_barrier_sync_f_slow_invoke(void *ctxt) { struct dispatch_barrier_sync_slow2_s *dbss2 = ctxt; - dbss2->dbss2_func(dbss2->dbss2_ctxt); + dispatch_assert(dbss2->dbss2_dq == dispatch_get_current_queue()); + // ALL blocks on the main queue, must be run on the main thread + if (dbss2->dbss2_dq == dispatch_get_main_queue()) { + dbss2->dbss2_func(dbss2->dbss2_ctxt); + } else { + dispatch_suspend(dbss2->dbss2_dq); + } dispatch_semaphore_signal(dbss2->dbss2_sema); } @@ -692,9 +715,15 @@ DISPATCH_NOINLINE static void _dispatch_barrier_sync_f_slow(dispatch_queue_t dq, void *ctxt, dispatch_function_t func) { + + // It's preferred to execute synchronous blocks on the current thread + // due to thread-local side effects, garbage collection, etc. However, + // blocks submitted to the main thread MUST be run on the main thread + struct dispatch_barrier_sync_slow2_s dbss2 = { + .dbss2_dq = dq, .dbss2_func = func, - .dbss2_ctxt = ctxt, + .dbss2_ctxt = ctxt, .dbss2_sema = _dispatch_get_thread_semaphore(), }; struct dispatch_barrier_sync_slow_s { @@ -704,17 +733,17 @@ _dispatch_barrier_sync_f_slow(dispatch_queue_t dq, void *ctxt, dispatch_function .dc_func = _dispatch_barrier_sync_f_slow_invoke, .dc_ctxt = &dbss2, }; - + + dispatch_queue_t old_dq = _dispatch_thread_getspecific(dispatch_queue_key); _dispatch_queue_push(dq, (void *)&dbss); + dispatch_semaphore_wait(dbss2.dbss2_sema, DISPATCH_TIME_FOREVER); - while (dispatch_semaphore_wait(dbss2.dbss2_sema, dispatch_time(0, 3ull * NSEC_PER_SEC))) { - if (DISPATCH_OBJECT_SUSPENDED(dq)) { - continue; - } - if (_dispatch_queue_trylock(dq)) { - _dispatch_queue_drain(dq); - _dispatch_queue_unlock(dq); - } + if (dq != dispatch_get_main_queue()) { + _dispatch_thread_setspecific(dispatch_queue_key, dq); + func(ctxt); + _dispatch_workitem_inc(); + _dispatch_thread_setspecific(dispatch_queue_key, old_dq); + dispatch_resume(dq); } _dispatch_put_thread_semaphore(dbss2.dbss2_sema); } @@ -723,6 +752,13 @@ _dispatch_barrier_sync_f_slow(dispatch_queue_t dq, void *ctxt, dispatch_function void dispatch_barrier_sync(dispatch_queue_t dq, void (^work)(void)) { + // Blocks submitted to the main queue MUST be run on the main thread, + // therefore we must Block_copy in order to notify the thread-local + // garbage collector that the objects are transferring to the main thread + if (dq == dispatch_get_main_queue()) { + dispatch_block_t block = Block_copy(work); + return dispatch_barrier_sync_f(dq, block, _dispatch_call_block_and_release); + } struct Block_basic *bb = (void *)work; dispatch_barrier_sync_f(dq, work, (dispatch_function_t)bb->Block_invoke); diff --git a/src/queue_internal.h b/src/queue_internal.h index b70c2d0..51b4fca 100644 --- a/src/queue_internal.h +++ b/src/queue_internal.h @@ -82,6 +82,7 @@ extern struct dispatch_queue_s _dispatch_mgr_q; void _dispatch_queue_init(dispatch_queue_t dq); void _dispatch_queue_drain(dispatch_queue_t dq); void _dispatch_queue_dispose(dispatch_queue_t dq); +void _dispatch_queue_push_list_slow(dispatch_queue_t dq, struct dispatch_object_s *obj); __attribute__((always_inline)) static inline void @@ -90,17 +91,14 @@ _dispatch_queue_push_list(dispatch_queue_t dq, dispatch_object_t _head, dispatch struct dispatch_object_s *prev, *head = _head._do, *tail = _tail._do; tail->do_next = NULL; - _dispatch_retain(dq); prev = fastpath(dispatch_atomic_xchg(&dq->dq_items_tail, tail)); if (prev) { // if we crash here with a value less than 0x1000, then we are at a known bug in client code // for example, see _dispatch_queue_dispose or _dispatch_atfork_child prev->do_next = head; } else { - dq->dq_items_head = head; - _dispatch_wakeup(dq); + _dispatch_queue_push_list_slow(dq, head); } - _dispatch_release(dq); } #define _dispatch_queue_push(x, y) _dispatch_queue_push_list((x), (y), (y)) diff --git a/testing/Makefile b/testing/Makefile new file mode 100644 index 0000000..1f2ec73 --- /dev/null +++ b/testing/Makefile @@ -0,0 +1,127 @@ +# No workie: dispatch_sema +TESTS= dispatch_apply \ + dispatch_api \ + dispatch_c99 \ + dispatch_cffd \ + dispatch_deadname \ + dispatch_debug \ + queue_finalizer \ + dispatch_group \ + dispatch_overcommit \ + dispatch_pingpong \ + dispatch_plusplus \ + dispatch_priority \ + dispatch_priority2 \ + dispatch_proc \ + dispatch_read \ + dispatch_read2 \ + dispatch_after \ + dispatch_timer \ + dispatch_sema \ + dispatch_suspend_timer \ + dispatch_timer_bit31 \ + dispatch_timer_bit63 \ + dispatch_timer_oneshot \ + dispatch_timer_set_time \ + dispatch_starfish \ + dispatch_cascade \ + dispatch_drift \ + dispatch_readsync \ + nsoperation + +all: harness summarize bench $(TESTS) + @lipo -remove x86_64 -output dispatch_timer_bit31 dispatch_timer_bit31 2>/dev/null || true + +logs: $(addsuffix .testlog, $(TESTS)) +debuglogs: $(addsuffix .debuglog, $(TESTS)) + +testbots: + $(MAKE) test + +test: clean-logs + $(MAKE) _test + +_test: all logs debuglogs + @cat *.testlog *.debuglog + @cat *.testlog *.debuglog | ./summarize + +# Override ARCHS and SDKROOT to cross-build test suite + +SRCS = dispatch_test.c +OBJS = $(SRCS:%.c=%.o) +ARCHS=i386 x86_64 ppc +CFLAGS = -Werror -Wall -Wextra -Wshadow -mdynamic-no-pic -Os -g $(patsubst %, -arch %,$(ARCHS)) +CPPFLAGS = $(CFLAGS) +LDFLAGS = $(patsubst %, -arch %,$(ARCHS)) +LDLIBS = -lstdc++ + +ifneq ($(SDKROOT),) +CFLAGS += -isysroot $(SDKROOT) +LDFLAGS += -isysroot $(SDKROOT) +CC = xcrun -sdk $(SDKROOT) gcc +endif + +harness: harness.o $(OBJS) +summarize: summarize.o +bench: bench.o func.o + $(CC) $(LDFLAGS) -framework Foundation $(LDLIBS) -o $@ $^ + +bench.o: bench.mm + $(CC) -x objective-c++ $(CFLAGS) -c $^ -o $@ +func.o: func.c + $(CC) -x c++ $(CFLAGS) -c $^ -o $@ + +dispatch_apply: dispatch_apply.o $(OBJS) +dispatch_api: dispatch_api.o $(OBJS) +dispatch_c99: dispatch_c99.o $(OBJS) +dispatch_cffd: dispatch_cffd.o $(OBJS) + $(CC) $(LDFLAGS) -framework CoreFoundation -o $@ $^ +dispatch_deadname: dispatch_deadname.o $(OBJS) +dispatch_debug: dispatch_debug.o $(OBJS) +dispatch_group: dispatch_group.o $(OBJS) +dispatch_overcommit: dispatch_overcommit.o $(OBJS) +dispatch_pingpong: dispatch_pingpong.o $(OBJS) +dispatch_plusplus: dispatch_plusplus.o $(OBJS) +dispatch_priority: dispatch_priority.o $(OBJS) +dispatch_priority2: dispatch_priority2.o $(OBJS) +dispatch_proc: dispatch_proc.o $(OBJS) +queue_finalizer: queue_finalizer.o $(OBJS) +dispatch_read: dispatch_read.o $(OBJS) +dispatch_read2: dispatch_read2.o $(OBJS) +dispatch_after: dispatch_after.o $(OBJS) +dispatch_timer: dispatch_timer.o $(OBJS) +dispatch_suspend_timer: dispatch_suspend_timer.o $(OBJS) +dispatch_sema: dispatch_sema.o $(OBJS) +dispatch_timer_bit31: dispatch_timer_bit31.o $(OBJS) +dispatch_timer_bit63: dispatch_timer_bit63.o $(OBJS) +dispatch_timer_oneshot: dispatch_timer_oneshot.o $(OBJS) +dispatch_timer_set_time: dispatch_timer_set_time.o $(OBJS) +dispatch_drift: dispatch_drift.o $(OBJS) +dispatch_starfish: dispatch_starfish.o $(OBJS) +dispatch_cascade: dispatch_cascade.o $(OBJS) +dispatch_timer_bit31: dispatch_timer_bit31.o $(OBJS) +dispatch_readsync: dispatch_readsync.o $(OBJS) +ENVIRON_nsoperation = NOLEAKS=1 +nsoperation: nsoperation.o $(OBJS) + $(CC) $(LDFLAGS) -framework Foundation -o $@ $^ + +dispatch_api.o: dispatch_api.c + $(CC) -c $(CFLAGS) -include $(SDKROOT)/usr/include/dispatch/dispatch.h -pendantic -o $@ $^ + +dispatch_c99.o: dispatch_c99.c + $(CC) -c $(CFLAGS) -std=c99 -pedantic -o $@ $^ + +dispatch_priority2.o: dispatch_priority.c + $(CC) -c $(CFLAGS) -DUSE_SET_TARGET_QUEUE=1 -o $@ $^ + +$(addsuffix .testlog, $(TESTS)): harness $(TESTS) + $(ENVIRON_$(basename $@)) ./harness ./$(basename $@) > $@ + +$(addsuffix .debuglog, $(TESTS)): harness $(TESTS) + $(ENVIRON_$(basename $@)) DYLD_IMAGE_SUFFIX=_debug ./harness ./$(basename $@) > $@ + +clean-logs: + rm -f *.testlog *.debuglog *.leakslog + +clean: clean-logs + rm -f *.o *.dSYM bench harness summarize $(TESTS) diff --git a/testing/apply_strtoull.c b/testing/apply_strtoull.c new file mode 100644 index 0000000..82671a0 --- /dev/null +++ b/testing/apply_strtoull.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + struct stat sb; + char **numbers; + char *map; + size_t i, j; + int r, fd; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(EXIT_FAILURE); + } + + fd = open(argv[1], O_RDONLY); + assert(fd != -1); + + r = fstat(fd, &sb); + assert(r != -1); + + if (sb.st_len == 0) { + fprintf(stderr, "The file is zero length.\n"); + exit(EXIT_FAILURE); + } + + map = mmap(NULL, sb.st_len, PROT_READ, MAP_FILE, fd, 0); + assert(map != MAP_FAILED); + + numbers = malloc(sb.st_len * sizeof(void *)); /* more than enough */ + assert(numbers); + + /* XXX finish me */ + numbers[0] = map; + j = 1; + for (i = 0; i < sb.st_len; i++) { + if (map[i] == '\n') { + numbers[j] = map + i; + j++; + continue; + } + i++; + } + i + ; i < sb.st_len; i++) { + if (map[i] + } + + dispatch_apply(b, cnt); + + exit(EXIT_SUCCESS); +} diff --git a/testing/bench.mm b/testing/bench.mm new file mode 100644 index 0000000..51809b2 --- /dev/null +++ b/testing/bench.mm @@ -0,0 +1,488 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __BLOCKS__ +#include +#endif +#include +#include + +extern "C" { +__private_extern__ void func(void); +#ifdef __BLOCKS__ +__private_extern__ void (^block)(void); +#endif +static void backflip(void *ctxt); +static void backflip_done(void); +} + +@interface BasicObject : NSObject +{ +} +- (void) method; +@end + +@implementation BasicObject +- (void) method +{ +} +@end + +class BasicClass { +public: + virtual void virtfunc(void) { + }; +}; + +static void * +force_a_thread(void *arg) +{ + pause(); + abort(); + return arg; +} + +static volatile int32_t global; + +static const size_t cnt = 10000000; +static const size_t cnt2 = 100000; + +static uint64_t bfs; +static long double loop_cost; +static long double cycles_per_nanosecond; +static mach_timebase_info_data_t tbi; + +//static void func2(void *, dispatch_item_t di); + +static void __attribute__((noinline)) +print_result(uint64_t s, const char *str) +{ + uint64_t d, e = mach_absolute_time(); + long double dd; + + d = e - s; + + if (tbi.numer != tbi.denom) { + d *= tbi.numer; + d /= tbi.denom; + } + + dd = (typeof(dd))d / (typeof(dd))cnt; + + dd -= loop_cost; + + if (loop_cost == 0.0) { + loop_cost = dd; + } + + dd *= cycles_per_nanosecond; + + printf("%-45s%15.3Lf cycles\n", str, dd); +} + +static void __attribute__((noinline)) +print_result2(uint64_t s, const char *str) +{ + uint64_t d, e = mach_absolute_time(); + long double dd; + + d = e - s; + + if (tbi.numer != tbi.denom) { + d *= tbi.numer; + d /= tbi.denom; + } + + dd = (typeof(dd))d / (typeof(dd))cnt2; + + dd -= loop_cost; + dd *= cycles_per_nanosecond; + + printf("%-45s%15.3Lf cycles\n", str, dd); +} + +#if defined(__i386__) || defined(__x86_64__) +static inline uint64_t +rdtsc(void) +{ + uint32_t lo, hi; + + asm volatile("rdtsc" : "=a" (lo), "=d" (hi)); + + return (uint64_t)hi << 32 | lo; +} +#endif + +static struct fml { + struct fml *fml_next; +} *fixed_malloc_lifo_head; + +struct fml *fixed_malloc_lifo(void);// __attribute__((noinline)); +void fixed_free_lifo(struct fml *fml);// __attribute__((noinline)); + +struct fml * +fixed_malloc_lifo(void) +{ + struct fml *fml_r = fixed_malloc_lifo_head; + + if (fml_r) { + fixed_malloc_lifo_head = fml_r->fml_next; + return fml_r; + } else { + return (struct fml *)malloc(32); + } +} + +void +fixed_free_lifo(struct fml *fml) +{ + fml->fml_next = fixed_malloc_lifo_head; + fixed_malloc_lifo_head = fml; +} + +int +main(void) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + pthread_mutex_t plock = PTHREAD_MUTEX_INITIALIZER; + OSSpinLock slock = OS_SPINLOCK_INIT; + BasicObject *bo; + BasicClass *bc; + pthread_t pthr_pause; + dispatch_queue_t q, mq; + kern_return_t kr; + semaphore_t sem; + uint64_t freq; + uint64_t s; + size_t freq_len = sizeof(freq); + size_t bf_cnt = cnt; + unsigned i; + int r; + + r = sysctlbyname("hw.cpufrequency", &freq, &freq_len, NULL, 0); + assert(r != -1); + assert(freq_len == sizeof(freq)); + + cycles_per_nanosecond = (long double)freq / (long double)NSEC_PER_SEC; + + assert(pool); + + /* Malloc has different logic for threaded apps. */ + r = pthread_create(&pthr_pause, NULL, force_a_thread, NULL); + assert(r == 0); + + kr = mach_timebase_info(&tbi); + assert(kr == 0); +#if defined(__i386__) || defined(__x86_64__) + assert(tbi.numer == tbi.denom); /* This will fail on PowerPC. */ +#endif + + bo = [[BasicObject alloc] init]; + assert(bo); + + bc = new BasicClass(); + assert(bc); + + q = dispatch_queue_create("com.apple.bench-dispatch", NULL); + assert(q); + + mq = dispatch_get_main_queue(); + assert(mq); + + printf("%-45s%15Lf\n\n", "Cycles per nanosecond:", cycles_per_nanosecond); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + asm volatile(""); + } + print_result(s, "Empty loop:"); + + printf("\nLoop cost subtracted from the following:\n\n"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + mach_absolute_time(); + } + print_result(s, "mach_absolute_time():"); + +#if defined(__i386__) || defined(__x86_64__) + s = mach_absolute_time(); + for (i = cnt; i; i--) { + rdtsc(); + } + print_result(s, "rdtsc():"); +#endif + + s = mach_absolute_time(); + for (i = cnt2; i; i--) { + pthread_t pthr; + void *pr; + + r = pthread_create(&pthr, NULL, (void *(*)(void *))func, NULL); + assert(r == 0); + r = pthread_join(pthr, &pr); + assert(r == 0); + } + print_result2(s, "pthread create+join:"); + + s = mach_absolute_time(); + for (i = cnt2; i; i--) { + kr = semaphore_create(mach_task_self(), &sem, SYNC_POLICY_FIFO, 0); + assert(kr == 0); + kr = semaphore_destroy(mach_task_self(), sem); + assert(kr == 0); + } + print_result2(s, "Mach semaphore create/destroy:"); + + kr = semaphore_create(mach_task_self(), &sem, SYNC_POLICY_FIFO, 0); + assert(kr == 0); + s = mach_absolute_time(); + for (i = cnt2; i; i--) { + kr = semaphore_signal(sem); + assert(kr == 0); + } + print_result2(s, "Mach semaphore signal:"); + kr = semaphore_destroy(mach_task_self(), sem); + assert(kr == 0); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + pthread_yield_np(); + } + print_result(s, "pthread_yield_np():"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + free(malloc(32)); + } + print_result(s, "free(malloc(32)):"); + + s = mach_absolute_time(); + for (i = cnt / 2; i; i--) { + void *m1 = malloc(32); + void *m2 = malloc(32); + free(m1); + free(m2); + } + print_result(s, "Avoiding the MRU cache of free(malloc(32)):"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + fixed_free_lifo(fixed_malloc_lifo()); + } + print_result(s, "per-thread/fixed free(malloc(32)):"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + assert(strtoull("18446744073709551615", NULL, 0) == ~0ull); + } + print_result(s, "strtoull(\"18446744073709551615\") == ~0ull:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + func(); + } + print_result(s, "Empty function call:"); + +#ifdef __BLOCKS__ + s = mach_absolute_time(); + for (i = cnt; i; i--) { + block(); + } + print_result(s, "Empty block call:"); +#endif + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + [bo method]; + } + print_result(s, "Empty ObjC call:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + bc->virtfunc(); + } + print_result(s, "Empty C++ virtual call:"); + + s = mach_absolute_time(); + for (i = cnt2; i; i--) { + [bo description]; + } + print_result2(s, "\"description\" ObjC call:"); + + [pool release]; + + pool = NULL; + +#if defined(__i386__) || defined(__x86_64__) + s = mach_absolute_time(); + for (i = cnt; i; i--) { + asm("nop"); + } + print_result(s, "raw 'nop':"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + asm("pause"); + } + print_result(s, "raw 'pause':"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + asm("mfence"); + } + print_result(s, "Atomic mfence:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + asm("lfence"); + } + print_result(s, "Atomic lfence:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + asm("sfence"); + } + print_result(s, "Atomic sfence:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + uint64_t sidt_rval; + asm("sidt %0" : "=m" (sidt_rval)); + } + print_result(s, "'sidt' instruction:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + int prev; + asm volatile("cmpxchg %1,%2" : "=a" (prev) : "r" (0l), "m" (global), "0" (1l)); + } + print_result(s, "'cmpxchg' without the 'lock' prefix:"); +#endif + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + __sync_lock_test_and_set(&global, 0); + } + print_result(s, "Atomic xchg:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + __sync_val_compare_and_swap(&global, 1, 0); + } + print_result(s, "Atomic cmpxchg:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + __sync_fetch_and_add(&global, 1); + } + print_result(s, "Atomic increment:"); + + global = 0; + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + OSAtomicIncrement32Barrier(&global); + } + print_result(s, "OSAtomic increment:"); + + global = 0; + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + while (!__sync_bool_compare_and_swap(&global, 0, 1)) { + do { +#if defined(__i386__) || defined(__x86_64__) + asm("pause"); +#endif + } while (global); + } + global = 0; + } + print_result(s, "Inlined spin lock/unlock:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + OSSpinLockLock(&slock); + OSSpinLockUnlock(&slock); + } + print_result(s, "OS spin lock/unlock:"); + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + r = pthread_mutex_lock(&plock); + assert(r == 0); + r = pthread_mutex_unlock(&plock); + assert(r == 0); + } + print_result(s, "pthread lock/unlock:"); + +#ifdef __BLOCKS__ + s = mach_absolute_time(); + for (i = cnt; i; i--) { + dispatch_sync(q, ^{ }); + } + print_result(s, "dispatch_sync:"); +#endif + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + dispatch_sync_f(q, NULL, (void (*)(void *))func); + } + print_result(s, "dispatch_sync_f:"); + +#ifdef __BLOCKS__ + s = mach_absolute_time(); + for (i = cnt; i; i--) { + dispatch_barrier_sync(q, ^{ }); + } + print_result(s, "dispatch_barrier_sync:"); +#endif + + s = mach_absolute_time(); + for (i = cnt; i; i--) { + dispatch_barrier_sync_f(q, NULL, (void (*)(void *))func); + } + print_result(s, "dispatch_barrier_sync_f:"); + + s = mach_absolute_time(); + dispatch_apply_f(cnt, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), NULL, (void (*)(void *, size_t))func); + s += loop_cost; /* cancel out the implicit subtraction done by the next line */ + print_result(s, "dispatch_apply_f():"); + + // we do a "double backflip" to hit the fast-path of the enqueue/dequeue logic + bfs = mach_absolute_time(); + dispatch_async_f(dispatch_get_main_queue(), &bf_cnt, backflip); + dispatch_async_f(dispatch_get_main_queue(), &bf_cnt, backflip); + + dispatch_main(); +} + +__attribute__((noinline)) +void +backflip_done(void) +{ + print_result(bfs, "dispatch_async_f():"); + exit(EXIT_SUCCESS); +} + +void +backflip(void *ctxt) +{ + size_t *bf_cnt = (size_t *)ctxt; + if (--(*bf_cnt)) { + return dispatch_async_f(dispatch_get_main_queue(), ctxt, backflip); + } + backflip_done(); +} diff --git a/testing/conc.c b/testing/conc.c new file mode 100644 index 0000000..40766e2 --- /dev/null +++ b/testing/conc.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int +main(void) +{ + dispatch_block_t wb = ^(dispatch_item_t di) { printf("\t\t%p\tstart\n", pthread_self()); sleep(3); }; + dispatch_block_t cb = ^(dispatch_item_t di) { printf("\t\t%p\tdone\n", pthread_self()); }; + dispatch_queue_t dq; + bool r; + int i; + + dq = dispatch_queue_new("conc", DISPATCH_QUEUE_CONCURRENT, NULL, NULL, NULL); + assert(dq); + + for (i = 0; i < 10; i++) { + r = dispatch_call(dq, wb, cb, NULL, NULL); + assert(r); + } + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_after.c b/testing/dispatch_after.c new file mode 100644 index 0000000..7bb174a --- /dev/null +++ b/testing/dispatch_after.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" +#include + +void done(void *arg __unused) { + sleep(1); + test_stop(); +} + +int +main(void) +{ + __block dispatch_time_t time_a_min, time_a, time_a_max; + __block dispatch_time_t time_b_min, time_b, time_b_max; + __block dispatch_time_t time_c_min, time_c, time_c_max; + + + test_start("Dispatch After"); + + dispatch_async(dispatch_get_main_queue(), ^{ + time_a_min = dispatch_time(0, 5.5*NSEC_PER_SEC); + time_a = dispatch_time(0, 6*NSEC_PER_SEC); + time_a_max = dispatch_time(0, 6.5*NSEC_PER_SEC); + dispatch_after(time_a, dispatch_get_current_queue(), ^{ + dispatch_time_t now_a = dispatch_time(0, 0); + test_long_less_than("can't finish faster than 5.5s", 0, now_a - time_a_min); + test_long_less_than("must finish faster than 6.5s", 0, time_a_max - now_a); + + time_b_min = dispatch_time(0, 1.5*NSEC_PER_SEC); + time_b = dispatch_time(0, 2*NSEC_PER_SEC); + time_b_max = dispatch_time(0, 2.5*NSEC_PER_SEC); + dispatch_after(time_b, dispatch_get_current_queue(), ^{ + dispatch_time_t now_b = dispatch_time(0, 0); + test_long_less_than("can't finish faster than 1.5s", 0, now_b - time_b_min); + test_long_less_than("must finish faster than 2.5s", 0, time_b_max - now_b); + + time_c_min = dispatch_time(0, 0*NSEC_PER_SEC); + time_c = dispatch_time(0, 0*NSEC_PER_SEC); + time_c_max = dispatch_time(0, .5*NSEC_PER_SEC); + dispatch_after(time_c, dispatch_get_current_queue(), ^{ + dispatch_time_t now_c = dispatch_time(0, 0); + test_long_less_than("can't finish faster than 0s", 0, now_c - time_c_min); + test_long_less_than("must finish faster than .5s", 0, time_c_max - now_c); + + dispatch_async_f(dispatch_get_current_queue(), NULL, done); + }); + }); + }); + }); + + dispatch_main(); + return 0; +} diff --git a/testing/dispatch_api.c b/testing/dispatch_api.c new file mode 100644 index 0000000..e1fb916 --- /dev/null +++ b/testing/dispatch_api.c @@ -0,0 +1,20 @@ +#include + +#include "dispatch_test.h" + +void +work(void *context __attribute__((unused))) +{ + test_stop(); + exit(0); +} + +int main(void) { + test_start("Dispatch (Public) API"); + dispatch_queue_t q = dispatch_get_main_queue(); + test_ptr_notnull("dispatch_get_main_queue", q); + + dispatch_async_f(dispatch_get_main_queue(), NULL, work); + dispatch_main(); + return 0; +} diff --git a/testing/dispatch_apply.c b/testing/dispatch_apply.c new file mode 100644 index 0000000..60e075f --- /dev/null +++ b/testing/dispatch_apply.c @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +int +main(void) +{ + test_start("Dispatch Apply"); + + volatile __block int32_t count = 0; + const int32_t final = 32; + + dispatch_queue_t queue = dispatch_get_concurrent_queue(0); + test_ptr_notnull("dispatch_get_concurrent_queue", queue); + + dispatch_apply(final, queue, ^(size_t i __attribute__((unused))) { + OSAtomicIncrement32(&count); + }); + + test_long("count", count, final); + test_stop(); + + return 0; +} diff --git a/testing/dispatch_c99.c b/testing/dispatch_c99.c new file mode 100644 index 0000000..2205c9e --- /dev/null +++ b/testing/dispatch_c99.c @@ -0,0 +1,21 @@ +#include +#include + +#include "dispatch_test.h" + +void +work(void *context __attribute__((unused))) +{ + test_stop(); + exit(0); +} + +int main(void) { + test_start("Dispatch C99"); + dispatch_queue_t q = dispatch_get_main_queue(); + test_ptr_notnull("dispatch_get_main_queue", q); + + dispatch_async_f(dispatch_get_main_queue(), NULL, work); + dispatch_main(); + return 0; +} diff --git a/testing/dispatch_cascade.c b/testing/dispatch_cascade.c new file mode 100644 index 0000000..7e09e41 --- /dev/null +++ b/testing/dispatch_cascade.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include + +#include "dispatch_test.h" + +int done = 0; + +#define QUEUES 80 +dispatch_queue_t queues[QUEUES]; + + +#define BLOCKS 10000 +union { + size_t index; + char padding[64]; +} indices[BLOCKS]; + +size_t iterations = QUEUES * BLOCKS * 0.25; + +void +histogram(void) { + size_t counts[QUEUES] = {}; + size_t maxcount = 0; + + size_t q; + for (q = 0; q < QUEUES; ++q) { + size_t i; + for (i = 0; i < BLOCKS; ++i) { + if (indices[i].index == q) { + ++counts[q]; + } + } + } + + for (q = 0; q < QUEUES; ++q) { + if (counts[q] > maxcount) { + maxcount = counts[q]; + } + } + + printf("maxcount = %ld\n", maxcount); + + size_t x,y; + for (y = 20; y > 0; --y) { + for (x = 0; x < QUEUES; ++x) { + double fraction = (double)counts[x] / (double)maxcount; + double value = fraction * (double)20; + printf("%s", (value > y) ? "*" : " "); + } + printf("\n"); + } +} + +void +cascade(void* context) { + size_t idx, *idxptr = context; + + if (done) return; + + idx = *idxptr + 1; + + if (idx < QUEUES) { + *idxptr = idx; + dispatch_async_f(queues[idx], context, cascade); + } + + if (__sync_sub_and_fetch(&iterations, 1) == 0) { + done = 1; + histogram(); + test_stop(); + exit(0); + } +} + +int +main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) { + int i; + + test_start("Dispatch Cascade"); + + for (i = 0; i < QUEUES; ++i) { + queues[i] = dispatch_queue_create(NULL, NULL); + } + + for (i = 0; i < BLOCKS; ++i) { + cascade(&indices[i].index); + } + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_cffd.c b/testing/dispatch_cffd.c new file mode 100644 index 0000000..13e4ac3 --- /dev/null +++ b/testing/dispatch_cffd.c @@ -0,0 +1,139 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +int debug = 0; + +#define DEBUG(...) do { \ + if (debug) fprintf(stderr, __VA_ARGS__); \ + } while(0); + +#define assert_errno(str, expr) do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: %s\n", (str), strerror(errno)); \ + exit(1); \ + } } while(0); + +int +init_kqueue(void) +{ + int kq; + int res; + struct kevent ke; + static struct timespec t0; + + kq = kqueue(); + assert_errno("kqueue", kq >= 0); + + EV_SET(&ke, 1, EVFILT_TIMER, EV_ADD, NOTE_SECONDS, 1, 0); + + res = kevent(kq, &ke, 1, NULL, 0, &t0); + assert_errno("kevent", res == 0); + + return kq; +} + +int +read_kevent(int kq) +{ + int res; + struct kevent ke; + //static struct timespec t0; + + res = kevent(kq, NULL, 0, &ke, 1, NULL); + assert_errno("kevent", res >= 0); + + fprintf(stdout, "kevent.data = %ld\n", ke.data); + + return (res < 0); +} + + +static void +cffd_callback(CFFileDescriptorRef cffd, + CFOptionFlags callBackTypes __attribute__((unused)), + void *info __attribute__((unused))) +{ + int kq; + + kq = CFFileDescriptorGetNativeDescriptor(cffd); + if (read_kevent(kq) == 0) { + // ... + } + + CFFileDescriptorEnableCallBacks(cffd, kCFFileDescriptorReadCallBack); +} + +void +timer() +{ + dispatch_source_t ds; + ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); + assert(ds); + dispatch_source_set_timer(ds, dispatch_time(0, 1*NSEC_PER_SEC), NSEC_PER_SEC, 0); + dispatch_source_set_event_handler(ds, ^{ + printf("ping\n"); + }); + dispatch_resume(ds); +} + +void +hangup() +{ + dispatch_source_t ds; + ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, dispatch_get_main_queue()); + assert(ds); + dispatch_source_set_event_handler(ds, ^{ + printf("hangup\n"); + }); + dispatch_resume(ds); +} + +int +main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) +{ + int kq; + CFFileDescriptorRef cffd; + CFRunLoopSourceRef rls; + CFFileDescriptorContext ctx; + + test_start("CFFileDescriptor"); + + signal(SIGHUP, SIG_IGN); + + kq = init_kqueue(); + + memset(&ctx, 0, sizeof(CFFileDescriptorContext)); + cffd = CFFileDescriptorCreate(NULL, kq, 1, cffd_callback, &ctx); + assert(cffd); + + rls = CFFileDescriptorCreateRunLoopSource(NULL, cffd, 0); + assert(rls); + CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode); + CFFileDescriptorEnableCallBacks(cffd, kCFFileDescriptorReadCallBack); + +// timer(); +// hangup(); + + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, false); + + test_stop(); + + return 0; +} + diff --git a/testing/dispatch_deadname.c b/testing/dispatch_deadname.c new file mode 100644 index 0000000..1dfc1c7 --- /dev/null +++ b/testing/dispatch_deadname.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +int +main(void) +{ + test_start("Dispatch dead-name notification"); + + dispatch_async(dispatch_get_concurrent_queue(0), ^{ + mach_port_t mp = pthread_mach_thread_np(pthread_self()); + dispatch_source_t ds0; + kern_return_t kr; + + assert(mp); + + kr = mach_port_mod_refs(mach_task_self(), mp, MACH_PORT_RIGHT_SEND, 1); + + assert(kr == 0); + + ds0 = dispatch_source_machport_create(mp, DISPATCH_MACHPORT_DEAD, NULL, dispatch_get_main_queue(), + ^(dispatch_event_t de) { + dispatch_release(dispatch_event_get_source(de)); + test_stop(); + exit(EXIT_SUCCESS); + }); + + test_ptr_notnull("dispatch_source_machport_create", ds0); + + // give the mgr queue time to start, otherwise the mgr queue will run + // on this thread, thus defeating the test which assumes that this + // thread will die. + sleep(1); + }); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_debug.c b/testing/dispatch_debug.c new file mode 100644 index 0000000..8f55106 --- /dev/null +++ b/testing/dispatch_debug.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include + +#include "dispatch_test.h" + +int main(void) +{ + test_start("Dispatch Debug"); + + dispatch_queue_t main_q = dispatch_get_main_queue(); + dispatch_debug(main_q, "dispatch_queue_t"); + + dispatch_queue_t default_q = dispatch_get_concurrent_queue(0); + dispatch_debug(default_q, "dispatch_queue_t"); + + dispatch_source_attr_t attr = dispatch_source_attr_create(); + dispatch_debug(attr, "dispatch_source_attr_t"); + + dispatch_source_t s = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + 1000000000ull, 0, attr, main_q, ^(dispatch_event_t ev __attribute__((unused))) {}); + dispatch_debug(s, "dispatch_source_t"); + + dispatch_group_t g = dispatch_group_create(); + dispatch_debug(g, "dispatch_group_t"); + + return 0; +} diff --git a/testing/dispatch_drift.c b/testing/dispatch_drift.c new file mode 100644 index 0000000..902d278 --- /dev/null +++ b/testing/dispatch_drift.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include +#include "dispatch_test.h" + +int +main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) +{ + __block uint32_t count = 0; + __block uint64_t first_time_m = 0ULL; + __block double first_time_d; + __block double last_jitter = 0; + // 10 times a second + uint64_t interval = 1000000000 / 10; + double interval_d = interval / 1000000000.0; + // for 25 seconds + unsigned int target = 25 / interval_d; + + test_start("Timer drift test"); + + dispatch_source_t t = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, interval, 0, NULL, dispatch_get_main_queue(), + ^(dispatch_event_t event __attribute__((unused))) { + struct timeval now_tv; + static double first = 0; + gettimeofday(&now_tv, NULL); + double now = now_tv.tv_sec + now_tv.tv_usec / 1000000.0; + + if (count == 0) { + // Because this is taken at 1st timer fire, + // later jitter values may be negitave. + // This doesn't effect the drift calculation. + first = now; + } + double goal = first + interval_d * count; + double jitter = goal - now; + double drift = jitter - last_jitter; + + printf("%4d: jitter %f, drift %f\n", count, jitter, drift); +#if 0 + test_double_less_than("drift", now_d - expected_fire_time_d, .001); +#endif + + if (target <= ++count) { + if (drift < 0) { + drift = -drift; + } + test_double_less_than("drift", drift, 0.0001); + test_stop(); + } + last_jitter = jitter; + }); + + test_ptr_notnull("timer source", t); + + dispatch_main(); + return 0; +} + diff --git a/testing/dispatch_group.c b/testing/dispatch_group.c new file mode 100644 index 0000000..bda3a03 --- /dev/null +++ b/testing/dispatch_group.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +#ifndef NSEC_PER_SEC +#define NSEC_PER_SEC 1000000000 +#endif + +dispatch_group_t +create_group(size_t count, int delay) +{ + size_t i; + + dispatch_group_t group = dispatch_group_create(); + + for (i = 0; i < count; ++i) { + dispatch_queue_t queue = dispatch_queue_create(NULL, NULL); + assert(queue); + + dispatch_group_async(group, queue, ^{ + if (delay) { + fprintf(stderr, "sleeping...\n"); + sleep(delay); + fprintf(stderr, "done.\n"); + } + }); + + dispatch_release(queue); + } + return group; +} + +int +main(void) +{ + long res; + + test_start("Dispatch Group"); + + dispatch_group_t group; + + group = create_group(100, 0); + test_ptr_notnull("dispatch_group_async", group); + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + // should be OK to re-use a group + dispatch_group_async(group, dispatch_get_concurrent_queue(0), ^{}); + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + dispatch_release(group); + group = NULL; + + group = create_group(3, 7); + test_ptr_notnull("dispatch_group_async", group); + + res = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC)); + test_long("dispatch_group_wait", !res, 0); + + // retry after timeout (this time succeed) + res = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC)); + test_long("dispatch_group_wait", res, 0); + + dispatch_release(group); + group = NULL; + + group = create_group(100, 0); + test_ptr_notnull("dispatch_group_async", group); + + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + dispatch_queue_t m = dispatch_get_main_queue(); + dispatch_queue_t c = dispatch_get_current_queue(); + test_ptr("Notification Received", m, c); + test_stop(); + }); + + dispatch_release(group); + group = NULL; + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_overcommit.c b/testing/dispatch_overcommit.c new file mode 100644 index 0000000..af5e59a --- /dev/null +++ b/testing/dispatch_overcommit.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +int32_t count = 0; +const int32_t final = 32; + +int +main(void) +{ + test_start("Dispatch Overcommit"); + + dispatch_queue_attr_t attr = dispatch_queue_attr_create(); + test_ptr_notnull("dispatch_queue_attr_create", attr); + dispatch_queue_attr_set_flags(attr, DISPATCH_QUEUE_OVERCOMMIT); + + int i; + for (i = 0; i < final; ++i) { + char* name; + asprintf(&name, "test.overcommit.%d", i); + + dispatch_queue_t queue = dispatch_queue_create(name, attr); + test_ptr_notnull("dispatch_queue_create", queue); + free(name); + + dispatch_async(queue, ^{ + OSAtomicIncrement32(&count); + if (count == final) { + test_long("count", count, final); + test_stop(); + } else { + while (1); // spin + } + }); + } + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_pingpong.c b/testing/dispatch_pingpong.c new file mode 100644 index 0000000..f40825e --- /dev/null +++ b/testing/dispatch_pingpong.c @@ -0,0 +1,37 @@ +#include +#include + +#include "dispatch_test.h" + +uint32_t count = 0; +const uint32_t final = 1000000; // 10M + +void pingpongloop(dispatch_group_t group, dispatch_queue_t ping, dispatch_queue_t pong, size_t counter) { + //printf("[%p] %s: %lu\n", (void*)(uintptr_t)pthread_self(), dispatch_queue_get_label(dispatch_get_current_queue()), counter); + if (counter < final) { + dispatch_group_async(group, pong, ^{ pingpongloop(group, pong, ping, counter+1); }); + } else { + count = counter; + } +} + +int main(void) { + + test_start("Dispatch Ping Pong"); + + dispatch_queue_t ping = dispatch_queue_create("ping", NULL); + test_ptr_notnull("dispatch_queue_create(ping)", ping); + dispatch_queue_t pong = dispatch_queue_create("pong", NULL); + test_ptr_notnull("dispatch_queue_create(pong)", pong); + + dispatch_group_t group = dispatch_group_create(); + test_ptr_notnull("dispatch_group_create", group); + + pingpongloop(group, ping, pong, 0); + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + test_long("count", count, final); + test_stop(); + + return 0; +} diff --git a/testing/dispatch_plusplus.cpp b/testing/dispatch_plusplus.cpp new file mode 100644 index 0000000..6baccab --- /dev/null +++ b/testing/dispatch_plusplus.cpp @@ -0,0 +1,17 @@ +#include +#include + +#include "dispatch_test.h" + +int main(void) { + test_start("Dispatch C++"); + dispatch_queue_t q = dispatch_get_main_queue(); + test_ptr_notnull("dispatch_get_main_queue", q); + + dispatch_async(dispatch_get_main_queue(), ^{ + test_stop(); + exit(0); + }); + dispatch_main(); + return 0; +} diff --git a/testing/dispatch_priority.c b/testing/dispatch_priority.c new file mode 100644 index 0000000..a964765 --- /dev/null +++ b/testing/dispatch_priority.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +int done = 0; + +#define BLOCKS 128 +#define PRIORITIES 3 + +#if TARGET_OS_EMBEDDED +#define LOOP_COUNT 2000000 +#else +#define LOOP_COUNT 100000000 +#endif + +char *labels[PRIORITIES] = { "LOW", "DEFAULT", "HIGH" }; +int priorities[PRIORITIES] = { DISPATCH_QUEUE_PRIORITY_LOW, DISPATCH_QUEUE_PRIORITY_DEFAULT, DISPATCH_QUEUE_PRIORITY_HIGH }; + +union { + size_t count; + char padding[64]; +} counts[PRIORITIES]; + +#define ITERATIONS (size_t)(PRIORITIES * BLOCKS * 0.50) +size_t iterations = ITERATIONS; + +void +histogram(void) { + size_t maxcount = BLOCKS; + size_t sc[PRIORITIES]; + + size_t total = 0; + + size_t x,y; + for (y = 0; y < PRIORITIES; ++y) { + sc[y] = counts[y].count; + } + + for (y = 0; y < PRIORITIES; ++y) { + printf("%s: %ld\n", labels[y], sc[y]); + total += sc[y]; + + double fraction = (double)sc[y] / (double)maxcount; + double value = fraction * (double)80; + for (x = 0; x < 80; ++x) { + printf("%s", (value > x) ? "*" : " "); + } + printf("\n"); + } + + test_long("blocks completed", total, ITERATIONS); + test_long_less_than("high priority precedence", (long)sc[0], (long)sc[2]); +} + +void +cpubusy(void* context) +{ + size_t *count = context; + size_t iterdone; + + size_t idx; + for (idx = 0; idx < LOOP_COUNT; ++idx) { + if (done) break; + } + + if ((iterdone = __sync_sub_and_fetch(&iterations, 1)) == 0) { + __sync_add_and_fetch(&done, 1); + __sync_add_and_fetch(count, 1); + histogram(); + test_stop(); + exit(0); + } else if (iterdone > 0) { + __sync_add_and_fetch(count, 1); + } +} + +void +submit_work(dispatch_queue_t queue, void* context) +{ + int i; + + for (i = 0; i < BLOCKS; ++i) { + dispatch_async_f(queue, context, cpubusy); + } + +#if USE_SET_TARGET_QUEUE + dispatch_release(queue); +#endif +} + +int +main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) +{ + dispatch_queue_t q[PRIORITIES]; + int i; + +#if USE_SET_TARGET_QUEUE + test_start("Dispatch Priority (Set Target Queue)"); + for(i = 0; i < PRIORITIES; i++) { + q[i] = dispatch_queue_create(labels[i], NULL); + test_ptr_notnull("q[i]", q[i]); + assert(q[i]); + dispatch_set_target_queue(q[i], dispatch_get_global_queue(priorities[i], 0)); + dispatch_queue_set_width(q[i], DISPATCH_QUEUE_WIDTH_MAX_LOGICAL_CPUS); + } +#else + test_start("Dispatch Priority"); + for(i = 0; i < PRIORITIES; i++) { + q[i] = dispatch_get_global_queue(priorities[i], 0); + } +#endif + + for(i = 0; i < PRIORITIES; i++) { + submit_work(q[i], &counts[i].count); + } + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_proc.c b/testing/dispatch_proc.c new file mode 100644 index 0000000..6f5ef02 --- /dev/null +++ b/testing/dispatch_proc.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +#define PID_CNT 5 + +static long event_cnt; +static long cancel_cnt; + +int +main(void) +{ + dispatch_source_t proc; + int res; + pid_t pid; + + test_start("Dispatch Proc"); + + // Creates a process and register multiple observers. Send a signal, + // exit the process, etc., and verify all observers were notified. + + posix_spawnattr_t attr; + res = posix_spawnattr_init(&attr); + assert(res == 0); + res = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED); + assert(res == 0); + + char* args[] = { + "/bin/sleep", "2", NULL + }; + + res = posix_spawnp(&pid, args[0], NULL, &attr, args, NULL); + if (res < 0) { + perror(args[0]); + exit(127); + } + + res = posix_spawnattr_destroy(&attr); + assert(res == 0); + + dispatch_queue_t semaphore = dispatch_queue_create("semaphore", NULL); + + assert(pid > 0); + + int i; + for (i = 0; i < PID_CNT; ++i) { + dispatch_suspend(semaphore); + proc = dispatch_source_proc_create(pid, DISPATCH_PROC_EXIT, NULL, dispatch_get_main_queue(), + ^(dispatch_event_t ev) { + long err_dom, err_val; + if ((err_dom = dispatch_event_get_error(ev, &err_val))) { + test_long("PROC error domain", err_dom, DISPATCH_ERROR_DOMAIN_POSIX); + test_long("PROC error value", err_val, ECANCELED); + cancel_cnt++; + dispatch_resume(semaphore); + } else { + long flags = dispatch_event_get_flags(ev); + test_long("DISPATCH_PROC_EXIT", flags, DISPATCH_PROC_EXIT); + event_cnt++; + dispatch_release(dispatch_event_get_source(ev)); + } + }); + test_ptr_notnull("dispatch_source_proc_create", proc); + } + + dispatch_async(semaphore, ^{ + int status; + int res2 = waitpid(pid, &status, 0); + assert(res2 != -1); + //int passed = (WIFEXITED(status) && WEXITSTATUS(status) == 0); + test_long("Sub-process exited", WEXITSTATUS(status) | WTERMSIG(status), 0); + test_long("Event count", event_cnt, PID_CNT); + test_long("Cancel count", cancel_cnt, PID_CNT); + test_stop(); + }); + + kill(pid, SIGCONT); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_rdlock_bmark.c b/testing/dispatch_rdlock_bmark.c new file mode 100644 index 0000000..ec87100 --- /dev/null +++ b/testing/dispatch_rdlock_bmark.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +#define LAPS (1024 * 1024) +#define THREADS 1 + +static pthread_rwlock_t pthr_rwlock = PTHREAD_RWLOCK_INITIALIZER; +static semaphore_t wake_port; +static void (*test_func)(void *); +static unsigned int thr_count; + +static void +reader(void *ctxt __attribute__((unused))) +{ +} + +static void +pthr_worker(void *ctxt __attribute__((unused))) +{ + size_t i; + int r; + + for (i = 0; i < (LAPS / THREADS); i++) { + r = pthread_rwlock_rdlock(&pthr_rwlock); + assert(r == 0); + r = pthread_rwlock_unlock(&pthr_rwlock); + assert(r == 0); + } +} + +static void +gcd_worker(void *ctxt) +{ + dispatch_queue_t dq = ctxt; + size_t i; + + for (i = 0; i < (LAPS / THREADS); i++) { + dispatch_read_sync_f(dq, NULL, reader); + } +} + +static void * +worker(void *ctxt) +{ + kern_return_t kr; + + __sync_add_and_fetch(&thr_count, 1); + + kr = semaphore_wait(wake_port); + assert(kr == 0); + + test_func(ctxt); + + if (__sync_sub_and_fetch(&thr_count, 1) == 0) { + kr = semaphore_signal(wake_port); + assert(kr == 0); + } + + return NULL; +} + +static uint64_t +benchmark(void *ctxt) +{ + pthread_t pthr[THREADS]; + uint64_t cycles; + void *rval; + size_t i; + int r; + + for (i = 0; i < THREADS; i++) { + r = pthread_create(&pthr[i], NULL, worker, ctxt); + assert(r == 0); + } + + while (thr_count != THREADS) { + sleep(1); + } + + sleep(1); + + cycles = dispatch_benchmark(1, ^{ + kern_return_t kr; + + kr = semaphore_signal_all(wake_port); + assert(kr == 0); + kr = semaphore_wait(wake_port); + assert(kr == 0); + }); + + for (i = 0; i < THREADS; i++) { + r = pthread_join(pthr[i], &rval); + assert(r == 0); + } + + return cycles; +} + +int +main(void) +{ + uint64_t pthr_cycles, gcd_cycles; + long double ratio; + dispatch_queue_t dq; + kern_return_t kr; + int r; + + dq = dispatch_queue_create("test", NULL); + assert(dq); + + // pthreads lazily inits the object + // do not benchmark that fact + r = pthread_rwlock_rdlock(&pthr_rwlock); + assert(r == 0); + r = pthread_rwlock_unlock(&pthr_rwlock); + assert(r == 0); + + kr = semaphore_create(mach_task_self(), &wake_port, SYNC_POLICY_FIFO, 0); + assert(kr == 0); + + test_func = pthr_worker; + pthr_cycles = benchmark(NULL); + + test_func = gcd_worker; + gcd_cycles = benchmark(dq); + + dispatch_release(dq); + + ratio = pthr_cycles; + ratio /= gcd_cycles; + + printf("Cycles:\n\tPOSIX\t%llu\n", pthr_cycles); + printf("\tGCD\t%llu\n", gcd_cycles); + printf("Ratio:\t%Lf\n", ratio); + + return 0; +} diff --git a/testing/dispatch_read.c b/testing/dispatch_read.c new file mode 100644 index 0000000..02ede77 --- /dev/null +++ b/testing/dispatch_read.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +static size_t bytes_total; +static size_t bytes_read; + +int main(void) +{ + const char *path = "/usr/share/dict/words"; + struct stat sb; + + test_start("Dispatch Source Read"); + + int infd = open(path, O_RDONLY); + if (infd == -1) { + perror(path); + exit(EXIT_FAILURE); + } + if (fstat(infd, &sb) == -1) { + perror(path); + exit(EXIT_FAILURE); + } + bytes_total = sb.st_size; + + if (fcntl(infd, F_SETFL, O_NONBLOCK) != 0) { + perror(path); + exit(EXIT_FAILURE); + } + + dispatch_queue_t main_q = dispatch_get_main_queue(); + test_ptr_notnull("dispatch_get_main_queue", main_q); + + dispatch_source_attr_t attr = dispatch_source_attr_create(); + test_ptr_notnull("dispatch_source_attr_create", attr); + + dispatch_source_attr_set_finalizer(attr, ^(dispatch_source_t ds) { + test_ptr_notnull("finalizer ran", ds); + int res = close(infd); + test_errno("close", res == -1 ? errno : 0, 0); + test_stop(); + }); + + dispatch_source_t reader = dispatch_source_read_create(infd, attr, + main_q, ^(dispatch_event_t ev) { + long err_val; + long err_dom = dispatch_event_get_error(ev, &err_val); + if (!err_dom) { + size_t estimated = dispatch_event_get_bytes_available(ev); + printf("bytes available: %zu\n", estimated); + const ssize_t bufsiz = 1024*500; // 500 KB buffer + static char buffer[1024*500]; // 500 KB buffer + ssize_t actual = read(infd, buffer, sizeof(buffer)); + bytes_read += actual; + printf("bytes read: %zd\n", actual); + if (actual < bufsiz) { + actual = read(infd, buffer, sizeof(buffer)); + bytes_read += actual; + // confirm EOF condition + test_long("EOF", actual, 0); + dispatch_release(dispatch_event_get_source(ev)); + } + } else { + test_long("Error domain", err_dom, DISPATCH_ERROR_DOMAIN_POSIX); + test_long("Error value", err_val, ECANCELED); + test_long("Bytes read", bytes_read, bytes_total); + } + }); + + printf("reader = %p\n", reader); + assert(reader); + + dispatch_release(attr); + + dispatch_main(); +} diff --git a/testing/dispatch_read2.c b/testing/dispatch_read2.c new file mode 100644 index 0000000..1be8150 --- /dev/null +++ b/testing/dispatch_read2.c @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +static size_t bytes_total; +static size_t bytes_read; + +int main(void) +{ + const char *path = "/usr/share/dict/words"; + struct stat sb; + + test_start("Dispatch Source Read"); + + int infd = open(path, O_RDONLY); + if (infd == -1) { + perror(path); + exit(EXIT_FAILURE); + } + if (fstat(infd, &sb) == -1) { + perror(path); + exit(EXIT_FAILURE); + } + bytes_total = sb.st_size; + + if (fcntl(infd, F_SETFL, O_NONBLOCK) != 0) { + perror(path); + exit(EXIT_FAILURE); + } + + dispatch_queue_t main_q = dispatch_get_main_queue(); + test_ptr_notnull("dispatch_get_main_queue", main_q); + + dispatch_source_t reader = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, infd, 0, main_q); + test_ptr_notnull("dispatch_source_create", reader); + assert(reader); + + dispatch_source_set_event_handler(reader, ^{ + size_t estimated = dispatch_source_get_data(reader); + printf("bytes available: %zu\n", estimated); + const ssize_t bufsiz = 1024*500; // 500 KB buffer + static char buffer[1024*500]; // 500 KB buffer + ssize_t actual = read(infd, buffer, sizeof(buffer)); + bytes_read += actual; + printf("bytes read: %zd\n", actual); + if (actual < bufsiz) { + actual = read(infd, buffer, sizeof(buffer)); + bytes_read += actual; + // confirm EOF condition + test_long("EOF", actual, 0); + dispatch_source_cancel(reader); + } + }); + dispatch_source_set_cancel_handler(reader, ^{ + test_long("Bytes read", bytes_read, bytes_total); + test_stop(); + }); + dispatch_resume(reader); + + dispatch_main(); +} diff --git a/testing/dispatch_readsync.c b/testing/dispatch_readsync.c new file mode 100644 index 0000000..64da538 --- /dev/null +++ b/testing/dispatch_readsync.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +#define LAPS 10000 +#define INTERVAL 100 + +static size_t r_count = LAPS; +static size_t w_count = LAPS / INTERVAL; + +static void +writer(void *ctxt __attribute__((unused))) +{ + if (--w_count == 0) { + if (r_count == 0) { + test_stop(); + } + } +} + +static void +reader(void *ctxt __attribute__((unused))) +{ + if (__sync_sub_and_fetch(&r_count, 1) == 0) { + if (r_count == 0) { + test_stop(); + } + } +} + +int +main(void) +{ + dispatch_queue_t dq; + + test_start("Dispatch Reader/Writer Queues"); + + dq = dispatch_queue_create("com.apple.libdispatch.test_readsync", NULL); + assert(dq); + + dispatch_queue_set_width(dq, LONG_MAX); + + dispatch_apply(LAPS, dispatch_get_concurrent_queue(0), ^(size_t idx) { + dispatch_sync_f(dq, NULL, reader); + + if (idx % INTERVAL) { + dispatch_barrier_async_f(dq, NULL, writer); + } + }); + + dispatch_release(dq); + + dispatch_main(); +} diff --git a/testing/dispatch_sema.c b/testing/dispatch_sema.c new file mode 100644 index 0000000..f30a2c8 --- /dev/null +++ b/testing/dispatch_sema.c @@ -0,0 +1,33 @@ +#include +#include +#include +#include + +#include "dispatch_test.h" + +#define LAPS 10000 + +int +main(void) +{ + static size_t total; + dispatch_semaphore_t dsema; + + test_start("Dispatch Semaphore"); + + dsema = dispatch_semaphore_create(1); + assert(dsema); + + dispatch_apply(LAPS, dispatch_get_concurrent_queue(0), ^(size_t idx __attribute__((unused))) { + dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER); + total++; + dispatch_semaphore_signal(dsema); + }); + + dispatch_release(dsema); + + test_long("count", total, LAPS); + test_stop(); + + return 0; +} diff --git a/testing/dispatch_starfish.c b/testing/dispatch_starfish.c new file mode 100644 index 0000000..fb1f709 --- /dev/null +++ b/testing/dispatch_starfish.c @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +#if TARGET_OS_EMBEDDED +#define COUNT 300ul +#define LAPS 10ul +#else +#define COUNT 1000ul +#define LAPS 10ul +#endif + +static dispatch_queue_t queues[COUNT]; +static size_t lap_count_down = LAPS; +static size_t count_down; +static uint64_t start; +static mach_timebase_info_data_t tbi; + +static void do_test(void); + +static void +collect(void *context __attribute__((unused))) +{ + uint64_t delta; + long double math; + size_t i; + + if (--count_down) { + return; + } + + delta = mach_absolute_time() - start; + delta *= tbi.numer; + delta /= tbi.denom; + math = delta; + math /= COUNT * COUNT * 2ul + COUNT * 2ul; + + printf("lap: %ld\n", lap_count_down); + printf("count: %lu\n", COUNT); + printf("delta: %llu ns\n", delta); + printf("math: %Lf ns / lap\n", math); + + for (i = 0; i < COUNT; i++) { + dispatch_release(queues[i]); + } + + // our malloc could be a lot better, + // this result is really a malloc torture test + test_long_less_than("Latency" , (unsigned long)math, 1000); + + if (--lap_count_down) { + return do_test(); + } + + // give the threads some time to settle before test_stop() runs "leaks" + // ...also note, this is a total cheat. dispatch_after lets this + // thread go idle, so dispatch cleans up the continuations cache. + // Doign the "old style" sleep left that stuff around and leaks + // took a LONG TIME to complete. Long enough that the test harness + // decided to kill us. + dispatch_after_f(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), NULL, test_stop_after_delay); +} + +static void +pong(void *context) +{ + dispatch_queue_t this_q = context; + size_t replies = (size_t)dispatch_get_context(this_q); + + dispatch_set_context(this_q, (void *)--replies); + if (!replies) { + //printf("collect from: %s\n", dispatch_queue_get_label(dispatch_get_current_queue())); + dispatch_async_f(dispatch_get_main_queue(), NULL, collect); + } +} + +static void +ping(void *context) +{ + dispatch_queue_t reply_q = context; + + dispatch_async_f(reply_q, reply_q, pong); +} + +static void +start_node(void *context) +{ + dispatch_queue_t this_q = context; + size_t i; + + dispatch_set_context(this_q, (void *)COUNT); + + for (i = 0; i < COUNT; i++) { + dispatch_async_f(queues[i], this_q, ping); + } +} + +void +do_test(void) +{ + size_t i; + kern_return_t kr; + + count_down = COUNT; + + kr = mach_timebase_info(&tbi); + assert(kr == 0); + + start = mach_absolute_time(); + + for (i = 0; i < COUNT; i++) { + char buf[1000]; + snprintf(buf, sizeof(buf), "com.example.starfish-node#%ld", i); + queues[i] = dispatch_queue_create(buf, NULL); + dispatch_suspend(queues[i]); + } + + for (i = 0; i < COUNT; i++) { + dispatch_async_f(queues[i], queues[i], start_node); + } + + for (i = 0; i < COUNT; i++) { + dispatch_resume(queues[i]); + } +} + +int +main(void) +{ + test_start("Dispatch Starfish"); + + do_test(); + + dispatch_main(); +} diff --git a/testing/dispatch_suspend_timer.c b/testing/dispatch_suspend_timer.c new file mode 100644 index 0000000..f271f39 --- /dev/null +++ b/testing/dispatch_suspend_timer.c @@ -0,0 +1,76 @@ +#include +#include +#include + +#include + +#include "dispatch_test.h" + +dispatch_source_t tweedledee; +dispatch_source_t tweedledum; + +int main(void) +{ + test_start("Dispatch Suspend Timer"); + + dispatch_queue_t main_q = dispatch_get_main_queue(); + test_ptr("dispatch_get_main_queue", main_q, dispatch_get_current_queue()); + + __block int i = 0; + __block int j = 0; + + dispatch_source_attr_t attr = dispatch_source_attr_create(); + test_ptr_notnull("dispatch_source_attr_create", attr); + + dispatch_source_attr_set_finalizer(attr, ^(dispatch_source_t ds) { + test_ptr_notnull("finalizer ran", ds); + if (ds == tweedledum) test_stop(); + }); + + tweedledee = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + 1000000000ull, 0, attr, main_q, ^(dispatch_event_t ev) { + long err; + if (dispatch_event_get_error(ev, &err)) { + test_errno("dispatch_event_get_error", err, ECANCELED); + dispatch_release(dispatch_event_get_source(ev)); + } else { + fprintf(stderr, "%d\n", ++i); + if (i == 10) { + dispatch_cancel(dispatch_event_get_source(ev)); + } + } + }); + test_ptr_notnull("dispatch_source_timer_create", tweedledee); + dispatch_retain(tweedledee); + + tweedledum = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + 3000000000ull, 0, attr, main_q, ^(dispatch_event_t ev) { + long err; + if (dispatch_event_get_error(ev, &err)) { + test_errno("dispatch_event_get_error", err, ECANCELED); + dispatch_release(dispatch_event_get_source(ev)); + } else { + switch(++j) { + case 1: + fprintf(stderr, "suspending timer for 3 seconds\n"); + dispatch_suspend(tweedledee); + break; + case 2: + fprintf(stderr, "resuming timer\n"); + dispatch_resume(tweedledee); + dispatch_release(tweedledee); + break; + default: + dispatch_cancel(dispatch_event_get_source(ev)); + break; + } + } + }); + test_ptr_notnull("dispatch_source_timer_create", tweedledum); + + dispatch_release(attr); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_test.c b/testing/dispatch_test.c new file mode 100644 index 0000000..82b404f --- /dev/null +++ b/testing/dispatch_test.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +#define _test_print(_file, _line, _desc, \ + _expr, _fmt1, _val1, _fmt2, _val2) do { \ + const char* _exprstr = _expr ? "PASS" : "FAIL"; \ + char _linestr[BUFSIZ]; \ + if (!_expr) { \ + snprintf(_linestr, sizeof(_linestr), \ + " (%s:%ld)", _file, _line); \ + } else { \ + _linestr[0] = 0; \ + } \ + if (_fmt2 == 0) { \ + printf("\tValue: " _fmt1 "\n" \ + "[%s] %s%s\n", \ + _val1, \ + _exprstr, \ + _desc, \ + _linestr); \ + } else { \ + printf("\tActual: " _fmt1 "\n" \ + "\tExpected: " _fmt2 "\n" \ + "[%s] %s%s\n", \ + _val1, \ + _val2, \ + _exprstr, \ + _desc, \ + _linestr); \ + } \ + if (!_expr) { \ + printf("\t%s:%ld\n", _file, _line); \ + } \ + fflush(stdout); \ +} while (0); + +void +test_start(const char* desc) { + printf("\n==================================================\n"); + printf("[TEST] %s\n", desc); + printf("[PID] %d\n", getpid()); + printf("==================================================\n\n"); + usleep(100000); // give 'gdb --waitfor=' a chance to find this proc +} + +#define test_ptr_null(a,b) _test_ptr_null(__FILE__, __LINE__, a, b) +void +_test_ptr_null(const char* file, long line, const char* desc, const void* ptr) { + _test_print(file, line, desc, + (ptr == NULL), "%p", ptr, "%p", (void*)0); +} + +#define test_ptr_notnull(a,b) _test_ptr_notnull(__FILE__, __LINE__, a, b) +void +_test_ptr_notnull(const char* file, long line, const char* desc, const void* ptr) { + _test_print(file, line, desc, + (ptr != NULL), "%p", ptr, "%p", ptr ?: (void*)~0); +} + +#define test_ptr(a,b,c) _test_ptr(__FILE__, __LINE__, a, b, c) +void +_test_ptr(const char* file, long line, const char* desc, const void* actual, const void* expected) { + _test_print(file, line, desc, + (actual == expected), "%p", actual, "%p", expected); +} + +#define test_long(a,b,c) _test_long(__FILE__, __LINE__, a, b, c) +void +_test_long(const char* file, long line, const char* desc, long actual, long expected) { + _test_print(file, line, desc, + (actual == expected), "%ld", actual, "%ld", expected); +} + +#define test_long_less_than(a, b, c) _test_long_less_than(__FILE__, __LINE__, a, b, c) +void +_test_long_less_than(const char* file, long line, const char* desc, long actual, long expected_max) { + _test_print(file, line, desc, (actual < expected_max), "%ld", actual, "<%ld", expected_max); +} + +#define test_double_less_than(d, v, m) _test_double_less_than(__FILE__, __LINE__, d, v, m) +void +_test_double_less_than(const char* file, long line, const char* desc, double val, double max_expected) { + _test_print(file, line, desc, (val < max_expected), "%f", val, "<%f", max_expected); +} + +#define test_double_less_than_or_equal(d, v, m) _test_double_less_than(__FILE__, __LINE__, d, v, m) +void +_test_double_less_than_or_equal(const char* file, long line, const char* desc, double val, double max_expected) { + _test_print(file, line, desc, (val <= max_expected), "%f", val, "<%f", max_expected); +} + +#define test_errno(a,b,c) _test_errno(__FILE__, __LINE__, a, b, c) +void +_test_errno(const char* file, long line, const char* desc, long actual, long expected) { + char* actual_str; + char* expected_str; + asprintf(&actual_str, "%ld\t%s", actual, actual ? strerror(actual) : ""); + asprintf(&expected_str, "%ld\t%s", expected, expected ? strerror(expected) : ""); + _test_print(file, line, desc, + (actual == expected), "%s", actual_str, "%s", expected_str); + free(actual_str); + free(expected_str); +} + +#include + +extern char **environ; + +void +test_stop(void) { + test_stop_after_delay((void *)(intptr_t)0); +} + +void +test_stop_after_delay(void *delay) { + int res; + pid_t pid; + char pidstr[10]; + + if (delay != NULL) { + sleep((int)(intptr_t)delay); + } + + if (getenv("NOLEAKS")) _exit(EXIT_SUCCESS); + + /* leaks doesn't work against debug variant malloc */ + if (getenv("DYLD_IMAGE_SUFFIX")) _exit(EXIT_SUCCESS); + + snprintf(pidstr, sizeof(pidstr), "%d", getpid()); + char* args[] = { "./leaks-wrapper", pidstr, NULL }; + res = posix_spawnp(&pid, args[0], NULL, NULL, args, environ); + if (res == 0 && pid > 0) { + int status; + waitpid(pid, &status, 0); + test_long("Leaks", status, 0); + } else { + perror(args[0]); + } + + _exit(EXIT_SUCCESS); +} diff --git a/testing/dispatch_test.h b/testing/dispatch_test.h new file mode 100644 index 0000000..59547f4 --- /dev/null +++ b/testing/dispatch_test.h @@ -0,0 +1,33 @@ +#include + +__BEGIN_DECLS + +void test_start(const char* desc); +void test_stop(void); +void test_stop_after_delay(void *delay); + +void _test_ptr_null(const char* file, long line, const char* desc, const void* ptr); +#define test_ptr_null(a,b) _test_ptr_null(__FILE__, __LINE__, a, b) + +void _test_ptr_notnull(const char* file, long line, const char* desc, const void* ptr); +#define test_ptr_notnull(a,b) _test_ptr_notnull(__FILE__, __LINE__, a, b) + +void _test_ptr(const char* file, long line, const char* desc, const void* actual, const void* expected); +#define test_ptr(a,b,c) _test_ptr(__FILE__, __LINE__, a, b, c) + +void _test_long(const char* file, long line, const char* desc, long actual, long expected); +#define test_long(a,b,c) _test_long(__FILE__, __LINE__, a, b, c) + +void _test_long_less_than(const char* file, long line, const char* desc, long actual, long max_expected); +#define test_long_less_than(a,b,c) _test_long_less_than(__FILE__, __LINE__, a, b, c) + +void _test_double_less_than_or_equal(const char* file, long line, const char* desc, double val, double max_expected); +#define test_double_less_than_or_equal(d, v, m) _test_double_less_than(__FILE__, __LINE__, d, v, m) + +void _test_double_less_than(const char* file, long line, const char* desc, double val, double max_expected); +#define test_double_less_than(d, v, m) _test_double_less_than(__FILE__, __LINE__, d, v, m) + +void _test_errno(const char* file, long line, const char* desc, long actual, long expected); +#define test_errno(a,b,c) _test_errno(__FILE__, __LINE__, a, b, c) + +__END_DECLS diff --git a/testing/dispatch_test_sync_on_main.c b/testing/dispatch_test_sync_on_main.c new file mode 100644 index 0000000..6adba96 --- /dev/null +++ b/testing/dispatch_test_sync_on_main.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include +#include + +int global_count; + +void +main_work(void* ctxt) +{ + if (global_count == 20) { + exit(0); + } + uint64_t time = random() % NSEC_PER_SEC; + printf("Firing timer on main %d\n", ++global_count); + dispatch_after_f(dispatch_time(0, time), dispatch_get_main_queue(), NULL, main_work); +} + + +int main(void) { + global_count = 0; + + dispatch_queue_t dq = dispatch_queue_create("foo.bar", NULL); + dispatch_async(dq, ^{ + + dispatch_async_f(dispatch_get_main_queue(), NULL, main_work); + + int i; + for (i=0; i<5; ++i) { + dispatch_sync(dispatch_get_main_queue(), ^{ + printf("Calling sync %d\n", i); + assert(pthread_main_np() == 1); + if (i==4) { + global_count = 20; + } + }); + } + }); + + //dispatch_main(); + CFRunLoopRun(); + return 0; +} diff --git a/testing/dispatch_timer.c b/testing/dispatch_timer.c new file mode 100644 index 0000000..ddda1a9 --- /dev/null +++ b/testing/dispatch_timer.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +int main(void) +{ + test_start("Dispatch Source Timer"); + + dispatch_queue_t main_q = dispatch_get_main_queue(); + test_ptr("dispatch_get_main_queue", main_q, dispatch_get_current_queue()); + + uint64_t j; + + // create several timers and release them. + for (j = 1; j <= 5; ++j) + { + dispatch_source_t s = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + (uint64_t)j * (uint64_t)1000000000ull, 0, NULL, dispatch_get_concurrent_queue(0), + ^(dispatch_event_t ev) { + if (!dispatch_event_get_error(ev, NULL)) { + fprintf(stderr, "timer[%lld]\n", j); + dispatch_release(dispatch_event_get_source(ev)); + } + }); + test_ptr_notnull("dispatch_source_timer_create", s); + } + + dispatch_source_attr_t attr = dispatch_source_attr_create(); + dispatch_source_attr_set_finalizer(attr, ^(dispatch_source_t ds) { + test_ptr_notnull("finalizer ran", ds); + test_stop(); + }); + + __block int i = 0; + + dispatch_source_t s = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + 1000000000ull, + 0, + attr, + main_q, + ^(dispatch_event_t ev) { + long err; + if (dispatch_event_get_error(ev, &err)) { + test_errno("dispatch_event_get_error", err, ECANCELED); + dispatch_release(dispatch_event_get_source(ev)); + } else { + fprintf(stderr, "%d\n", ++i); + if (i >= 3) { + dispatch_cancel(dispatch_event_get_source(ev)); + } + } + }); + test_ptr_notnull("dispatch_source_timer_create", s); + + dispatch_release(attr); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_timer_bit31.c b/testing/dispatch_timer_bit31.c new file mode 100644 index 0000000..131c8c0 --- /dev/null +++ b/testing/dispatch_timer_bit31.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +int main(void) +{ + test_start("Dispatch Source Timer, bit 31"); + + dispatch_queue_t main_q = dispatch_get_main_queue(); + test_ptr("dispatch_get_main_queue", main_q, dispatch_get_current_queue()); + + __block int i = 0; + struct timeval start_time; + + gettimeofday(&start_time, NULL); + dispatch_source_attr_t attr = dispatch_source_attr_create(); + dispatch_source_attr_set_finalizer(attr, ^(dispatch_source_t ds) { + struct timeval end_time; + gettimeofday(&end_time, NULL); + test_ptr_notnull("finalizer ran", ds); + // XXX: check, s/b 2.0799... seconds, which is <4 seconds + // when it could end on a bad boundry. + test_long_less_than("needs to finish faster than 4 seconds", end_time.tv_sec - start_time.tv_sec, 4); + // And it has to take at least two seconds... + test_long_less_than("can't finish faster than 2 seconds", 1, end_time.tv_sec - start_time.tv_sec); + test_stop(); + }); + + dispatch_source_t s = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + 0x80000000ull, + 0, + attr, + main_q, + ^(dispatch_event_t ev) { + long err; + if (dispatch_event_get_error(ev, &err)) { + test_errno("dispatch_event_get_error", err, ECANCELED); + dispatch_release(dispatch_event_get_source(ev)); + } else { + fprintf(stderr, "%d\n", ++i); + dispatch_cancel(dispatch_event_get_source(ev)); + } + }); + test_ptr_notnull("dispatch_source_timer_create", s); + + dispatch_release(attr); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_timer_bit63.c b/testing/dispatch_timer_bit63.c new file mode 100644 index 0000000..6630794 --- /dev/null +++ b/testing/dispatch_timer_bit63.c @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +int main(void) +{ + test_start("Dispatch Source Timer, bit 63"); + + //uint64_t interval = 0xffffffffffffffffull; + uint64_t interval = 0x8000000000000001ull; + + dispatch_queue_t mainq = dispatch_get_main_queue(); + + __block int i = 0; + struct timeval start_time; + + gettimeofday(&start_time, NULL); + + dispatch_source_t ds; + ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, mainq); + assert(ds); + dispatch_source_set_event_handler(ds, ^{ + assert(i < 1); + printf("%d\n", i++); + }); + dispatch_source_set_timer(ds, DISPATCH_TIME_NOW, interval, 0); + dispatch_resume(ds); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + test_stop(); + }); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_timer_oneshot.c b/testing/dispatch_timer_oneshot.c new file mode 100644 index 0000000..9860601 --- /dev/null +++ b/testing/dispatch_timer_oneshot.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +static void +oneshot(void* context __attribute__((unused)), dispatch_event_t de) +{ + dispatch_source_t ds = dispatch_event_get_source(de); + test_ptr_notnull("dispatch_event_get_source", ds); + + if (!dispatch_event_get_error(de, NULL)) { + long canceled = dispatch_testcancel(ds); + test_long("dispatch_testcancel", canceled, 0); + + dispatch_release(ds); + test_stop(); + } +} + +int +main(void) +{ + test_start("Dispatch Timer One-Shot"); + + dispatch_source_t s; + + s = dispatch_source_timer_create_f(DISPATCH_TIMER_ONESHOT, + (uint64_t)1000000000ull, // 1s + 0, + NULL, + dispatch_get_concurrent_queue(0), + NULL, + &oneshot); + + dispatch_main(); + + return 0; +} diff --git a/testing/dispatch_timer_set_time.c b/testing/dispatch_timer_set_time.c new file mode 100644 index 0000000..189db33 --- /dev/null +++ b/testing/dispatch_timer_set_time.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include + +#include + +#include "dispatch_test.h" + +int main(void) +{ + test_start("Dispatch Update Timer"); + + dispatch_queue_t main_q = dispatch_get_main_queue(); + test_ptr("dispatch_get_main_queue", main_q, dispatch_get_current_queue()); + + __block int i = 0; + struct timeval start_time; + + gettimeofday(&start_time, NULL); + dispatch_source_attr_t attr = dispatch_source_attr_create(); + dispatch_source_attr_set_finalizer(attr, ^(dispatch_source_t ds) { + struct timeval end_time; + gettimeofday(&end_time, NULL); + // Make sure we actually managed to adjust the interval + // duration. Seven one second ticks would blow past + // this. + test_long_less_than("total duration", end_time.tv_sec - start_time.tv_sec, 3); + test_ptr_notnull("finalizer ran", ds); + test_stop(); + }); + + dispatch_source_t s = dispatch_source_timer_create(DISPATCH_TIMER_INTERVAL, + 1000000000ull, + 0, + attr, + main_q, + ^(dispatch_event_t ev) { + long err; + if (dispatch_event_get_error(ev, &err)) { + test_errno("dispatch_event_get_error", err, ECANCELED); + dispatch_release(dispatch_event_get_source(ev)); + } else { + fprintf(stderr, "%d\n", ++i); + if (i >= 7) { + dispatch_cancel(dispatch_event_get_source(ev)); + } else if (i == 1) { + dispatch_source_timer_set_time(dispatch_event_get_source(ev), 100, 0); + } + } + }); + test_ptr_notnull("dispatch_source_timer_create", s); + + dispatch_release(attr); + + dispatch_main(); + + return 0; +} diff --git a/testing/fast_apply_bench.c b/testing/fast_apply_bench.c new file mode 100644 index 0000000..599845e --- /dev/null +++ b/testing/fast_apply_bench.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include +#include + +static inline uint64_t +rdtsc(void) +{ + uint32_t lo, hi; + + asm volatile("rdtsc" : "=a" (lo), "=d" (hi)); + + return (uint64_t)hi << 32 | lo; +} + +__attribute__((noinline)) void +apply_p(void (^b)(size_t), size_t offset, size_t count) +{ + /* This would feed through to the existing dispatch_apply() */ + abort(); +} + +/* a dynamically variable to eventually be added to the kernel/user 'commpage' */ +size_t total_active_cpus = 8; + +__attribute__((noinline)) void +apply(void (^b)(size_t), size_t offset, size_t count) +{ + const size_t too_long = 100000; /* 100 us */ + const size_t laps = 16; + uint64_t delta, tmp, now; + size_t i; + + if (total_active_cpus == 1) { + for (i = 0; i < count; i++) { + b(offset + i); + } + return; + } + + now = mach_absolute_time(); + + for (i = 0; i < count; i++) { + b(offset + i); + + if (i % laps) { + continue; + } + + tmp = mach_absolute_time(); + delta = tmp - now; + now = tmp; + + if (delta > (too_long * laps) || (i == 0 && delta > too_long)) { + apply_p(b, offset + i + 1, count - (i + 1)); + return; + } + } +} + +int +main(void) +{ + void (^b)(size_t) = ^(size_t index) { + asm volatile(""); /* defeat compiler optimizations */ + }; + const size_t laps = 10000000; + mach_timebase_info_data_t tbi; + kern_return_t kr; + long double dd; + uint64_t s, e; + size_t i; + + kr = mach_timebase_info(&tbi); + assert(kr == 0); + assert(tbi.numer == tbi.denom); /* This will fail on PowerPC. */ + + s = mach_absolute_time(); + for (i = 0; i < laps; i++) { + b(i); + } + e = mach_absolute_time(); + dd = e - s; + dd /= laps; + printf("direct:\t%Lf ns\n", dd); + + s = mach_absolute_time(); + apply(b, 0, laps); + e = mach_absolute_time(); + dd = e - s; + dd /= laps; + printf("apply:\t%Lf ns\n", dd); + + return 0; +} diff --git a/testing/fd_stress.c b/testing/fd_stress.c new file mode 100644 index 0000000..fb2cd5a --- /dev/null +++ b/testing/fd_stress.c @@ -0,0 +1,457 @@ +/* + * fd_stress.c + * + * Stress test for dispatch read and write sources. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline size_t max(size_t a, size_t b) { + return (a > b) ? a : b; +} + +static inline size_t min(size_t a, size_t b) { + return (a < b) ? a : b; +} + +int debug = 0; + +#define DEBUG(...) do { \ + if (debug) fprintf(stderr, __VA_ARGS__); \ + } while(0); + +#define assert_errno(str, expr) do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: %s\n", (str), strerror(errno)); \ + exit(1); \ + } } while(0); + +#define assert_gai_errno(str, expr) do { \ + if (!(expr)) { \ + fprintf(stderr, "%s: %s\n", (str), gai_strerror(errno)); \ + exit(1); \ + } } while(0); + + +/* sock_context + * + * Context structure used by the reader and writer queues. + * + * Writers begin by generating a random length and writing it to the descriptor. + * The write buffer is filled with a random byte value and written until empty + * or until the total length is reached. The write buffer is refilled with more + * random data when empty. Each write updates an MD5 digest which is written to + * the descriptor once the total length is reached. + * + * Readers begin by reading the total length of data. The read buffer is filled + * and an MD5 digest is computed on the bytes as they are received. Once the + * total length of data has be read, an MD5 digest is read from the descriptor + * and compared with the computed value. + */ +struct sock_context { + enum { + LENGTH, + DATA, + CKSUM, + DONE, + } state; + char label[64]; + uint32_t len; + off_t offset; + char buf[8192]; + size_t buflen; + CC_MD5_CTX md5ctx; + char md5[CC_MD5_DIGEST_LENGTH]; +}; + +dispatch_source_t +create_writer(int wfd, dispatch_block_t completion) +{ + dispatch_source_t ds; + struct sock_context *ctx = calloc(1, sizeof(struct sock_context)); + assert(ctx); + + snprintf(ctx->label, sizeof(ctx->label), "writer.fd.%d", wfd); + dispatch_queue_t queue = dispatch_queue_create(ctx->label, 0); + + ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, wfd, 0, queue); + assert(ds); + dispatch_release(queue); + + uint32_t len; + do { + len = (arc4random() & 0x7FFF); + } while (len == 0); + ctx->state = LENGTH; + CC_MD5_Init(&ctx->md5ctx); + ctx->len = len; + ctx->buflen = sizeof(len); + len = htonl(len); + memcpy(ctx->buf, &len, ctx->buflen); + DEBUG("%s: LENGTH %d\n", ctx->label, ctx->len); + + dispatch_source_set_event_handler(ds, ^{ + DEBUG("%s: available %ld\n", ctx->label, dispatch_source_get_data(ds)); + ssize_t res; + size_t wrsz = min(ctx->len, ctx->buflen); + res = write(wfd, &ctx->buf[ctx->offset], wrsz); + DEBUG("%s: write(%d, %p, %ld): %ld\n", ctx->label, wfd, &ctx->buf[ctx->offset], wrsz, res); + if (res > 0) { + if (ctx->state == DATA) { + CC_MD5_Update(&ctx->md5ctx, &ctx->buf[ctx->offset], res); + ctx->len -= res; + } + ctx->offset += res; + ctx->buflen -= res; + assert(ctx->offset >= 0); + assert(ctx->len >= 0); + assert(ctx->buflen >= 0); + if (ctx->buflen == 0 || ctx->len == 0) { + if (ctx->state == LENGTH) { + // finished writing length, move on to data. + ctx->state = DATA; + ctx->buflen = sizeof(ctx->buf); + char pattern = arc4random() & 0xFF; + memset(ctx->buf, pattern, ctx->buflen); + } else if (ctx->state == DATA && ctx->len == 0) { + // finished writing data, move on to cksum. + ctx->state = CKSUM; + ctx->len = sizeof(ctx->md5); + ctx->buflen = sizeof(ctx->md5); + CC_MD5_Final(ctx->md5, &ctx->md5ctx); + memcpy(ctx->buf, ctx->md5, ctx->buflen); + } else if (ctx->state == DATA) { + ctx->buflen = sizeof(ctx->buf); + char pattern = arc4random() & 0xFF; + memset(ctx->buf, pattern, ctx->buflen); + } else if (ctx->state == CKSUM) { + ctx->state = DONE; + dispatch_source_cancel(ds); + } else { + assert(0); + } + ctx->offset = 0; + } + } else if (res == 0) { + assert(ctx->state == DONE); + assert(0); + } else if (res == -1 && errno == EAGAIN) { + DEBUG("%s: EAGAIN\n", ctx->label); + } else { + assert_errno("write", res >= 0); + } + }); + dispatch_source_set_cancel_handler(ds, ^{ + DEBUG("%s: close(%d)\n", ctx->label, wfd); + int res = close(wfd); + assert_errno("close", res == 0); + completion(); + dispatch_release(ds); + free(ctx); + }); + dispatch_resume(ds); + return ds; +} + +dispatch_source_t +create_reader(int rfd, dispatch_block_t completion) +{ + dispatch_source_t ds; + struct sock_context *ctx = calloc(1, sizeof(struct sock_context)); + assert(ctx); + + snprintf(ctx->label, sizeof(ctx->label), "reader.fd.%d", rfd); + dispatch_queue_t queue = dispatch_queue_create(ctx->label, 0); + + ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, rfd, 0, queue); + assert(ds); + dispatch_release(queue); + + ctx->state = LENGTH; + ctx->len = sizeof(ctx->len); + ctx->buflen = sizeof(ctx->len); + CC_MD5_Init(&ctx->md5ctx); + + dispatch_source_set_event_handler(ds, ^{ + DEBUG("%s: available %ld\n", ctx->label, dispatch_source_get_data(ds)); + ssize_t res; + size_t rdsz = min(ctx->len, ctx->buflen); + res = read(rfd, &ctx->buf[ctx->offset], rdsz); + DEBUG("%s: read(%d,%p,%ld): %ld\n", ctx->label, rfd, &ctx->buf[ctx->offset], rdsz, res); + + // log unexpected data lengths... + long expected = dispatch_source_get_data(ds); + long actual = res; + if (actual >= 0 && (actual != expected && actual != rdsz)) { + fprintf(stderr, "%s: expected %ld, actual %ld (rdsz = %ld)\n", ctx->label, expected, actual, rdsz); + } + + if (res > 0) { + if (ctx->state == DATA) { + CC_MD5_Update(&ctx->md5ctx, &ctx->buf[ctx->offset], res); + ctx->len -= res; + } + ctx->offset += res; + ctx->buflen -= res; + if (ctx->buflen == 0 || ctx->len == 0) { + if (ctx->state == LENGTH) { + // buffer is full, interpret as uint32_t + memcpy(&ctx->len, ctx->buf, sizeof(ctx->len)); + ctx->len = ntohl(ctx->len); + ctx->buflen = sizeof(ctx->buf); + ctx->state = DATA; + } else if (ctx->state == DATA && ctx->len == 0) { + CC_MD5_Final(ctx->md5, &ctx->md5ctx); + ctx->state = CKSUM; + ctx->len = CC_MD5_DIGEST_LENGTH; + ctx->buflen = ctx->len; + } else if (ctx->state == DATA) { + ctx->buflen = sizeof(ctx->buf); + } else if (ctx->state == CKSUM) { + ctx->state = DONE; + res = memcmp(ctx->buf, ctx->md5, sizeof(ctx->md5)); + if (res != 0) { + DEBUG("%s: MD5 FAILURE\n", ctx->label); + } + assert(res == 0); + } + ctx->offset = 0; + } + } else if (res == 0) { + assert(ctx->state == DONE); + DEBUG("%s: EOF\n", ctx->label); + dispatch_source_cancel(ds); + } else { + assert_errno("read", res >= 0); + } + }); + dispatch_source_set_cancel_handler(ds, ^{ + DEBUG("%s: close(%d)\n", ctx->label, rfd); + int res = close(rfd); + assert_errno("close", res == 0); + completion(); + dispatch_release(ds); + free(ctx); + }); + dispatch_resume(ds); + return ds; +} + +void +set_nonblock(int fd) +{ + int res, flags; + flags = fcntl(fd, F_GETFL); + + flags |= O_NONBLOCK; + res = fcntl(fd, F_SETFL, flags); + assert_errno("fcntl(F_SETFL,O_NONBLOCK)", res == 0); +} + +void +create_fifo(int *rfd, int *wfd) +{ + int res; + char *name; + + char path[MAXPATHLEN]; + strlcpy(path, "/tmp/fd_stress.fifo.XXXXXX", sizeof(path)); + name = mktemp(path); + + res = unlink(name); + + res = mkfifo(name, 0700); + assert_errno(name, res == 0); + + *rfd = open(name, O_RDONLY | O_NONBLOCK); + assert_errno(name, *rfd >= 0); + + *wfd = open(name, O_WRONLY | O_NONBLOCK); + assert_errno(name, *wfd >= 0); +} + +void +create_pipe(int *rfd, int *wfd) +{ + int res; + int fildes[2]; + + res = pipe(fildes); + assert_errno("pipe", res == 0); + + *rfd = fildes[0]; + *wfd = fildes[1]; + + set_nonblock(*rfd); + set_nonblock(*wfd); +} + +void +create_server_socket(int *rfd, struct sockaddr_in *sa) +{ + int res; + int value; + socklen_t salen = sizeof(*sa); + + memset(sa, 0, salen); + sa->sin_len = salen; + sa->sin_family = AF_INET; + sa->sin_port = htons(12345); + sa->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + *rfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + assert_errno("socket", *rfd >= 0); + + value = 1; + res = setsockopt(*rfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)); + assert_errno("setsockopt(SO_REUSEADDR)", res == 0); + + value = 1; + res = setsockopt(*rfd, SOL_SOCKET, SO_REUSEPORT, &value, sizeof(value)); + assert_errno("setsockopt(SO_REUSEPORT)", res == 0); + + res = bind(*rfd, (const struct sockaddr *)sa, salen); + assert_errno("bind", res == 0); + + res = listen(*rfd, 128); + assert_errno("listen", res == 0); +} + +void +create_client_socket(int *wfd, const struct sockaddr_in *sa) +{ + int res; + + *wfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + assert_errno("socket", *wfd >= 0); + + set_nonblock(*wfd); + + res = connect(*wfd, (const struct sockaddr *)sa, sa->sin_len); + assert_errno("connect", res == 0 || errno == EINPROGRESS); +} + +extern int optind; + +void +usage(void) +{ + fprintf(stderr, "usage: fd_stress [-d] iterations width\n"); + exit(1); +} + +int +main(int argc, char* argv[]) +{ + int serverfd; + struct sockaddr_in sa; + create_server_socket(&serverfd, &sa); + + int ch; + + while ((ch = getopt(argc, argv, "d")) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case '?': + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + + if (argc != 2) { + usage(); + } + + size_t iterations = strtol(argv[0], NULL, 10); + size_t width = strtol(argv[1], NULL, 10); + + if (iterations == 0 || width == 0) { + usage(); + } + + fprintf(stdout, "pid %d\n", getpid()); + + dispatch_group_t group; + group = dispatch_group_create(); + assert(group); + +#if 0 + dispatch_queue_t queue = dispatch_queue_create("server", NULL); + + dispatch_source_t ds; + ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, serverfd, 0, queue); + assert(ds); + dispatch_source_set_event_handler(ds, ^{ + int res; + int fd; + struct sockaddr peer; + socklen_t peerlen; + + fd = accept(serverfd, &peer, &peerlen); + assert_errno("accept", fd >= 0); + + set_nonblock(fd); + + char host[NI_MAXHOST], serv[NI_MAXSERV]; + host[0] = 0; + serv[0] = 0; + res = getnameinfo(&peer, peerlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST|NI_NUMERICSERV); + DEBUG("ACCEPTED %d (%s:%s)\n", fd, host, serv); + + create_reader(fd, ^{ dispatch_group_leave(group); }); + }); + dispatch_resume(ds); +#endif + + size_t i; + for (i = 1; i < iterations; ++i) { + fprintf(stderr, "iteration %ld\n", i); + + size_t j; + for (j = 0; j < width; ++j) { + int rfd, wfd; + dispatch_group_enter(group); + create_pipe(&rfd, &wfd); + DEBUG("PIPE %d %d\n", rfd, wfd); + dispatch_source_t reader; + reader = create_reader(rfd, ^{ dispatch_group_leave(group); }); + create_writer(wfd, ^{}); + } + +#if 0 + int clientfd; + dispatch_group_enter(group); + create_client_socket(&clientfd, &sa); + DEBUG("CLIENT %d\n", clientfd); + create_writer(clientfd, ^{}); + + dispatch_group_enter(group); + create_fifo(&rfd, &wfd); + DEBUG("FIFO %d %d\n", rfd, wfd); + create_writer(wfd, ^{}); + create_reader(rfd, ^{ dispatch_group_leave(group); }); +#endif + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + } + fprintf(stdout, "pid %d\n", getpid()); + dispatch_main(); + + return 0; +} diff --git a/testing/float_parsing.c b/testing/float_parsing.c new file mode 100644 index 0000000..716bf50 --- /dev/null +++ b/testing/float_parsing.c @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXLAPS (512 * 1024) + +static void +test(size_t LAPS, char *nums) +{ + uint64_t concurrent_cycles; + uint64_t serial_cycles; + char **result_strings; + char *nums_off; + double *results; + size_t i; + + result_strings = calloc(1, sizeof(char *) * LAPS); + assert(result_strings); + + results = calloc(1, sizeof(double) * LAPS); + assert(results); + + printf("%zu random floats\n", LAPS); + + i = 0; + nums_off = nums; + do { + result_strings[i] = nums_off; + do { + nums_off++; + assert(*nums_off); + } while (*nums_off != '\n'); + i++; + nums_off++; + } while (i < LAPS); + + for (i = 0; i < LAPS; i++) { + assert(result_strings[i]); + } + + concurrent_cycles = dispatch_benchmark(10, ^{ + dispatch_apply(LAPS, dispatch_get_concurrent_queue(0), ^(size_t idx) { + results[idx] = strtod(result_strings[idx], NULL); + }); + }); + + for (i = 0; i < LAPS; i++) { + assert(results[i]); + } + + serial_cycles = dispatch_benchmark(10, ^{ + size_t k = 0; + do { + results[k] = strtod(result_strings[k], NULL); + k++; + } while (k < LAPS); + }); + + for (i = 0; i < LAPS; i++) { + assert(results[i]); + } + + printf( "\tserial cycles:\t%llu\n" + "\tapply() cycles:\t%llu\n" + "\tserial / concurrent: %.2Lf\n", + serial_cycles, concurrent_cycles, + (long double)serial_cycles / (long double)concurrent_cycles); + + free(result_strings); + free(results); +} + +int +main(void) +{ + char path[PATH_MAX] = "/tmp/random_floats_XXXXXX"; + struct stat sb; + double words[1000]; + char buf[1024]; + char *nums; + int fd, rfd; + size_t i, j; + ssize_t r; + + rfd = open("/dev/random", O_RDONLY); + assert(rfd != -1); + + fd = mkstemp(path); + assert(fd != -1); + + r = unlink(path); + assert(r != -1); + + i = 0; + do { + r = read(rfd, words, sizeof(words)); + assert(r == sizeof(words)); + for (j = 0; j < 1000; j++) { + if (isnormal(words[j])) { + r = write(fd, buf, snprintf(buf, sizeof(buf), "%.20e\n", words[j])); + assert(r != -1); + i++; + } + } + } while (i < MAXLAPS); + + r = close(rfd); + assert(r != -1); + + r = fstat(fd, &sb); + assert(r != -1); + + nums = mmap(NULL, sb.st_size, PROT_READ, MAP_FILE|MAP_SHARED, fd, 0); + assert(nums != MAP_FAILED); + + for (i = MAXLAPS; i > 0; i /= 2) { + test(i, nums); + } + + r = munmap(nums, sb.st_size); + assert(r != -1); + + r = close(fd); + assert(r != -1); + + return 0; +} diff --git a/testing/fork-join.c b/testing/fork-join.c new file mode 100644 index 0000000..d1b0e57 --- /dev/null +++ b/testing/fork-join.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include +#include + +int +main(void) +{ + long double nano_per_lap; + size_t i, cnt = 1000000; + dispatch_future_t *df; + uint64_t s, e; + + df = malloc(cnt * sizeof(df)); + assert(df); + + s = mach_absolute_time(); + + for (i = 0; i < cnt; i++) { + df[i] = dispatch_fork(dispatch_get_concurrent_queue(0), ^{ + }); + assert(df[i]); + } + + for (i = 0; i < cnt; i++) { + dispatch_join(df[i]); + } + + e = mach_absolute_time(); + + nano_per_lap = (e - s); + nano_per_lap /= cnt; + + printf("%Lf nanoseconds per lap\n", nano_per_lap); + + return 0; +} diff --git a/testing/func.c b/testing/func.c new file mode 100644 index 0000000..223fd84 --- /dev/null +++ b/testing/func.c @@ -0,0 +1,9 @@ +extern "C" { +void +func(void) +{ +} +#ifdef __BLOCKS__ +void (^block)(void) = ^{ }; +#endif +}; diff --git a/testing/harness.c b/testing/harness.c new file mode 100644 index 0000000..d0430c2 --- /dev/null +++ b/testing/harness.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +extern char **environ; + +int +main(int argc, char *argv[]) +{ + dispatch_source_t tmp_ds; + int res; + pid_t pid; + + if (argc < 2) { + fprintf(stderr, "usage: harness [...]\n"); + exit(1); + } + + posix_spawnattr_t attr; + res = posix_spawnattr_init(&attr); + assert(res == 0); + res = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED); + assert(res == 0); + + int i; + char** newargv = calloc(argc, sizeof(void*)); + for (i = 1; i < argc; ++i) { + newargv[i-1] = argv[i]; + } + newargv[i-1] = NULL; + + res = posix_spawnp(&pid, newargv[0], NULL, &attr, newargv, environ); + if (res) { + errno = res; + perror(newargv[0]); + exit(EXIT_FAILURE); + } + //fprintf(stderr, "pid = %d\n", pid); + assert(pid > 0); + + dispatch_queue_t main_q = dispatch_get_main_queue(); + + tmp_ds = dispatch_source_proc_create(pid, DISPATCH_PROC_EXIT, NULL, main_q, + ^(dispatch_event_t ev __attribute__((unused))) { + int status; + int res2 = waitpid(pid, &status, 0); + assert(res2 != -1); + //int passed = (WIFEXITED(status) && WEXITSTATUS(status) == 0); + test_long("Process exited", WEXITSTATUS(status) | WTERMSIG(status), 0); + exit(0); + }); + assert(tmp_ds); + + uint64_t timeout = 30LL * NSEC_PER_SEC; + + tmp_ds = dispatch_source_timer_create(DISPATCH_TIMER_ONESHOT, timeout, 0, NULL, main_q, + ^(dispatch_event_t ev __attribute__((unused))) { + kill(pid, SIGKILL); + fprintf(stderr, "Terminating unresponsive process (%0.1lfs)\n", (double)timeout/NSEC_PER_SEC); + }); + assert(tmp_ds); + + signal(SIGINT, SIG_IGN); + tmp_ds = dispatch_source_signal_create(SIGINT, NULL, main_q, + ^(dispatch_event_t ev __attribute__((unused))) { + fprintf(stderr, "Terminating process due to signal\n"); + kill(pid, SIGKILL); + }); + assert(tmp_ds); + + kill(pid, SIGCONT); + + dispatch_main(); + + return 0; +} diff --git a/testing/leaks-wrapper b/testing/leaks-wrapper new file mode 100755 index 0000000..6e9f9ed --- /dev/null +++ b/testing/leaks-wrapper @@ -0,0 +1,10 @@ +#!/bin/sh + +/usr/bin/leaks "$@" 2>&1 | tee "$@.leakslog" | grep -q " 0 leaks for 0 total leaked bytes" + +if [ $? -eq 0 ]; then + rm "$@.leakslog" + exit 0 +else + exit $? +fi diff --git a/testing/mach_server.c b/testing/mach_server.c new file mode 100644 index 0000000..24535ae --- /dev/null +++ b/testing/mach_server.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void +load(void) +{ + launch_data_t msg, config, dict, array, val; + + config = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + + val = launch_data_new_string("com.apple.test.mach-server"); + launch_data_dict_insert(config, val, LAUNCH_JOBKEY_LABEL); + + val = launch_data_new_bool(1); + dict = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + launch_data_dict_insert(dict, val, "com.apple.test.mach-server"); + launch_data_dict_insert(config, dict, LAUNCH_JOBKEY_MACHSERVICES); + + char path[PATH_MAX]; + uint32_t size = sizeof(path); + _NSGetExecutablePath(path, &size); + + array = launch_data_alloc(LAUNCH_DATA_ARRAY); + val = launch_data_new_string(path); + launch_data_array_set_index(array, val, 0); + val = launch_data_new_string("-launchd"); + launch_data_array_set_index(array, val, 1); + launch_data_dict_insert(config, array, LAUNCH_JOBKEY_PROGRAMARGUMENTS); + + + msg = launch_data_alloc(LAUNCH_DATA_DICTIONARY); + launch_data_dict_insert(msg, config, LAUNCH_KEY_SUBMITJOB); + fprintf(stderr, "loading launchd job com.apple.test.mach-server\n"); + msg = launch_msg(msg); + if (msg && launch_data_get_type(config) == LAUNCH_DATA_ERRNO) { + fprintf(stderr, "launch load failed: %s\n", strerror(launch_data_get_errno(msg))); + } else { + fprintf(stderr, "successful\n"); + } +} + +mach_port_t +checkin(void) +{ + launch_data_t config = NULL, checkin = NULL; + checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN); + config = launch_msg(checkin); + if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) + return MACH_PORT_NULL; + + launch_data_t svc; + svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES); + if (!svc) return MACH_PORT_NULL; + + svc = launch_data_dict_lookup(svc, "com.apple.test.mach-server"); + if (!svc) return MACH_PORT_NULL; + + mach_port_t mp = launch_data_get_machport(svc); + return mp; +} + + +#include + +void my_cf_callback(CFMachPortRef mp, void *msg, CFIndex size, void *info) +{ + char *payload = (char *)((uintptr_t)msg + sizeof(mach_msg_header_t)); + asl_log(NULL, NULL, ASL_LEVEL_NOTICE, "message received: %s %s", payload, (char *)info); +} + +DISPATCH_CFMACHPORT_CALLBACK_DECL(my_mig_callback, my_cf_callback); + +struct strmsg { + mach_msg_header_t header; + char payload[32]; + mach_msg_security_trailer_t trailer; +}; + +int +client(int argc, char* argv[]) +{ + mach_port_t mp; + kern_return_t kr; + struct strmsg msg; + + char *str = (argc > 1) ? argv[1] : ""; + + kr = bootstrap_look_up(bootstrap_port, "com.apple.test.mach-server", + &mp); + printf("lookup %s\n", !kr ? "successful" : mach_error_string(kr)); + + strlcpy(msg.payload, str, sizeof(msg.payload)); + msg.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND_ONCE); + msg.header.msgh_size = round_msg(sizeof(msg.header) + strlen(msg.payload) + 1); + msg.header.msgh_remote_port = mp; + msg.header.msgh_local_port = MACH_PORT_NULL; + msg.header.msgh_id = 0x12345678; + kr = mach_msg(&msg.header, MACH_SEND_MSG, msg.header.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + printf("client send %s\n", !kr ? "successful" : mach_error_string(kr)); +} + +int main(int argc, char* argv[]) +{ + if (argc > 1 && strcmp(argv[1], "-launchd") == 0) { + mach_port_t mp = checkin(); + printf("checkin %s\n", mp ? "successful" : "failed"); + if (mp) { + kern_return_t kr; + kr = mach_port_set_context(mach_task_self(), mp, (mach_vm_address_t)"phear"); + + dispatch_source_t mig = dispatch_source_mig_create(mp, 0 /* max msg size */, + NULL, dispatch_get_main_queue(), my_mig_callback); + dispatch_main(); + } + } else if (argc > 1) { + client(argc, argv); + } else { + // load the job + load(); + } + + return 0; +} + diff --git a/testing/mig/Makefile b/testing/mig/Makefile new file mode 100644 index 0000000..bd44a2f --- /dev/null +++ b/testing/mig/Makefile @@ -0,0 +1,17 @@ +CFLAGS = -g -Os -Wall -Wextra -Wshadow -Wmissing-prototypes -Wmissing-declarations -Werror + +all: client server + +hello_logger.h hello_loggerServer.c hello_loggerUser.c hello_loggerServer.h: hello_logger.defs + mig -sheader hello_loggerServer.h hello_logger.defs + +client.o: hello_logger.h + +server.o: hello_loggerServer.h + +client: hello_loggerUser.o client.o + +server: hello_loggerServer.o server.o + +clean: + -rm -f *.o client server hello_logger.h hello_loggerServer.c hello_loggerUser.c hello_loggerServer.h diff --git a/testing/mig/client.c b/testing/mig/client.c new file mode 100644 index 0000000..15fc515 --- /dev/null +++ b/testing/mig/client.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "hello_logger.h" + +int main(int argc, char *argv[]) +{ + kern_return_t kr; + mach_port_t mp; + + if (argc != 2) { + fprintf(stderr, "I need a string to send!\n"); + exit(EXIT_FAILURE); + } + + kr = bootstrap_look_up(bootstrap_port, HELLO_LOGGER_BOOTSTRAP_NAME, &mp); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "bootstrap_look_up(): %s\n", bootstrap_strerror(kr)); + exit(EXIT_FAILURE); + } + + kr = example(mp, argv[1]); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "test_hello_logger(): %s\n", mach_error_string(kr)); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/testing/mig/hello_logger.defs b/testing/mig/hello_logger.defs new file mode 100644 index 0000000..0eb8c38 --- /dev/null +++ b/testing/mig/hello_logger.defs @@ -0,0 +1,12 @@ +#include +#include +import "hello_logger_types.h"; + +type string_t = c_string[*:1024]; + +subsystem hello_logger 12345; +serverprefix do_; + +routine example( + test_port : mach_port_t; + some_string : string_t); diff --git a/testing/mig/hello_logger_types.h b/testing/mig/hello_logger_types.h new file mode 100644 index 0000000..a3bafb2 --- /dev/null +++ b/testing/mig/hello_logger_types.h @@ -0,0 +1,8 @@ +#ifndef _HELLO_WORLD_TYPES_H_ +#define _HELLO_WORLD_TYPES_H_ + +#define HELLO_LOGGER_BOOTSTRAP_NAME "com.example.hello_logger" + +typedef char *string_t; + +#endif diff --git a/testing/mig/server.c b/testing/mig/server.c new file mode 100644 index 0000000..fb1e19f --- /dev/null +++ b/testing/mig/server.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hello_logger.h" +#include "hello_loggerServer.h" + +static mach_port_t checkin_or_register(char *bname); + +int main(void) +{ + mach_port_t mp = checkin_or_register(HELLO_LOGGER_BOOTSTRAP_NAME); + dispatch_source_t ds = dispatch_source_mig_new(mp, do_hello_logger_subsystem.maxsize, hello_logger_server, NULL, NULL, NULL); + + assert(ds); + + dispatch_main(); + + exit(EXIT_SUCCESS); +} + +kern_return_t +do_example(mach_port_t test_port __attribute__((unused)), string_t somestring) +{ + fprintf(stdout, "%s\n", somestring); + + return KERN_SUCCESS; +} + +mach_port_t +checkin_or_register(char *bname) +{ + kern_return_t kr; + mach_port_t mp; + + /* If we're started by launchd or the old mach_init */ + kr = bootstrap_check_in(bootstrap_port, bname, &mp); + if (kr == KERN_SUCCESS) + return mp; + + abort(); +} diff --git a/testing/nsoperation.m b/testing/nsoperation.m new file mode 100644 index 0000000..bb5b065 --- /dev/null +++ b/testing/nsoperation.m @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +#include +#include + +#include "dispatch_test.h" + +@interface MYOperation : NSOperation +{ +} +@end + +@implementation MYOperation + +- (id) init +{ + self = [super init]; + return self; +} + +- (void)main +{ + test_stop(); +} + +@end + +int +main(void) +{ + test_start("NSOperation"); + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease]; + test_ptr_notnull("NSOperationQueue", queue); + + MYOperation *operation = [[MYOperation alloc] init]; + test_ptr_notnull("NSOperation", operation); + + [queue addOperation:operation]; + [operation release]; + + [[NSRunLoop mainRunLoop] run]; + + [pool release]; + + return 0; +} diff --git a/testing/queue_finalizer.c b/testing/queue_finalizer.c new file mode 100644 index 0000000..6411b9a --- /dev/null +++ b/testing/queue_finalizer.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include +#include +#include + +#include "dispatch_test.h" + +int main(void) { + long res; + + test_start("Dispatch Queue Finalizer"); + +#ifdef __LP64__ + void* ctxt_magic = (void*)((uintptr_t)arc4random() << 32 | arc4random()); +#else + void* ctxt_magic = (void*)arc4random(); +#endif + + dispatch_queue_attr_t attr = dispatch_queue_attr_create(); + test_ptr_notnull("dispatch_queue_attr_create", attr); + + __block long finalizer_ran = 0; + + res = dispatch_queue_attr_set_finalizer(attr, ^(dispatch_queue_t dq) { + void* ctxt = dispatch_queue_get_context(dq); + test_ptr("dispatch_queue_get_context", ctxt, ctxt_magic); + test_ptr_notnull("finalizer ran", dq); + test_stop(); + }); + test_long("dispatch_queue_attr_set_finalizer", res, 0); + + dispatch_queue_t q = dispatch_queue_create(NULL, attr); + test_ptr_notnull("dispatch_queue_new", q); + + dispatch_queue_set_context(q, ctxt_magic); + + dispatch_release(attr); + + dispatch_release(q); + + dispatch_main(); + + return 0; +} diff --git a/testing/slice_benchmarks.c b/testing/slice_benchmarks.c new file mode 100644 index 0000000..0f9c054 --- /dev/null +++ b/testing/slice_benchmarks.c @@ -0,0 +1,445 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// #include "../src/private.h" +#include + +// "normal" loop size +#define LOOP 100000 +#define SMALL_LOOP 1000 + +void report(const char *func, char *full_name, double x, unsigned long loops, char *unit) { + // XXX: make cols pretty & stuff + const char *prefix = "bench_"; + const int plen = strlen(prefix); + assert(!strncmp(func, prefix, plen)); + func += plen; + char *name; + asprintf(&name, "[%s] %s", func, full_name); + assert(name); + + x /= loops; + + if (!strcmp("mach", unit)) { + static mach_timebase_info_data_t mtb; + if (!mtb.denom) { + (void)mach_timebase_info(&mtb); + } + x = (x * mtb.numer) / mtb.denom; + unit = "ns"; + } + + printf("%-64s %13f%-2s\n", name, x, unit); + free(name); +} + +void bench_queue_mem_use(void) { + struct proc_taskinfo pti; + uint64_t target_size; + + // The 1st call eats a little memory that isn't accounted for + // until the 2nd call. Also the _first_ printf eats >1M, so + // if you insert some for debugging make sure it isn't the first! + proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &pti, sizeof(pti)); + proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &pti, sizeof(pti)); + target_size = pti.pti_virtual_size + 1024*1024; + int n; + + for(n = 0; target_size >= pti.pti_virtual_size; n++) { + dispatch_queue_t leak = dispatch_queue_create("to be deleted", NULL); + assert(leak); + proc_pidinfo(getpid(), PROC_PIDTASKINFO, 0, &pti, sizeof(pti)); + //printf("pti_virtual_size %qd; togo %qd, n %d\n", pti.pti_virtual_size, target_size - pti.pti_virtual_size, n); + } + + report(__FUNCTION__, "#queues to grow VSIZE 1Mbyte", n-1, 1, "x"); +} + +void bench_message_round_trip(void) { + dispatch_queue_t q1 = dispatch_queue_create("q1", NULL); + dispatch_queue_t q2 = dispatch_queue_create("q2", NULL); + uint64_t start = mach_absolute_time(); + + int i; + for(i = 0; i < LOOP; i++) { + // make sure we don't build up too much of a backlog + if (i && !(i & 0x3ff)) { + dispatch_sync(q2, ^{}); + } + dispatch_queue_retain(q2); + dispatch_async(q1, ^{ + dispatch_async(q2, ^{ + dispatch_queue_release(q2); + }); + }); + } + + // Make sure eveything has drained before we take the end timestamp + dispatch_sync(q1, ^{}); + dispatch_sync(q2, ^{}); + + uint64_t end = mach_absolute_time(); + report(__FUNCTION__, "round trip (async async - implicit copy)", (end - start), LOOP, "mach"); + dispatch_queue_release(q1); + dispatch_queue_release(q2); +} + +void bench_precopy_message_round_trip(void) { + dispatch_queue_t q1 = dispatch_queue_create("q1", NULL); + dispatch_queue_t q2 = dispatch_queue_create("q2", NULL); + assert(q1 && q2); + + unsigned long rc; + + dispatch_block_t b2 = Block_copy(^{ + }); + dispatch_block_t b1 = Block_copy(^{ + unsigned long rc = dispatch_async(q2, b2); + assert(!rc); + dispatch_queue_release(q2); + }); + dispatch_block_t be = Block_copy(^{}); + assert(b1 && b2); + uint64_t start = mach_absolute_time(); + + int i; + for(i = 0; i < LOOP; i++) { + // make sure we don't build up too much of a backlog + if (i && !(i & 0x3ff)) { + dispatch_sync(q2, be); + } + dispatch_queue_retain(q2); + rc = dispatch_async(q1, b1); + assert(!rc); + } + + // Make sure eveything has drained before we take the end timestamp + dispatch_sync(q1, be); + dispatch_sync(q2, be); + + uint64_t end = mach_absolute_time(); + report(__FUNCTION__, "round trip (a/a - precopy)", (end - start), LOOP, "mach"); + dispatch_queue_release(q1); + dispatch_queue_release(q2); +} + +void bench_message_round_type_syncasync(void) { + dispatch_queue_t q1 = dispatch_queue_create("q1", NULL); + dispatch_queue_t q2 = dispatch_queue_create("q2", NULL); + uint64_t start = mach_absolute_time(); + + int i; + for(i = 0; i < LOOP; i++) { + dispatch_queue_retain(q2); + dispatch_sync(q1, ^{ + dispatch_async(q2, ^{ + dispatch_queue_release(q2); + }); + }); + } + + // Make sure eveything has drained before we take the end timestamp + dispatch_sync(q1, ^{}); + dispatch_sync(q2, ^{}); + + uint64_t end = mach_absolute_time(); + report(__FUNCTION__, "round trip (s/a - implicit copy)", (end - start), LOOP, "mach"); + dispatch_queue_release(q1); + dispatch_queue_release(q2); +} + +void nothing_f(void *ignored) { +} + +void brt_f_q1(void *vq2) { + unsigned long rc = dispatch_async_f((dispatch_queue_t)vq2, NULL, nothing_f); + assert(!rc); +} + +void bench_message_round_trip_f(void) { + dispatch_queue_t q1 = dispatch_queue_create("q1", NULL); + dispatch_queue_t q2 = dispatch_queue_create("q2", NULL); + uint64_t start = mach_absolute_time(); + unsigned long rc; + + int i; + for(i = 0; i < LOOP; i++) { + // make sure we don't build up too much of a backlog + if (i && !(i & 0x3ff)) { + dispatch_sync_f(q2, NULL, nothing_f); + } + rc = dispatch_async_f(q1, q2, brt_f_q1); + assert(!rc); + } + + // Make sure eveything has drained before we take the end timestamp + dispatch_sync_f(q1, NULL, nothing_f); + dispatch_sync_f(q2, NULL, nothing_f); + + uint64_t end = mach_absolute_time(); + report(__FUNCTION__, "round trip (a/a - no blocks)", (end - start), LOOP, "mach"); + dispatch_queue_release(q1); + dispatch_queue_release(q2); +} + +void bench_message_round_type_syncasync_f(void) { +} + +struct baton { + // should extend to keep data on times for latency calc + int passes_left; + int at_q; + int baton_number; + + // Avoid false ache line shares. Big speed difference on a Mac Pro + char pad[128 - sizeof(int)*3]; +}; + +pthread_mutex_t kludge; +static int n_baton_kludge; + +void pass(dispatch_queue_t *q, struct baton *bat, const int n_queues, dispatch_queue_t complete_q) { + //fprintf(stderr, "bat#%d q#%d, passes left: %d\n", bat->baton_number, bat->at_q, bat->baton_number); + if (0 == --(bat->passes_left)) { + dispatch_queue_resume(complete_q); + // XXX: atomic + if (!__sync_sub_and_fetch(&n_baton_kludge, 1)) { + pthread_mutex_unlock(&kludge); + } + return; + } + bat->at_q = (bat->at_q + 1) % n_queues; + unsigned long rc = dispatch_async(q[bat->at_q], ^{ pass(q, bat, n_queues, complete_q); }); + assert(rc == 0); +} + +void bench_baton() { + const int n_queues = 128; + const int q_div_b = 4; + const int n_batons = n_queues / q_div_b; + assert(q_div_b * n_batons == n_queues); + n_baton_kludge = n_batons; + dispatch_queue_t *q; + dispatch_queue_t complete_q = dispatch_queue_create("completion q", NULL);; + char *q_labels[n_queues]; + int i; + unsigned long rc; + + // creting a queue ("C"), suspending it, blocking in a dispatch_sync, and + // having another queue resume C does not appear to ever unblock the + // dispatch_sync. XXX: make test case and file radar. (if it still + // works that way on recent builds, with dispatch inside libsystem, and + // such) + + + pthread_mutex_init(&kludge, NULL); + rc = pthread_mutex_trylock(&kludge); + assert(!rc); + q = alloca(n_queues * sizeof(dispatch_queue_t)); + + for(i = 0; i < n_queues; i++) { + asprintf(q_labels + i, "relay#%d (%s)", i, __FUNCTION__); + assert(q_labels[i]); + q[i] = dispatch_queue_create(q_labels[i], NULL); + assert(q[i]); + } + + uint64_t start_time = mach_absolute_time(); + + for(i = 0; i < n_queues; i += q_div_b) { + struct baton *bat = valloc(sizeof(struct baton)); + assert(bat); + bat->passes_left = SMALL_LOOP; + bat->at_q = i; + bat->baton_number = i / q_div_b; + dispatch_queue_suspend(complete_q); + rc = dispatch_async(q[i], ^{ + pass(q, bat, n_queues, complete_q); + }); + assert(rc == 0); + } + + // XXX: dispatch_sync(complete_q, ^{}); + rc = pthread_mutex_lock(&kludge); + assert(!rc); + uint64_t end_time = mach_absolute_time(); + report(__FUNCTION__, "baton pass", (end_time - start_time), SMALL_LOOP*n_batons, "mach"); + // dispatch_queue_release(q); +} + +void bench_overload2() { + const int n_queues = 128; + const int q_div_b = 1; + const int n_batons = n_queues / q_div_b; + n_baton_kludge = n_batons; + assert(q_div_b * n_batons == n_queues); + dispatch_queue_t *q = alloca(n_queues * sizeof(dispatch_queue_t)); + dispatch_source_t *ds = alloca(n_queues * sizeof(dispatch_source_t)); + dispatch_queue_t complete_q = dispatch_queue_create("completion q", NULL); + __block uint64_t start_time = 0; + uint64_t time_to_start; + uint64_t end_time; + char *q_labels[n_queues]; + int i; + unsigned int rc; + + rc = pthread_mutex_unlock(&kludge); + assert(!rc); + rc = pthread_mutex_trylock(&kludge); + assert(!rc); + + // Start all batons one to two seconds from now. + time_to_start = (2 + time(NULL)) * 1000000000; + + for(i = 0; i < n_queues; i++) { + asprintf(q_labels + i, "queue#%d (%s)", i, __FUNCTION__); + assert(q_labels[i]); + q[i] = dispatch_queue_create(q_labels[i], NULL); + assert(q[i]); + struct baton *bat = valloc(sizeof(struct baton)); + assert(bat); + bat->passes_left = SMALL_LOOP; + bat->at_q = i; + bat->baton_number = i / q_div_b; + dispatch_queue_suspend(complete_q); + ds[i] = dispatch_source_timer_create(DISPATCH_TIMER_ABSOLUTE, time_to_start, 0, NULL, q[i], ^(dispatch_event_t event){ + assert(!dispatch_event_get_error(event, NULL)); + // We want to measure the time from the first + // baton pass, and NOT include hte wait time + // for eveyone to start to fire + if (!start_time) { + uint64_t s = mach_absolute_time(); + __sync_bool_compare_and_swap(&start_time, 0, s); + } + pass(q, bat, n_queues, complete_q); + }); + assert(ds[i]); + } + + // XXX: dispatch_sync(complete_q, ^{}); + rc = pthread_mutex_lock(&kludge); + assert(!rc); + + end_time = mach_absolute_time(); + report(__FUNCTION__, "overload#2", (end_time - start_time), SMALL_LOOP*n_batons, "mach"); + // Many releases and free()s + +} + +void bench_overload1() { + const int n_queues = 128; + const int q_div_b = 1; + const int n_batons = n_queues / q_div_b; + n_baton_kludge = n_batons; + assert(q_div_b * n_batons == n_queues); + dispatch_queue_t *q = alloca(n_queues * sizeof(dispatch_queue_t)); + dispatch_queue_t complete_q = dispatch_queue_create("completion q", NULL); + __block uint64_t start_time = 0; + struct timeval time_to_start; + uint64_t end_time; + char *q_labels[n_queues]; + int i; + unsigned int rc; + + rc = pthread_mutex_unlock(&kludge); + assert(!rc); + rc = pthread_mutex_trylock(&kludge); + assert(!rc); + + // Start all batons one to two seconds from now. + gettimeofday(&time_to_start, NULL); + time_to_start.tv_sec += 2; + + for(i = 0; i < n_queues; i++) { + asprintf(q_labels + i, "queue#%d (%s)", i, __FUNCTION__); + assert(q_labels[i]); + q[i] = dispatch_queue_create(q_labels[i], NULL); + assert(q[i]); + struct baton *bat = valloc(sizeof(struct baton)); + assert(bat); + bat->passes_left = SMALL_LOOP; + bat->at_q = i; + bat->baton_number = i / q_div_b; + dispatch_queue_suspend(complete_q); + dispatch_async(q[i], ^(void) { + struct timeval now; + gettimeofday(&now, NULL); + int sec = time_to_start.tv_sec - now.tv_sec; + if (sec >= 0) { + int usec = time_to_start.tv_usec + now.tv_usec; + if (usec > 0 || sec > 0) { + usleep(1000000 * sec + usec); + } else { + // XXX: log here + } + } + + // We want to measure the time from the first + // baton pass, and NOT include hte wait time + // for eveyone to start to fire + if (!start_time) { + uint64_t s = mach_absolute_time(); + __sync_bool_compare_and_swap(&start_time, 0, s); + } + + pass(q, bat, n_queues, complete_q); + }); + } + + // XXX: dispatch_sync(complete_q, ^{}); + rc = pthread_mutex_lock(&kludge); + assert(!rc); + + end_time = mach_absolute_time(); + report(__FUNCTION__, "overload#1", (end_time - start_time), SMALL_LOOP*n_batons, "mach"); + // Many releases and free()s + +} + +int main(int argc, char *argv[]) { + // Someday we will be able to take a list of tests to run, or exclude, or something. + + // There are somewhat diffrent perfomance chararistics when using the + // main queue, so we use a "normal" queue for all our tests. + dispatch_queue_t bench_q = dispatch_queue_create("benhmark Q", NULL); + + dispatch_async(bench_q, ^{ + // These two aren't as intresting in duel core, they queue all + // the calls before making them which isn't really what we + // want to test, is it? It also limites the number of loops + // we can spin around. +#if 1 + bench_message_round_trip(); + bench_precopy_message_round_trip(); + + bench_message_round_type_syncasync(); + bench_message_round_trip_f(); + bench_message_round_type_syncasync_f(); +#endif + bench_baton(); + bench_overload1(); + bench_overload2(); + + // This leaks, so we run it last. Also it gives + // wrong results if stdio hasn't been started already, + // so we definitly don't want to run it first even if + // the leaks are fixed (or ignored) + bench_queue_mem_use(); + + exit(0); + }); + + dispatch_main(); +} diff --git a/testing/summarize.c b/testing/summarize.c new file mode 100644 index 0000000..6eeb0d0 --- /dev/null +++ b/testing/summarize.c @@ -0,0 +1,76 @@ +#include +#include +#include + +int +has_prefix(const char* str, const char* prefix) { + return (strncmp(str, prefix, strlen(prefix)) == 0); +} + +int +print_summary(FILE* f, long total, long pass, long fail) { + fprintf(f, "Total: %ld\n", total); + fprintf(f, "Passed: %ld (%0.0lf%%)\n", pass, ((double)pass / (double)total) * (double)100.0); + fprintf(f, "Failed: %ld (%0.0lf%%)\n", fail, ((double)fail / (double)total) * (double)100.0); + fprintf(f, "\n"); + return 0; +} + +int main(int argc, char* argv[]) { + if (argc > 1) { + fprintf(stderr, "%s: usage: summarize\n", argv[0]); + exit(1); + } + + /* + FILE* f = fopen(argv[1], "w"); + if (f == NULL) { + perror(argv[1]); + exit(1); + } + */ + FILE* f = stdout; + + fprintf(f, "\n==================================================\n"); + fprintf(f, "[SUMMARY] Test Summary\n"); + fprintf(f, "==================================================\n\n"); + + size_t len; + char* ln; + long total = 0; + long pass = 0; + long fail = 0; + long total_total = 0; + long total_pass = 0; + long total_fail = 0; + for(;;) { + ln = fgetln(stdin, &len); + //if (ln) fprintf(stdout, "%.*s", (int)len, ln); + if (ln == NULL || has_prefix(ln, "[TEST]")) { + if (total) { + print_summary(f, total, pass, fail); + } + total_total += total; + total_pass += pass; + total_fail += fail; + total = 0; + pass = 0; + fail = 0; + if (ln) { + fprintf(f, "%.*s", (int)len, ln); + } else { + fprintf(f, "[TOTAL]\n"); + print_summary(f, total_total, total_pass, total_fail); + break; + } + } else if (has_prefix(ln, "[PASS]")) { + ++total; + ++pass; + } else if (has_prefix(ln, "[FAIL]")) { + ++total; + ++fail; + } + } + + return (total_fail ? EXIT_FAILURE : EXIT_SUCCESS); +} diff --git a/testing/test.c b/testing/test.c new file mode 100644 index 0000000..631cb3b --- /dev/null +++ b/testing/test.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include + +int +main(void) +{ + void (^wb)(dispatch_item_t) = ^(dispatch_item_t di) { + printf("%p\t%p\t%s:\t%llu\n", pthread_self(), di, __func__, mach_absolute_time()); + }; + void (^cb)(dispatch_item_t) = ^(dispatch_item_t di) { + printf("%p\t%p\t%s:\t%llu\n", pthread_self(), di, __func__, mach_absolute_time()); + }; + dispatch_queue_t q; + dispatch_item_t di_r; + size_t i; + bool r; + + q = dispatch_queue_new("test", 0, NULL, NULL, NULL); + assert(q != NULL); + + for (i = 0; i < 1000; i++) { + r = dispatch_call_wait(q, wb, NULL); + assert(r); + } + + printf("done with dispatch_call_wait()\n"); + + r = dispatch_apply_wait(wb, 10, NULL); + assert(r); + + r = dispatch_call(q, wb, cb, NULL, &di_r); + assert(r); + assert(di_r); + + printf("waiting for dispatch_call() callback\n"); + + dispatch_main(); + + return 0; +} diff --git a/testing/yet-another-apply-test.c b/testing/yet-another-apply-test.c new file mode 100644 index 0000000..7329cdf --- /dev/null +++ b/testing/yet-another-apply-test.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint64_t total; + +#define LAPS (256 * 1024 * 1024) +#define SIZE (LAPS * sizeof(int)) + +int +main(int argc, char *argv[]) +{ + dispatch_queue_t cq = dispatch_get_concurrent_queue(0); + struct stat sb; + long double cycles; + uint64_t s, e, d; + uint64_t tmp_total; + int r, fd; + const int *nums; + size_t i, stride; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + exit(EXIT_FAILURE); + } + + // make sure to have 2GB + of RAM installed and run this before hand: + // dd if=/dev/random bs=1024k count=1024 of=/tmp/testfile + fd = open(argv[1], O_RDONLY); + assert(fd != -1); + + r = fstat(fd, &sb); + assert(r != -1); + assert(sb.st_size >= (off_t)SIZE); + + nums = mmap(NULL, SIZE, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0); + assert(nums != MAP_FAILED); + + // force pages to be faulted in + for (i = 0; i < LAPS; i++) { + total += nums[i]; + } + + for (stride = 1; stride < (LAPS + 1); stride <<= 1) { + total = 0; + s = mach_absolute_time(); + dispatch_apply(LAPS / stride, cq, ^(size_t idx) { + const int *nums2 = nums + (idx * stride); + uint64_t ptotal = 0; + size_t idx2 = 0; + + // assert(stride > 0); + do { + ptotal += nums2[idx2++]; + } while (idx2 < stride); + + __sync_fetch_and_add(&total, ptotal); + }); + e = mach_absolute_time(); + d = e - s; + cycles = d; + cycles /= LAPS; + printf("da%lu:\t%Lf ns\n", stride, cycles); + } + + tmp_total = 0; + total = 0; + s = mach_absolute_time(); + for (i = 0; i < LAPS; i++) { + tmp_total += nums[i]; + } + total = tmp_total; + e = mach_absolute_time(); + d = e - s; + cycles = d; + cycles /= LAPS; + printf("naïve:\t%Lf ns\n", cycles); + + + tmp_total = 0; + total = 0; + s = mach_absolute_time(); +#pragma omp parallel for reduction(+:tmp_total) + for (i = 0; i < LAPS; i++) { + tmp_total += nums[i]; + } + total = tmp_total; + e = mach_absolute_time(); + d = e - s; + cycles = d; + cycles /= LAPS; + printf("OpenMP:\t%Lf ns\n", cycles); + + exit(EXIT_SUCCESS); +} -- 2.45.2