Skip to content

Commit

Permalink
Squashed rebase from tclSE back porting branch (8.5/8.6), interim state.
Browse files Browse the repository at this point in the history
Current commit-history:

* partially back ported event-performance

* after at: added simple workaround for absolute timers/sleep ("after at real-time"): because we use monotonic time in all wait functions, so to avoid too long wait by the absolute timers (to be able to trigger it) if time jumped to the expected absolute time, just let block for maximal 1 second if absolute timers available.

test-cases: time-jumps (TIP #302) test covered now.
Note: on some platforms it is only possible if the user has corresponding privileges to change system date and time.
Ex.:  sudo LD_LIBRARY_PATH=. ./tclsh ../tests/timer.test -match timer-20.*

* after at: added simple workaround for absolute timers/sleep ("after at real-time"): because we use monotonic time in all wait functions, so to avoid too long wait by the absolute timers (to be able to trigger it) if time jumped to the expected absolute time, just let block for maximal 1 second if absolute timers available.

test-cases: time-jumps (TIP #302) test covered now.
Note: on some platforms it is only possible if the user has corresponding privileges to change system date and time.
Ex.:  sudo LD_LIBRARY_PATH=. ./tclsh ../tests/timer.test -match timer-20.*

* code review and small optimizations

* fix check event source threshold (corresponds 100-ns ranges, if the wide-clicks supported);
because of variable width of 1 wide-click: windows - frequency dependent, unix - nanoseconds, darwin/osx - tb.numer / tb.denom nanoseconds.

* unix: implements wide-clicks on unix (1 wide-click == 0.001 microseconds (1 nanosecond)), so more precise now (e. g. by time measurement etc.);
unix/configure: regenerated (autoconf)

* [unix] fixes conditional-wait: timeout is monotonic based;

* Introduced monotonic time as ultimate fix for time-jump issue (fixed for windows and unix now, TIP #302 fully implemented now);
Usage of monotonic time instead of adjustment via timeJump/timeJumpEpoch is more precise and effective.
New sub-command "clock monotonic" to provide monotonic time facility for tcl-level.

* don't cancel scheduled event as long as the event list is not bidirectional (too slow by large queue) - rewritten to cancel delayed (by execute it).

* fixed timer-marker handling: timer should be always executed after queued event (of the same generation), it was marked (be sure it marked to immediate execution in corresponding checkProc only).
tclIO: scheduled event rewritten using Tcl_Event instead of timer event (IO is not timer, e. g. executed also by usage of `vwait -notimer ...`, etc).

* Merge branch 'fix-busy-prompt-timers' into event-8.5-perf-branch

* Amend to timer-marker: dualize special state of timer-marker (to differentiate between timer generations), so:
  INT2PTR(-1) - exec immediate (marker reached);
  INT2PTR(-2) - check in the next-cycle (marker reached only if no other events available);
Avoids permanent busy execution of prompt-events (always busy in timer), if they regenerate itself continuously for waiting for other events (like writable/readable), see e. g. socket-2.12.

* "after at" set factor to 1000000 (seconds), test cases fixed

* revert dual lists (relative/absolute) back to single list (because of better handling, a bit faster, etc.)

* don't use tolerance in vwait, because of dual usage, it causes canceling of wait before end-time, on small timeout values (like 0.5, etc.)

* call TclWinResetTimerResolution at end of sleep resp. wait for event (no calibration thread anymore)

* calibration cycle completely rewritten (no calibration thread needed, soft drifts within 250ms intervals, fewer discrepancy and fewer virtual time gradation, etc).
todo: implement resetting timer-resolution to original value (without calibration thread now).

* extended performance test-cases (test-nrt-capability): RTS-near sleeps with very brief sleep-time.

* chanio.test: optimize several tests cases running too long (shorten unwanted large sleeps)

* bug fix: prevent setting of negative block-time by too few initial wait-time, that may expire immediately (for example `vwait 0.0001 test`).

* extended performance test-cases (test-nrt-capability): covering of brief wait-times and other RTS-near constructs.

* [unix] optimized Tcl_WaitForEvent similar to windows changes (makes Tcl for *nix more "RTS" resp. NRT-capable):
- more precise waiting now (e.g. still microseconds by time up to 0.005 ms), important since after/vwait accepting microseconds (double);
- avoids too long waiting on *nix wait/sleep primitives, e. g. by `timerate {vwait 0 a}` - 1.5µs now vs. 31.9µs before;
- extended with new internal function TclpSleep (in contrast to Tcl_Sleep accept Tcl_Time, so microseconds);

* added performance test-cases to cover timer-events speed resp. event-driven tcl-handling
(cherry-picked and back-ported from tclSE-9)

* fix sporadic errors on some fast cpu/platforms (because bgerror executed in background and it is an idle-event, give enough time to process it (resp. wait until last idle event is done);

* make timer test-case more precise and time-independent, ignores short tolerance (deviation by waiting);
several time-independent test-cases optimized (wait shorter now) + some new cases to cover more situations.

* after info, after cancel: compare interpreter of the timer-events by direct retrieving via internal representation (ignore foreign events), test cases extended.

* resolved some warnings / fixed unix resp. x64 compilation

* code review + better usage of the waiting tolerance (fewer CPU-greedy now, avoid busy-wait if the rest of wait-time too small and can be neglected);
TMR_RES_TOLERANCE can be defined to use wait-tolerance on *nix platforms (currently windows only as relation resp. deviation between default timer resolution 15.600 in exact milliseconds, means 15600/15000 + small overhead);
Decreasing of TMR_RES_TOLERANCE (up to 0) makes tcl more "RTS" resp. NRT-capable (very precise wait-intervals, but more CPU-hungry).

* [win] fallback to replace C++ keyword "inline" with C keyword "__inline"
Otherwise depending on the VC-version, context, include-order it can cause:
  error C2054: expected '(' to follow 'inline'

* [win32] use timer resolution handling in Tcl_Sleep also;

* Use auto-reset event object (system automatically resets the event state to nonsignaled after wake-up), avoids unwanted reset if wake-up for some other reasons (timeout/aio/message).

* optimization of Tcl_LimitExceeded by internal usage (tclInt header)

* dynamic increase of timer resolution corresponding wait-time;
non-blocking wait for event - if block-time set outside an event source traversal, use it as timeout, so can return with result 0 (no events);

* [enhancement] extend "vwait" with same options as "update", new syntax "vwait ?options? ?timeout? varname".
some small improvements and fixing:
- Tcl_DoOneEvent can wait for block time that was set with Tcl_SetMaxBlockTime outside an event source traversal,
  and stop waiting if Tcl_SetMaxBlockTime was called outside an event source (another event occurs and interrupt waiting loop), etc;
- safer more precise pre-lookup by options (use TclObjIsIndexOfTable instead of simply comparison of type with tclIndexType);
test cases extended to cover conditional "vwait" usage;

* interim commit: try to extend "vwait" with same options as "update"

* [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.

* command "vwait" extended with timeout argument (in ms), 0 could be used to process pending events only (without wait), negative value equivalent execution of "vwait" without timeout (infinite);
test cases fixed and extended;

* [performance] large performance increase by event servicing cycles (3x - 5x faster now);
[win] prevent listen using PeekMessage twice, and no wait anymore for too short timeouts (because windows can wait too long), compare 0µs with up-to 100µs overhead within MsgWaitForMultipleObjectsEx;
[bad behavior] process idle events only as long as no other events available (now TclPeekEventQueued will be used to check new events are available in service idle cycle);
[enhancement] new option "noidletasks" for command "update", so "update noidle" means "process all events but not idle";

* [performance] much better handling for timer events within Tcl_ServiceEvent using timer marker in the queue and direct call of TclServiceTimerEvents if marker reached (instead of continuous adding handler event, polling it in the queue and removing hereafter);
this provides double performance increase in the service cycle;

* [performance] introduced additional queue for prompt timer events (after 0) that should be executed immediately (no time);
normalizes timer, prompt and idle events structures using common TimerEntry structure for all types;

* bug fix: wrong release of after-id tcl-object if it switch type (object leak)

* [bug/stable fix] don't execute TimerSetupProc directly (may be unwanted, because changes the blocking time, also if TCL_TIMER_EVENTS|TCL_IDLE_EVENTS not set), so let do that within Tcl_DoOneEvent cycle only (we have registered an event source).
[performance] optimization for "after 0" as immediately execution without time (invoke as soon as possible) - generation and invocation of such timers twice faster now.
[performance] leave handler-event in the queue as long as pending timers still available (with expired time or immediate timers) by generation lock, resp. changed/not invalidated timer-queue) - so fewer event/allocations and guarantee to be executed within the next event cycle;

* after-id: introduced object of type "afterObjType" as self-referenced weak pointer to timer/idle event, used for fast access to the "after" event (cancel, info etc.);
test cases extended to cover it additionally

* rewrite interpreter limit handling using new timer event handling (with delete callback)

* timer resp. idle events optimized: better handling using doubly linked lists, prevents allocating memory twice for the "after" events (use memory inside timer/idle event for the "after" structure), etc.

* [performance] after-event list optimized (interp-assoc switched to doubly linked list, because requires handling from both ends of the list)
closes ticket [0520d17284500573d7c46aa88e0c6b4ebc9b6a02]
  • Loading branch information
sebres committed Jun 29, 2017
1 parent 599701d commit 3571421
Show file tree
Hide file tree
Showing 28 changed files with 11,060 additions and 16,237 deletions.
2 changes: 2 additions & 0 deletions generic/tcl.h
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ typedef void (Tcl_PanicProc) _ANSI_ARGS_((CONST char *format, ...));
typedef void (Tcl_TcpAcceptProc) _ANSI_ARGS_((ClientData callbackData,
Tcl_Channel chan, char *address, int port));
typedef void (Tcl_TimerProc) _ANSI_ARGS_((ClientData clientData));
typedef void (Tcl_TimerDeleteProc) _ANSI_ARGS_((ClientData clientData));
typedef int (Tcl_SetFromAnyProc) _ANSI_ARGS_((Tcl_Interp *interp,
struct Tcl_Obj *objPtr));
typedef void (Tcl_UpdateStringProc) _ANSI_ARGS_((struct Tcl_Obj *objPtr));
Expand Down Expand Up @@ -1296,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)
Expand Down
1 change: 1 addition & 0 deletions generic/tclBasic.c
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ static const CmdInfo builtInCmds[] = {
{"source", Tcl_SourceObjCmd, NULL, 0},
{"tell", Tcl_TellObjCmd, NULL, 1},
{"time", Tcl_TimeObjCmd, NULL, 1},
{"timerate", Tcl_TimeRateObjCmd, NULL, 1},
{"unload", Tcl_UnloadObjCmd, NULL, 0},
{"update", Tcl_UpdateObjCmd, NULL, 1},
{"vwait", Tcl_VwaitObjCmd, NULL, 1},
Expand Down
46 changes: 39 additions & 7 deletions generic/tclClock.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ static int ClockMicrosecondsObjCmd(
static int ClockMillisecondsObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
static int ClockMonotonicObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
static int ClockParseformatargsObjCmd(
ClientData clientData, Tcl_Interp* interp,
int objc, Tcl_Obj *const objv[]);
Expand Down Expand Up @@ -212,6 +215,7 @@ static const struct ClockCommand clockCommands[] = {
{ "getenv", ClockGetenvObjCmd },
{ "microseconds", ClockMicrosecondsObjCmd },
{ "milliseconds", ClockMillisecondsObjCmd },
{ "monotonic", ClockMonotonicObjCmd },
{ "seconds", ClockSecondsObjCmd },
{ "Oldscan", TclClockOldscanObjCmd },
{ "ConvertLocalToUTC", ClockConvertlocaltoutcObjCmd },
Expand Down Expand Up @@ -1739,9 +1743,7 @@ ClockClicksObjCmd(
break;
}
case CLICKS_MICROS:
Tcl_GetTime(&now);
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(
((Tcl_WideInt) now.sec * 1000000) + now.usec));
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(TclpGetMicroseconds()));
break;
}

Expand Down Expand Up @@ -1810,15 +1812,45 @@ ClockMicrosecondsObjCmd(
int objc, /* Parameter count */
Tcl_Obj* const* objv) /* Parameter values */
{
Tcl_Time now;
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return TCL_ERROR;
}
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(TclpGetMicroseconds()));
return TCL_OK;
}

/*----------------------------------------------------------------------
*
* ClockMonotonicObjCmd -
*
* Returns a count of microseconds since some starting point.
* This represents monotonic time not affected from the time-jumps.
*
* Results:
* Returns a standard Tcl result.
*
* Side effects:
* None.
*
* This function implements the 'clock monotonic' Tcl command. Refer to the
* user documentation for details on what it does.
*
*----------------------------------------------------------------------
*/

int
ClockMonotonicObjCmd(
ClientData clientData, /* Client data is unused */
Tcl_Interp* interp, /* Tcl interpreter */
int objc, /* Parameter count */
Tcl_Obj* const* objv) /* Parameter values */
{
if (objc != 1) {
Tcl_WrongNumArgs(interp, 1, objv, NULL);
return TCL_ERROR;
}
Tcl_GetTime(&now);
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(
((Tcl_WideInt) now.sec * 1000000) + now.usec));
Tcl_SetObjResult(interp, Tcl_NewWideIntObj(TclpGetUTimeMonotonic()));
return TCL_OK;
}

Expand Down
Loading

0 comments on commit 3571421

Please sign in to comment.