From 5daa7f610ab6e2ea43bca023cb3cfe96811b48b4 Mon Sep 17 00:00:00 2001 From: sebres Date: Mon, 3 Jul 2017 13:24:36 +0000 Subject: [performance] do one event (update / event servicing) cycle optimized (introduced threshold to prevent sourcing resp. waiting for new events by no-wait). [enhancement] new event type introduced: TCL_ASYNC_EVENTS, command "update" becomes options to process only specified types, resp. to bypass some event types (including -idle/-noidle that in opposite to "idletasks" does not included window events); test cases extended. --- generic/tcl.h | 1 + generic/tclEvent.c | 55 ++++++++++++------- generic/tclInt.h | 2 + generic/tclNotify.c | 154 ++++++++++++++++++++++++++++++++++++++++++++-------- generic/tclTimer.c | 122 +++++++++++++++++++++++++++++++++-------- tests/event.test | 61 +++++++++++++++++++-- 6 files changed, 326 insertions(+), 69 deletions(-) diff --git a/generic/tcl.h b/generic/tcl.h index 6a3c66a..b65a5cb 100644 --- a/generic/tcl.h +++ b/generic/tcl.h @@ -1297,6 +1297,7 @@ typedef struct { * events: */ +#define TCL_ASYNC_EVENTS (1<<0) #define TCL_DONT_WAIT (1<<1) #define TCL_WINDOW_EVENTS (1<<2) #define TCL_FILE_EVENTS (1<<3) diff --git a/generic/tclEvent.c b/generic/tclEvent.c index 27953a4..d18836b 100644 --- a/generic/tclEvent.c +++ b/generic/tclEvent.c @@ -1468,30 +1468,42 @@ Tcl_UpdateObjCmd( int objc, /* Number of arguments. */ Tcl_Obj *CONST objv[]) /* Argument objects. */ { - int optionIndex; - int flags = TCL_ALL_EVENTS|TCL_DONT_WAIT; - static CONST char *updateOptions[] = {"idletasks", "noidletasks", NULL}; - enum updateOptions {UPDATE_IDLETASKS, UPDATE_NOIDLETASKS}; - - if (objc == 1) { - } else if (objc == 2) { - if (Tcl_GetIndexFromObj(interp, objv[1], updateOptions, + int i, optionIndex; + static CONST defUpdateFlags = TCL_ALL_EVENTS|TCL_DONT_WAIT; + int flags = defUpdateFlags; + static CONST char *updateOptions[] = {"idletasks", /* backwards compat. */ + "-nowait", "-wait", /* new options */ + "-idle", "-noidle", "-timer", "-notimer", + "-file", "-nofile", "-window", "-nowindow", + "-async", "-noasync", + NULL}; + static CONST struct { + int minus; + int plus; + } *updateFlag, updateFlags[] = { + {TCL_ALL_EVENTS, + TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS}, /* idletasks */ + {0, TCL_DONT_WAIT}, {TCL_DONT_WAIT, 0}, /* -nowait, -wait */ + {0, TCL_IDLE_EVENTS}, {TCL_IDLE_EVENTS, 0}, /* -idle, -noidle */ + {0, TCL_TIMER_EVENTS}, {TCL_TIMER_EVENTS, 0}, /* -file, -nofile */ + {0, TCL_FILE_EVENTS}, {TCL_FILE_EVENTS, 0}, /* -file, -nofile */ + {0, TCL_WINDOW_EVENTS}, {TCL_WINDOW_EVENTS, 0}, /* -window, -nowindow */ + {0, TCL_ASYNC_EVENTS}, {TCL_ASYNC_EVENTS, 0}, /* -async, -noasync */ + {0, 0} /* dummy / place holder */ + }; + + for (i = 1; i < objc; i++) { + if (Tcl_GetIndexFromObj(interp, objv[i], updateOptions, "option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } - switch ((enum updateOptions) optionIndex) { - case UPDATE_IDLETASKS: - flags = TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS|TCL_DONT_WAIT; - break; - case UPDATE_NOIDLETASKS: - flags &= ~TCL_IDLE_EVENTS; - break; - default: - Tcl_Panic("Tcl_UpdateObjCmd: bad option index to UpdateOptions"); + updateFlag = &updateFlags[optionIndex]; + /* pure positive option and still default - reset all events */ + if (flags == defUpdateFlags && !updateFlag->minus) { + flags &= ~TCL_ALL_EVENTS; } - } else { - Tcl_WrongNumArgs(interp, 1, objv, "?option?"); - return TCL_ERROR; + flags &= ~updateFlag->minus; + flags |= updateFlag->plus; } while (Tcl_DoOneEvent(flags) != 0) { @@ -1500,6 +1512,9 @@ Tcl_UpdateObjCmd( Tcl_AppendResult(interp, "limit exceeded", NULL); return TCL_ERROR; } + + /* be sure not to produce infinite wait (wait only once) */ + flags |= TCL_DONT_WAIT; } /* diff --git a/generic/tclInt.h b/generic/tclInt.h index d0d1240..dd73eac 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -2922,6 +2922,7 @@ MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData, Tcl_Obj *const objv[]); MODULE_SCOPE void TclSetTimerEventMarker(void); MODULE_SCOPE int TclServiceTimerEvents(void); +MODULE_SCOPE int TclServiceIdleEx(int flags, int count); MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler( Tcl_Time *timePtr, Tcl_TimerProc *proc, ClientData clientData); @@ -2933,6 +2934,7 @@ MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx( Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc, size_t extraDataSize, int flags); MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr); +MODULE_SCOPE int TclPeekEventQueued(int flags); MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]); diff --git a/generic/tclNotify.c b/generic/tclNotify.c index 89182ea..f13fca3 100644 --- a/generic/tclNotify.c +++ b/generic/tclNotify.c @@ -72,11 +72,25 @@ typedef struct ThreadSpecificData { /* Next notifier in global list of notifiers. * Access is controlled by the listLock global * mutex. */ +#ifndef TCL_WIDE_CLICKS /* Last "time" source checked, used as threshold */ + unsigned long lastCheckClicks; +#else + Tcl_WideInt lastCheckClicks; +#endif } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* + * Used for performance purposes, threshold to bypass check source (if don't wait) + * Values under 1000 should be approximately under 1ms, e. g. 10 is ca. 0.01ms + */ +#ifndef CHECK_EVENT_SOURCE_THRESHOLD + #define CHECK_EVENT_SOURCE_THRESHOLD 10 +#endif + + +/* * Global list of notifiers. Access to this list is controlled by the listLock * mutex. If this becomes a performance bottleneck, this could be replaced * with a hashtable. @@ -618,28 +632,36 @@ Tcl_ServiceEvent( ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* + * No event flags is equivalent to TCL_ALL_EVENTS. + */ + + if ((flags & TCL_ALL_EVENTS) == 0) { + flags |= TCL_ALL_EVENTS; + } + + /* * Asynchronous event handlers are considered to be the highest priority * events, and so must be invoked before we process events on the event * queue. */ - if (Tcl_AsyncReady()) { + if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) { (void) Tcl_AsyncInvoke(NULL, 0); return 1; } - /* - * No event flags is equivalent to TCL_ALL_EVENTS. - */ - - if ((flags & TCL_ALL_EVENTS) == 0) { - flags |= TCL_ALL_EVENTS; + /* Async only */ + if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) { + return 0; } /* * If timer marker reached, process timer events now. */ - if (tsdPtr->timerMarkerPtr == INT2PTR(-1)) { + if ( tsdPtr->timerMarkerPtr == INT2PTR(-1) + || !tsdPtr->firstEventPtr + || ((flags & TCL_ALL_EVENTS) == TCL_TIMER_EVENTS) + ) { goto timer; } @@ -743,7 +765,7 @@ timer: /* * Process timer queue, if alloved and timers are enabled. */ - if (flags & TCL_TIMER_EVENTS && tsdPtr->timerMarkerPtr) { + if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) { /* reset marker */ tsdPtr->timerMarkerPtr = NULL; @@ -762,6 +784,73 @@ timer: /* *---------------------------------------------------------------------- * + * TclPeekEventQueued -- + * + * Check whether some event (except idle) available (async, queued, timer). + * + * This will be used e. g. in TclServiceIdle to stop the processing of the + * the idle events if some "normal" event occurred. + * + * Results: + * Returns 1 if some event queued, 0 otherwise. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclPeekEventQueued( + int flags) +{ + EventSource *sourcePtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + int repeat = 1; + + do { + /* + * Events already pending ? + */ + if ( Tcl_AsyncReady() + || (tsdPtr->firstEventPtr) + || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) + ) { + return 1; + } + + if (flags & TCL_DONT_WAIT) { + /* don't need to wait/check for events too often */ +#ifndef TCL_WIDE_CLICKS + unsigned long clicks = TclpGetClicks(); +#else + Tcl_WideInt clicks = TclpGetWideClicks(); +#endif + + if ((clicks - tsdPtr->lastCheckClicks) <= CHECK_EVENT_SOURCE_THRESHOLD) { + return 0; + } + tsdPtr->lastCheckClicks = clicks; + } + + /* + * Check all the event sources for new events. + */ + for (sourcePtr = tsdPtr->firstEventSourcePtr; sourcePtr != NULL; + sourcePtr = sourcePtr->nextPtr) { + if (sourcePtr->checkProc) { + (sourcePtr->checkProc)(sourcePtr->clientData, flags); + } + } + + } while (repeat--); + + return 0; +} + +/* + *---------------------------------------------------------------------- + * * TclSetTimerEventMarker -- * * Set timer event marker to the last pending event in the queue. @@ -920,26 +1009,33 @@ Tcl_DoOneEvent( * TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or * others defined by event sources. */ { - int result = 0, oldMode; + int result = 0, oldMode, i = 0; EventSource *sourcePtr; Tcl_Time *timePtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* - * The first thing we do is to service any asynchronous event handlers. + * No event flags is equivalent to TCL_ALL_EVENTS. */ - if (Tcl_AsyncReady()) { - (void) Tcl_AsyncInvoke(NULL, 0); - return 1; + if ((flags & TCL_ALL_EVENTS) == 0) { + flags |= TCL_ALL_EVENTS; } /* - * No event flags is equivalent to TCL_ALL_EVENTS. + * Asynchronous event handlers are considered to be the highest priority + * events, and so must be invoked before we process events on the event + * queue. */ - if ((flags & TCL_ALL_EVENTS) == 0) { - flags |= TCL_ALL_EVENTS; + if ((flags & TCL_ASYNC_EVENTS) && Tcl_AsyncReady()) { + (void) Tcl_AsyncInvoke(NULL, 0); + return 1; + } + + /* Async only */ + if ((flags & TCL_ALL_EVENTS) == TCL_ASYNC_EVENTS) { + return 0; } /* @@ -964,12 +1060,13 @@ Tcl_DoOneEvent( */ if ((flags & TCL_ALL_EVENTS) == TCL_IDLE_EVENTS) { - flags = TCL_IDLE_EVENTS | TCL_DONT_WAIT; + flags |= TCL_DONT_WAIT; goto idleEvents; } /* - * Ask Tcl to service a queued event, if there are any. + * Ask Tcl to service any asynchronous event handlers or + * queued event, if there are any. */ if (Tcl_ServiceEvent(flags)) { @@ -983,9 +1080,22 @@ Tcl_DoOneEvent( */ if (flags & TCL_DONT_WAIT) { + /* don't need to wait/check for events too often */ +#ifndef TCL_WIDE_CLICKS + unsigned long clicks = TclpGetClicks(); +#else + Tcl_WideInt clicks = TclpGetWideClicks(); +#endif + if ((clicks - tsdPtr->lastCheckClicks) <= CHECK_EVENT_SOURCE_THRESHOLD) { + goto idleEvents; + } + tsdPtr->lastCheckClicks = clicks; + tsdPtr->blockTime.sec = 0; tsdPtr->blockTime.usec = 0; tsdPtr->blockTimeSet = 1; + timePtr = &tsdPtr->blockTime; + goto wait; /* for notifier resp. system events */ } else { tsdPtr->blockTimeSet = 0; } @@ -1004,7 +1114,7 @@ Tcl_DoOneEvent( } tsdPtr->inTraversal = 0; - if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) { + if (tsdPtr->blockTimeSet) { timePtr = &tsdPtr->blockTime; } else { timePtr = NULL; @@ -1014,7 +1124,7 @@ Tcl_DoOneEvent( * Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1, * we should abort Tcl_DoOneEvent. */ - + wait: result = Tcl_WaitForEvent(timePtr); if (result < 0) { result = 0; @@ -1049,7 +1159,7 @@ Tcl_DoOneEvent( idleEvents: if (flags & TCL_IDLE_EVENTS) { - if (TclServiceIdle()) { + if (TclServiceIdleEx(flags, INT_MAX)) { result = 1; break; } diff --git a/generic/tclTimer.c b/generic/tclTimer.c index 12b039f..52a3073 100644 --- a/generic/tclTimer.c +++ b/generic/tclTimer.c @@ -121,6 +121,7 @@ static void FreeAfterPtr(ClientData clientData); static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr); static ThreadSpecificData *InitTimer(void); static void TimerExitProc(ClientData clientData); +static void TimerCheckProc(ClientData clientData, int flags); static void TimerSetupProc(ClientData clientData, int flags); static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *); @@ -258,7 +259,7 @@ InitTimer(void) if (tsdPtr == NULL) { tsdPtr = TCL_TSD_INIT(&dataKey); - Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr); + Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); Tcl_CreateThreadExitHandler(TimerExitProc, NULL); } return tsdPtr; @@ -289,7 +290,7 @@ TimerExitProc( TclThreadDataKeyGet(&dataKey); if (tsdPtr != NULL) { - Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr); + Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr); while ((tsdPtr->lastPromptPtr) != NULL) { TclDeleteTimerEntry(tsdPtr->lastPromptPtr); @@ -582,7 +583,7 @@ TclDeleteTimerEntry( static void TimerSetupProc( - ClientData data, /* Not used. */ + ClientData data, /* Specific data. */ int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { Tcl_Time blockTime, *firstTime; @@ -590,10 +591,11 @@ TimerSetupProc( if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; - if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) - || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) { + if ( ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending) + || ((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList ) + ) { /* - * There is an idle handler or a pending timer event, so just poll. + * There is a pending timer event or an idle handler, so just poll. */ blockTime.sec = 0; @@ -616,11 +618,10 @@ TimerSetupProc( blockTime.sec = 0; blockTime.usec = 0; } - + /* - * If the first timer has expired, stick an event on the queue. - */ - + * If the first timer has expired, stick an event on the queue right now. + */ if (!tsdPtr->timerPending && blockTime.sec == 0 && blockTime.usec == 0) { TclSetTimerEventMarker(); tsdPtr->timerPending = 1; @@ -636,6 +637,67 @@ TimerSetupProc( /* *---------------------------------------------------------------------- * + * TimerCheckProc -- + * + * This function is called by Tcl_DoOneEvent to check the timer event + * source for events. This routine checks the first timer in the list. + * + * Results: + * None. + * + * Side effects: + * May queue an event and update the maximum notifier block time. + * + *---------------------------------------------------------------------- + */ + +static void +TimerCheckProc( + ClientData data, /* Specific data. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ +{ + Tcl_Time blockTime, *firstTime; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data; + + if (!(flags & TCL_TIMER_EVENTS)) { + return; + } + + if (tsdPtr == NULL) { tsdPtr = InitTimer(); }; + + /* If already pending */ + if (!tsdPtr->timerList || tsdPtr->timerPending) { + return; + } + + /* + * Verify the first timer on the queue. + */ + Tcl_GetTime(&blockTime); + firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time); + blockTime.sec = firstTime->sec - blockTime.sec; + blockTime.usec = firstTime->usec - blockTime.usec; + if (blockTime.usec < 0) { + blockTime.sec -= 1; + blockTime.usec += 1000000; + } + if (blockTime.sec < 0) { + blockTime.sec = 0; + blockTime.usec = 0; + } + + /* + * If the first timer has expired, stick an event on the queue. + */ + if (blockTime.sec == 0 && blockTime.usec == 0) { + TclSetTimerEventMarker(); + tsdPtr->timerPending = 1; + } +} + +/* + *---------------------------------------------------------------------- + * * TclServiceTimerEvents -- * * This function is called by Tcl_ServiceEvent when a timer events should @@ -785,7 +847,7 @@ done: /* Compute the next timeout (later via TimerSetupProc using the first timer). */ tsdPtr->timerPending = 0; - return 1; /* processing done, again later via TimerSetupProc */ + return 1; /* processing done, again later via TimerCheckProc */ } /* @@ -928,7 +990,7 @@ Tcl_CancelIdleCall( /* *---------------------------------------------------------------------- * - * TclServiceIdle -- + * TclServiceIdle -- , TclServiceIdleEx -- * * This function is invoked by the notifier when it becomes idle. It will * invoke all idle handlers that are present at the time the call is @@ -945,14 +1007,15 @@ Tcl_CancelIdleCall( */ int -TclServiceIdle(void) +TclServiceIdleEx( + int flags, + int count) { TimerEntry *idlePtr; size_t currentGeneration; - Tcl_Time blockTime; ThreadSpecificData *tsdPtr = InitTimer(); - if (tsdPtr->idleList == NULL) { + if ((idlePtr = tsdPtr->idleList) == NULL) { return 0; } @@ -975,9 +1038,7 @@ TclServiceIdle(void) * during the call. */ - while ((idlePtr = tsdPtr->idleList) != NULL - && idlePtr->generation <= currentGeneration - ) { + while (idlePtr->generation <= currentGeneration) { /* detach entry from the owner's list */ TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr); @@ -989,18 +1050,33 @@ TclServiceIdle(void) (*idlePtr->deleteProc)(idlePtr->clientData); } ckfree((char *) idlePtr); + + /* + * Stop processing idle if idle queue empty, count reached or other + * events queued (only if not idle events only to service). + */ + + if ( (idlePtr = tsdPtr->idleList) == NULL + || !--count + || ((flags & TCL_ALL_EVENTS) != TCL_IDLE_EVENTS + && TclPeekEventQueued(flags)) + ) { + break; + } } - if (tsdPtr->idleList) { - blockTime.sec = 0; - blockTime.usec = 0; - Tcl_SetMaxBlockTime(&blockTime); - } + /* Reset generation */ if (!tsdPtr->idleList) { tsdPtr->idleGeneration = 0; } return 1; } + +int +TclServiceIdle(void) +{ + return TclServiceIdleEx(TCL_ALL_EVENTS, INT_MAX); +} /* *---------------------------------------------------------------------- diff --git a/tests/event.test b/tests/event.test index c905489..cf65ae17 100644 --- a/tests/event.test +++ b/tests/event.test @@ -694,12 +694,9 @@ test event-11.7 {Bug 16828b3744} { } {} -test event-12.1 {Tcl_UpdateCmd procedure} { - list [catch {update a b} msg] $msg -} {1 {wrong # args: should be "update ?option?"}} test event-12.2 {Tcl_UpdateCmd procedure} { list [catch {update bogus} msg] $msg -} {1 {bad option "bogus": must be idletasks or noidletasks}} +} {1 {bad option "bogus": must be idletasks, -nowait, -wait, -idle, -noidle, -timer, -notimer, -file, -nofile, -window, -nowindow, -async, or -noasync}} test event-12.3 {Tcl_UpdateCmd procedure} { foreach i [after info] { after cancel $i @@ -729,6 +726,62 @@ test event-12.4 {Tcl_UpdateCmd procedure} { list $x $y $z } {x-done before z-done} +test event-12.5 {update -idle, update -noidle} { + foreach i [after info] { + after cancel $i + } + set x {} + after idle {lappend x idle} + update -noidle + after 0 {lappend x 0} + update -noidle + after 50 {lappend x 1} + update -noidle + lappend x 2 + update -idle + lappend x 3 + after idle {lappend x idle} + after 0 {lappend x 4} + after 0 {lappend x 5} + update -idle + lappend x 6 + update + lappend x res:[vwait x 500] + set x +} {0 2 idle 3 idle 6 4 5 1 res:1} + +test event-12.6 {update -timer, update -notimer} { + foreach i [after info] { + after cancel $i + } + set x {} + after idle {lappend x idle.0} + update -timer + after 0 {lappend x 0a} + update -notimer + after idle { + lappend x idle.1a; + after 0 {lappend x 0b}; + after idle {lappend x idle.1b} + } + after 50 {lappend x 1; after idle {lappend x idle.2}} + update -timer + lappend x 2 + update -timer -idle + lappend x 3 + after idle {lappend x idle.3} + after 0 {lappend x 4} + after 0 {lappend x 5} + update -timer -idle + lappend x 6 + update + lappend x res:[vwait x 500] + update -noidle + lappend x 7 + update + set x +} {idle.0 0a 2 idle.1a 0b idle.1b 3 4 5 idle.3 6 1 res:1 7 idle.2} + test event-13.1 {Tcl_WaitForFile procedure, readable} {testfilehandler} { foreach i [after info] { after cancel $i -- cgit v0.12