The Inbox: Chronology-Core-ul.54.mcz

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
15 messages Options
Reply | Threaded
Open this post in threaded view
|

The Inbox: Chronology-Core-ul.54.mcz

commits-2
Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz

==================== Summary ====================

Name: Chronology-Core-ul.54
Author: ul
Time: 3 May 2020, 8:45:27.099465 pm
UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
Ancestors: Chronology-Core-nice.52

Automatically update the time zone cached by the VM every 30 minutes:
- copied and modified Eliot's accessor to primitive 243 to do the actual updating (Time class >> #primitiveUpdateTimeZone)
- update happens when Time class >> #posixMicrosecondClockWithOffset or Time class >> #posixMicrosecondClockWithOffset: are sent. Other low-level accessors of the VM time zone are not updating the cached time zone value (e.g. Time class >> #localMicrosecondClockPrimitive)
- actual update happens in Time class >> #updateTimeZoneCacheAt:
- postscript activates the update mechanism which can be turned off by evaluating [ Time classPool at: #UpdateVMTimeZoneCacheAt put: nil ]

Other changes:
- added Time class >> #posixUtcMicrosecondClock to have a fast way to access that raw timestamp
- added MicrosecondsBetweenPosixEpochAndSqueakEpoch to ChronologyConstants to support the above
- do not send Time class >> #initialize from DateAndTime class >> #startUp:. Let Time have its own #startUp: method instead. Postscript adds Time to the startUpList before DateAndTime.
- do not send super initialize from DateAndTime class >> #initialize
- do not send #automaticTimezone to DateAndTime on startup, because that's just an accessor with no side effects
- do not overwrite set variables in Time class >> #initialize
- assert that the value passed to Time class >> #clockPolicy: is one of the known values

=============== Diff against Chronology-Core-nice.52 ===============

Item was changed:
  SharedPool subclass: #ChronologyConstants
  instanceVariableNames: ''
+ classVariableNames: 'DayNames DaysInMonth MicrosecondsBetweenPosixEpochAndSqueakEpoch MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
- classVariableNames: 'DayNames DaysInMonth MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
  poolDictionaries: ''
  category: 'Chronology-Core'!
 
  !ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0!
  ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.!

Item was changed:
  ----- Method: ChronologyConstants class>>initialize (in category 'class initialization') -----
  initialize
  "ChronologyConstants initialize"
 
  SqueakEpoch := 2415386. "Julian day number of 1 Jan 1901"
  SecondsInDay := 86400.
  SecondsInHour := 3600.
  SecondsInMinute := 60.
  MicrosecondsInDay := 24 * 60 * 60 * 1000000.
  NanosInSecond := 10 raisedTo: 9.
  NanosInMillisecond := 10 raisedTo: 6.
  DayNames := #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday).
 
  MonthNames := #( January February March April May June
  July August September October November December).
+ DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31).
+ MicrosecondsBetweenPosixEpochAndSqueakEpoch := 2177452800000000!
- DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31)!

Item was changed:
  ----- Method: DateAndTime class>>initialize (in category 'initialize-release') -----
  initialize
 
- super initialize.
-
  ClockProvider := Time.
  PosixEpochJulianDays := 2440588.
  InitializeFromPrimitive := self canInitializeFromPrimitive.
  Smalltalk addToStartUpList: self.
  self startUp: true
  !

Item was changed:
  ----- Method: DateAndTime class>>startUp: (in category 'system startup') -----
  startUp: startingAfresh
  "Set local timezone"
  startingAfresh
  ifTrue: [InitializeFromPrimitive := self canInitializeFromPrimitive.
  Time initialize. "set LastClockTick to 0".
+ self now]!
- self now.
- self automaticTimezone]!

Item was changed:
  Magnitude subclass: #Time
  instanceVariableNames: 'seconds nanos'
+ classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UpdateVMTimeZoneCacheAt UseHighResClockForTiming'
- classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UseHighResClockForTiming'
  poolDictionaries: 'ChronologyConstants'
  category: 'Chronology-Core'!
 
  !Time commentStamp: 'dew 10/23/2004 17:58' prior: 0!
  This represents a particular point in time during any given day.  For example, '5:19:45 pm'.
 
  If you need a point in time on a particular day, use DateAndTime.  If you need a duration of time, use Duration.
  !

Item was changed:
  ----- Method: Time class>>clockPolicy: (in category 'class initialization') -----
  clockPolicy: aSymbol
  "When sequencial calls are made to DateAndTime now, it may be desirable to
  force the system clock to be monotonic, and it may be desirable for the clock
  to appear to be strictly increasing with no repeat values. The ClockPolicy
  identifies which of several possible strategies to use.
 
  Allowable values are
  #acceptPlatformTime
  #monotonicAllowDuplicates
  #monotonicForceMicrosecondIncrement
  #monotonicForceNanosecondIncrement "
 
+ self assert: (
+ #(
+ acceptPlatformTime
+ monotonicAllowDuplicates
+ monotonicForceMicrosecondIncrement
+ monotonicForceNanosecondIncrement) includes: aSymbol).
  ClockPolicy := aSymbol!

Item was changed:
  ----- Method: Time class>>initialize (in category 'class initialization') -----
  initialize
+ " self initialize "
- "Time initialize"
 
+ LastClockTick ifNil: [ LastClockTick := 0 ].
- "Initialize at startup time to protect for the case of an image saved with bad LastClockTick"
- LastClockTick := 0.
 
+ HighResClockTicksPerMillisecond ifNil: [ HighResClockTicksPerMillisecond := 0 ].
- HighResClockTicksPerMillisecond := 0.
 
+ ClockPolicy ifNil: [
+ "self clockPolicy: #acceptPlatformTime."
+ self clockPolicy: #monotonicAllowDuplicates.
+ "self clockPolicy: #monotonicForceMicrosecondIncrement."
+ "self clockPolicy: #monotonicForceNanosecondIncrement." ]!
- "self clockPolicy: #acceptPlatformTime."
- self clockPolicy: #monotonicAllowDuplicates.
- "self clockPolicy: #monotonicForceMicrosecondIncrement."
- "self clockPolicy: #monotonicForceNanosecondIncrement."
- !

Item was changed:
  ----- Method: Time class>>posixMicrosecondClockWithOffset (in category 'clock') -----
  posixMicrosecondClockWithOffset
  "Answer an array with local microseconds since the Posix epoch and the
  current seconds offset from GMT in the local time zone."
 
+ | array posixUtcValue |
- | array utcValue |
  array := self primPosixMicrosecondClockWithOffset.
+ posixUtcValue := array at: 1.
+ (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+ self primPosixMicrosecondClockWithOffset: array.
+ posixUtcValue := array at: 1 ].
  ClockPolicy caseOf: {
  [#acceptPlatformTime] -> [^ array] .
  [#monotonicAllowDuplicates] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := array at: 1.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
  ifFalse: [array at: 1 put: LastClockTick]] .
  [#monotonicForceMicrosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := array at: 1.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
  ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
  array at: 1 put: LastClockTick]] .
  [#monotonicForceNanosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := array at: 1.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
  ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
  array at: 1 put: LastClockTick]]
  } otherwise: [].
+ ^array!
- ^array
-
- !

Item was changed:
  ----- Method: Time class>>posixMicrosecondClockWithOffset: (in category 'clock') -----
  posixMicrosecondClockWithOffset: aDateAndTime
  "Initialize aDateAndTime initialized with local microseconds since the Posix
  epoch and the current seconds offset from GMT in the local time zone."
 
+ | posixUtcValue |
-
- | utcValue |
  self primPosixMicrosecondClockWithOffset: aDateAndTime.
+ posixUtcValue := aDateAndTime utcMicroseconds.
+ (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+ self primPosixMicrosecondClockWithOffset: aDateAndTime .
+ posixUtcValue := aDateAndTime utcMicroseconds ].
  ClockPolicy caseOf: {
  [#acceptPlatformTime] -> [^ aDateAndTime] .
  [#monotonicAllowDuplicates] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := aDateAndTime utcMicroseconds.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
  ifFalse: [aDateAndTime utcMicroseconds: LastClockTick]] .
  [#monotonicForceMicrosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := aDateAndTime utcMicroseconds.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
  ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
  aDateAndTime utcMicroseconds: LastClockTick]] .
  [#monotonicForceNanosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := aDateAndTime utcMicroseconds.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
  ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
  aDateAndTime utcMicroseconds: LastClockTick]]
  } otherwise: [].
+ ^aDateAndTime!
- ^aDateAndTime
- !

Item was added:
+ ----- Method: Time class>>posixUtcMicrosecondClock (in category 'clock') -----
+ posixUtcMicrosecondClock
+ "Answer the UTC microseconds since the POSIX epoch (January 1st 1970 00:00:00 UTC)."
+
+ ^self utcMicrosecondClock - MicrosecondsBetweenPosixEpochAndSqueakEpoch!

Item was added:
+ ----- Method: Time class>>primitiveUpdateTimeZone (in category 'clock') -----
+ primitiveUpdateTimeZone
+ "Update the VMs notion of the current time zone.  The VM sets its notion
+ of the time zone once at start-up.  If one wants the VM to keep its notion
+ up-to-date arrange to invoke this primitive periodically."
+
+ <primitive: 243>
+ ^nil "Return nil instead of self to indicate that the primitive failed."!

Item was added:
+ ----- Method: Time class>>startUp: (in category 'system startup') -----
+ startUp: resuming
+
+ resuming ifTrue: [
+ LastClockTick := 0 ]!

Item was added:
+ ----- Method: Time class>>updateTimeZoneCacheAt: (in category 'clock') -----
+ updateTimeZoneCacheAt: posixUtcMicrosecondClock
+ "Tell the VM to update its cached time zone value if the POSIX UTC time reached the valute stored in UpdateVMTimeZoneCacheAt has been reached. Assume that posixUtcMicrosecondClock is an integer with the current POSIX UTC microsecond clock value. Return true when the cache was updated to indicate that the time zone may have changed."
+
+ | updateInterval |
+ UpdateVMTimeZoneCacheAt ifNil: [
+ "Automatic update is disabled."
+ ^false ].
+ posixUtcMicrosecondClock < UpdateVMTimeZoneCacheAt ifTrue: [ ^false ].
+ self primitiveUpdateTimeZone ifNil: [
+ "The primitive failed."
+ ^false ].
+ updateInterval := 1800000000. "This could be a preference but 30 minutes matches all upcoming DST change times."
+ UpdateVMTimeZoneCacheAt := posixUtcMicrosecondClock // updateInterval + 1 * updateInterval "Round up posixUtcMicrosecondClock to the next multiple of updateInterval.".
+ ^true!

Item was changed:
+ (PackageInfo named: 'Chronology-Core') postscript: '"Make sure UpdateVMTimeZoneCacheAt of Time is initialized."
+ Time classPool at: #UpdateVMTimeZoneCacheAt put: 0.
+ "Separated Time''s startup duties from DateAndTime."
+ Smalltalk addToStartUpList: Time before: DateAndTime'!
- (PackageInfo named: 'Chronology-Core') postscript: 'DateAndTime startUp: true.
- HashedCollection rehashAll.
- '!


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

Tobias Pape
Yes! Thank you, this looks just right!

I'm not fond of all the things, but it's exactly the level of practicability I like in Squeak.

+1
        -Tobias

> On 03.05.2020, at 20:45, [hidden email] wrote:
>
> Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
> http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
>
> ==================== Summary ====================
>
> Name: Chronology-Core-ul.54
> Author: ul
> Time: 3 May 2020, 8:45:27.099465 pm
> UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
> Ancestors: Chronology-Core-nice.52
>
> Automatically update the time zone cached by the VM every 30 minutes:
> - copied and modified Eliot's accessor to primitive 243 to do the actual updating (Time class >> #primitiveUpdateTimeZone)
> - update happens when Time class >> #posixMicrosecondClockWithOffset or Time class >> #posixMicrosecondClockWithOffset: are sent. Other low-level accessors of the VM time zone are not updating the cached time zone value (e.g. Time class >> #localMicrosecondClockPrimitive)
> - actual update happens in Time class >> #updateTimeZoneCacheAt:
> - postscript activates the update mechanism which can be turned off by evaluating [ Time classPool at: #UpdateVMTimeZoneCacheAt put: nil ]
>
> Other changes:
> - added Time class >> #posixUtcMicrosecondClock to have a fast way to access that raw timestamp
> - added MicrosecondsBetweenPosixEpochAndSqueakEpoch to ChronologyConstants to support the above
> - do not send Time class >> #initialize from DateAndTime class >> #startUp:. Let Time have its own #startUp: method instead. Postscript adds Time to the startUpList before DateAndTime.
> - do not send super initialize from DateAndTime class >> #initialize
> - do not send #automaticTimezone to DateAndTime on startup, because that's just an accessor with no side effects
> - do not overwrite set variables in Time class >> #initialize
> - assert that the value passed to Time class >> #clockPolicy: is one of the known values
>
> =============== Diff against Chronology-Core-nice.52 ===============
>
> Item was changed:
>  SharedPool subclass: #ChronologyConstants
>   instanceVariableNames: ''
> + classVariableNames: 'DayNames DaysInMonth MicrosecondsBetweenPosixEpochAndSqueakEpoch MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
> - classVariableNames: 'DayNames DaysInMonth MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
>   poolDictionaries: ''
>   category: 'Chronology-Core'!
>
>  !ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0!
>  ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.!
>
> Item was changed:
>  ----- Method: ChronologyConstants class>>initialize (in category 'class initialization') -----
>  initialize
>   "ChronologyConstants initialize"
>  
>   SqueakEpoch := 2415386. "Julian day number of 1 Jan 1901"
>   SecondsInDay := 86400.
>   SecondsInHour := 3600.
>   SecondsInMinute := 60.
>   MicrosecondsInDay := 24 * 60 * 60 * 1000000.
>   NanosInSecond := 10 raisedTo: 9.
>   NanosInMillisecond := 10 raisedTo: 6.
>   DayNames := #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday).
>  
>   MonthNames := #( January February March April May June
>   July August September October November December).
> + DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31).
> + MicrosecondsBetweenPosixEpochAndSqueakEpoch := 2177452800000000!
> - DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31)!
>
> Item was changed:
>  ----- Method: DateAndTime class>>initialize (in category 'initialize-release') -----
>  initialize
>
> - super initialize.
> -
>   ClockProvider := Time.
>   PosixEpochJulianDays := 2440588.
>   InitializeFromPrimitive := self canInitializeFromPrimitive.
>   Smalltalk addToStartUpList: self.
>   self startUp: true
>  !
>
> Item was changed:
>  ----- Method: DateAndTime class>>startUp: (in category 'system startup') -----
>  startUp: startingAfresh
>   "Set local timezone"
>   startingAfresh
>   ifTrue: [InitializeFromPrimitive := self canInitializeFromPrimitive.
>   Time initialize. "set LastClockTick to 0".
> + self now]!
> - self now.
> - self automaticTimezone]!
>
> Item was changed:
>  Magnitude subclass: #Time
>   instanceVariableNames: 'seconds nanos'
> + classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UpdateVMTimeZoneCacheAt UseHighResClockForTiming'
> - classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UseHighResClockForTiming'
>   poolDictionaries: 'ChronologyConstants'
>   category: 'Chronology-Core'!
>
>  !Time commentStamp: 'dew 10/23/2004 17:58' prior: 0!
>  This represents a particular point in time during any given day.  For example, '5:19:45 pm'.
>
>  If you need a point in time on a particular day, use DateAndTime.  If you need a duration of time, use Duration.
>  !
>
> Item was changed:
>  ----- Method: Time class>>clockPolicy: (in category 'class initialization') -----
>  clockPolicy: aSymbol
>   "When sequencial calls are made to DateAndTime now, it may be desirable to
>   force the system clock to be monotonic, and it may be desirable for the clock
>   to appear to be strictly increasing with no repeat values. The ClockPolicy
>   identifies which of several possible strategies to use.
>
>   Allowable values are
>   #acceptPlatformTime
>   #monotonicAllowDuplicates
>   #monotonicForceMicrosecondIncrement
>   #monotonicForceNanosecondIncrement "
>
> + self assert: (
> + #(
> + acceptPlatformTime
> + monotonicAllowDuplicates
> + monotonicForceMicrosecondIncrement
> + monotonicForceNanosecondIncrement) includes: aSymbol).
>   ClockPolicy := aSymbol!
>
> Item was changed:
>  ----- Method: Time class>>initialize (in category 'class initialization') -----
>  initialize
> + " self initialize "
> - "Time initialize"
>
> + LastClockTick ifNil: [ LastClockTick := 0 ].
> - "Initialize at startup time to protect for the case of an image saved with bad LastClockTick"
> - LastClockTick := 0.
>  
> + HighResClockTicksPerMillisecond ifNil: [ HighResClockTicksPerMillisecond := 0 ].
> - HighResClockTicksPerMillisecond := 0.
>
> + ClockPolicy ifNil: [
> + "self clockPolicy: #acceptPlatformTime."
> + self clockPolicy: #monotonicAllowDuplicates.
> + "self clockPolicy: #monotonicForceMicrosecondIncrement."
> + "self clockPolicy: #monotonicForceNanosecondIncrement." ]!
> - "self clockPolicy: #acceptPlatformTime."
> - self clockPolicy: #monotonicAllowDuplicates.
> - "self clockPolicy: #monotonicForceMicrosecondIncrement."
> - "self clockPolicy: #monotonicForceNanosecondIncrement."
> - !
>
> Item was changed:
>  ----- Method: Time class>>posixMicrosecondClockWithOffset (in category 'clock') -----
>  posixMicrosecondClockWithOffset
>   "Answer an array with local microseconds since the Posix epoch and the
>   current seconds offset from GMT in the local time zone."
>
> + | array posixUtcValue |
> - | array utcValue |
>   array := self primPosixMicrosecondClockWithOffset.
> + posixUtcValue := array at: 1.
> + (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
> + self primPosixMicrosecondClockWithOffset: array.
> + posixUtcValue := array at: 1 ].
>   ClockPolicy caseOf: {
>   [#acceptPlatformTime] -> [^ array] .
>   [#monotonicAllowDuplicates] -> [
> + posixUtcValue > LastClockTick
> + ifTrue: [LastClockTick := posixUtcValue]
> - utcValue := array at: 1.
> - utcValue > LastClockTick
> - ifTrue: [LastClockTick := utcValue]
>   ifFalse: [array at: 1 put: LastClockTick]] .
>   [#monotonicForceMicrosecondIncrement] -> [
> + posixUtcValue > LastClockTick
> + ifTrue: [LastClockTick := posixUtcValue]
> - utcValue := array at: 1.
> - utcValue > LastClockTick
> - ifTrue: [LastClockTick := utcValue]
>   ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
>   array at: 1 put: LastClockTick]] .
>   [#monotonicForceNanosecondIncrement] -> [
> + posixUtcValue > LastClockTick
> + ifTrue: [LastClockTick := posixUtcValue]
> - utcValue := array at: 1.
> - utcValue > LastClockTick
> - ifTrue: [LastClockTick := utcValue]
>   ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
>   array at: 1 put: LastClockTick]]
>   } otherwise: [].
> + ^array!
> - ^array
> -
> - !
>
> Item was changed:
>  ----- Method: Time class>>posixMicrosecondClockWithOffset: (in category 'clock') -----
>  posixMicrosecondClockWithOffset: aDateAndTime
>   "Initialize aDateAndTime initialized with local microseconds since the Posix
>   epoch and the current seconds offset from GMT in the local time zone."
>
> + | posixUtcValue |
> -
> - | utcValue |
>   self primPosixMicrosecondClockWithOffset: aDateAndTime.
> + posixUtcValue := aDateAndTime utcMicroseconds.
> + (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
> + self primPosixMicrosecondClockWithOffset: aDateAndTime .
> + posixUtcValue := aDateAndTime utcMicroseconds ].
>   ClockPolicy caseOf: {
>   [#acceptPlatformTime] -> [^ aDateAndTime] .
>   [#monotonicAllowDuplicates] -> [
> + posixUtcValue > LastClockTick
> + ifTrue: [LastClockTick := posixUtcValue]
> - utcValue := aDateAndTime utcMicroseconds.
> - utcValue > LastClockTick
> - ifTrue: [LastClockTick := utcValue]
>   ifFalse: [aDateAndTime utcMicroseconds: LastClockTick]] .
>   [#monotonicForceMicrosecondIncrement] -> [
> + posixUtcValue > LastClockTick
> + ifTrue: [LastClockTick := posixUtcValue]
> - utcValue := aDateAndTime utcMicroseconds.
> - utcValue > LastClockTick
> - ifTrue: [LastClockTick := utcValue]
>   ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
>   aDateAndTime utcMicroseconds: LastClockTick]] .
>   [#monotonicForceNanosecondIncrement] -> [
> + posixUtcValue > LastClockTick
> + ifTrue: [LastClockTick := posixUtcValue]
> - utcValue := aDateAndTime utcMicroseconds.
> - utcValue > LastClockTick
> - ifTrue: [LastClockTick := utcValue]
>   ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
>   aDateAndTime utcMicroseconds: LastClockTick]]
>   } otherwise: [].
> + ^aDateAndTime!
> - ^aDateAndTime
> - !
>
> Item was added:
> + ----- Method: Time class>>posixUtcMicrosecondClock (in category 'clock') -----
> + posixUtcMicrosecondClock
> + "Answer the UTC microseconds since the POSIX epoch (January 1st 1970 00:00:00 UTC)."
> +
> + ^self utcMicrosecondClock - MicrosecondsBetweenPosixEpochAndSqueakEpoch!
>
> Item was added:
> + ----- Method: Time class>>primitiveUpdateTimeZone (in category 'clock') -----
> + primitiveUpdateTimeZone
> + "Update the VMs notion of the current time zone.  The VM sets its notion
> + of the time zone once at start-up.  If one wants the VM to keep its notion
> + up-to-date arrange to invoke this primitive periodically."
> +
> + <primitive: 243>
> + ^nil "Return nil instead of self to indicate that the primitive failed."!
>
> Item was added:
> + ----- Method: Time class>>startUp: (in category 'system startup') -----
> + startUp: resuming
> +
> + resuming ifTrue: [
> + LastClockTick := 0 ]!
>
> Item was added:
> + ----- Method: Time class>>updateTimeZoneCacheAt: (in category 'clock') -----
> + updateTimeZoneCacheAt: posixUtcMicrosecondClock
> + "Tell the VM to update its cached time zone value if the POSIX UTC time reached the valute stored in UpdateVMTimeZoneCacheAt has been reached. Assume that posixUtcMicrosecondClock is an integer with the current POSIX UTC microsecond clock value. Return true when the cache was updated to indicate that the time zone may have changed."
> +
> + | updateInterval |
> + UpdateVMTimeZoneCacheAt ifNil: [
> + "Automatic update is disabled."
> + ^false ].
> + posixUtcMicrosecondClock < UpdateVMTimeZoneCacheAt ifTrue: [ ^false ].
> + self primitiveUpdateTimeZone ifNil: [
> + "The primitive failed."
> + ^false ].
> + updateInterval := 1800000000. "This could be a preference but 30 minutes matches all upcoming DST change times."
> + UpdateVMTimeZoneCacheAt := posixUtcMicrosecondClock // updateInterval + 1 * updateInterval "Round up posixUtcMicrosecondClock to the next multiple of updateInterval.".
> + ^true!
>
> Item was changed:
> + (PackageInfo named: 'Chronology-Core') postscript: '"Make sure UpdateVMTimeZoneCacheAt of Time is initialized."
> + Time classPool at: #UpdateVMTimeZoneCacheAt put: 0.
> + "Separated Time''s startup duties from DateAndTime."
> + Smalltalk addToStartUpList: Time before: DateAndTime'!
> - (PackageInfo named: 'Chronology-Core') postscript: 'DateAndTime startUp: true.
> - HashedCollection rehashAll.
> - '!
>
>



Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

marcel.taeumel
In reply to this post by commits-2
Hi Levente!

+1

Just curious: There is DaysInMonth ... where is LeapDaysInMonth ? That fixed 28 seems suspicious... :-)

Best,
Marcel

Am 03.05.2020 20:46:01 schrieb [hidden email] <[hidden email]>:

Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz

==================== Summary ====================

Name: Chronology-Core-ul.54
Author: ul
Time: 3 May 2020, 8:45:27.099465 pm
UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
Ancestors: Chronology-Core-nice.52

Automatically update the time zone cached by the VM every 30 minutes:
- copied and modified Eliot's accessor to primitive 243 to do the actual updating (Time class >> #primitiveUpdateTimeZone)
- update happens when Time class >> #posixMicrosecondClockWithOffset or Time class >> #posixMicrosecondClockWithOffset: are sent. Other low-level accessors of the VM time zone are not updating the cached time zone value (e.g. Time class >> #localMicrosecondClockPrimitive)
- actual update happens in Time class >> #updateTimeZoneCacheAt:
- postscript activates the update mechanism which can be turned off by evaluating [ Time classPool at: #UpdateVMTimeZoneCacheAt put: nil ]

Other changes:
- added Time class >> #posixUtcMicrosecondClock to have a fast way to access that raw timestamp
- added MicrosecondsBetweenPosixEpochAndSqueakEpoch to ChronologyConstants to support the above
- do not send Time class >> #initialize from DateAndTime class >> #startUp:. Let Time have its own #startUp: method instead. Postscript adds Time to the startUpList before DateAndTime.
- do not send super initialize from DateAndTime class >> #initialize
- do not send #automaticTimezone to DateAndTime on startup, because that's just an accessor with no side effects
- do not overwrite set variables in Time class >> #initialize
- assert that the value passed to Time class >> #clockPolicy: is one of the known values

=============== Diff against Chronology-Core-nice.52 ===============

Item was changed:
SharedPool subclass: #ChronologyConstants
instanceVariableNames: ''
+ classVariableNames: 'DayNames DaysInMonth MicrosecondsBetweenPosixEpochAndSqueakEpoch MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
- classVariableNames: 'DayNames DaysInMonth MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
poolDictionaries: ''
category: 'Chronology-Core'!

!ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0!
ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.!

Item was changed:
----- Method: ChronologyConstants class>>initialize (in category 'class initialization') -----
initialize
"ChronologyConstants initialize"

SqueakEpoch := 2415386. "Julian day number of 1 Jan 1901"
SecondsInDay := 86400.
SecondsInHour := 3600.
SecondsInMinute := 60.
MicrosecondsInDay := 24 * 60 * 60 * 1000000.
NanosInSecond := 10 raisedTo: 9.
NanosInMillisecond := 10 raisedTo: 6.
DayNames := #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday).

MonthNames := #( January February March April May June
July August September October November December).
+ DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31).
+ MicrosecondsBetweenPosixEpochAndSqueakEpoch := 2177452800000000!
- DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31)!

Item was changed:
----- Method: DateAndTime class>>initialize (in category 'initialize-release') -----
initialize

- super initialize.
-
ClockProvider := Time.
PosixEpochJulianDays := 2440588.
InitializeFromPrimitive := self canInitializeFromPrimitive.
Smalltalk addToStartUpList: self.
self startUp: true
!

Item was changed:
----- Method: DateAndTime class>>startUp: (in category 'system startup') -----
startUp: startingAfresh
"Set local timezone"
startingAfresh
ifTrue: [InitializeFromPrimitive := self canInitializeFromPrimitive.
Time initialize. "set LastClockTick to 0".
+ self now]!
- self now.
- self automaticTimezone]!

Item was changed:
Magnitude subclass: #Time
instanceVariableNames: 'seconds nanos'
+ classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UpdateVMTimeZoneCacheAt UseHighResClockForTiming'
- classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UseHighResClockForTiming'
poolDictionaries: 'ChronologyConstants'
category: 'Chronology-Core'!

!Time commentStamp: 'dew 10/23/2004 17:58' prior: 0!
This represents a particular point in time during any given day. For example, '5:19:45 pm'.

If you need a point in time on a particular day, use DateAndTime. If you need a duration of time, use Duration.
!

Item was changed:
----- Method: Time class>>clockPolicy: (in category 'class initialization') -----
clockPolicy: aSymbol
"When sequencial calls are made to DateAndTime now, it may be desirable to
force the system clock to be monotonic, and it may be desirable for the clock
to appear to be strictly increasing with no repeat values. The ClockPolicy
identifies which of several possible strategies to use.

Allowable values are
#acceptPlatformTime
#monotonicAllowDuplicates
#monotonicForceMicrosecondIncrement
#monotonicForceNanosecondIncrement "

+ self assert: (
+ #(
+ acceptPlatformTime
+ monotonicAllowDuplicates
+ monotonicForceMicrosecondIncrement
+ monotonicForceNanosecondIncrement) includes: aSymbol).
ClockPolicy := aSymbol!

Item was changed:
----- Method: Time class>>initialize (in category 'class initialization') -----
initialize
+ " self initialize "
- "Time initialize"

+ LastClockTick ifNil: [ LastClockTick := 0 ].
- "Initialize at startup time to protect for the case of an image saved with bad LastClockTick"
- LastClockTick := 0.

+ HighResClockTicksPerMillisecond ifNil: [ HighResClockTicksPerMillisecond := 0 ].
- HighResClockTicksPerMillisecond := 0.

+ ClockPolicy ifNil: [
+ "self clockPolicy: #acceptPlatformTime."
+ self clockPolicy: #monotonicAllowDuplicates.
+ "self clockPolicy: #monotonicForceMicrosecondIncrement."
+ "self clockPolicy: #monotonicForceNanosecondIncrement." ]!
- "self clockPolicy: #acceptPlatformTime."
- self clockPolicy: #monotonicAllowDuplicates.
- "self clockPolicy: #monotonicForceMicrosecondIncrement."
- "self clockPolicy: #monotonicForceNanosecondIncrement."
- !

Item was changed:
----- Method: Time class>>posixMicrosecondClockWithOffset (in category 'clock') -----
posixMicrosecondClockWithOffset
"Answer an array with local microseconds since the Posix epoch and the
current seconds offset from GMT in the local time zone."

+ | array posixUtcValue |
- | array utcValue |
array := self primPosixMicrosecondClockWithOffset.
+ posixUtcValue := array at: 1.
+ (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+ self primPosixMicrosecondClockWithOffset: array.
+ posixUtcValue := array at: 1 ].
ClockPolicy caseOf: {
[#acceptPlatformTime] -> [^ array] .
[#monotonicAllowDuplicates] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := array at: 1.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
ifFalse: [array at: 1 put: LastClockTick]] .
[#monotonicForceMicrosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := array at: 1.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
array at: 1 put: LastClockTick]] .
[#monotonicForceNanosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := array at: 1.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
array at: 1 put: LastClockTick]]
} otherwise: [].
+ ^array!
- ^array
-
- !

Item was changed:
----- Method: Time class>>posixMicrosecondClockWithOffset: (in category 'clock') -----
posixMicrosecondClockWithOffset: aDateAndTime
"Initialize aDateAndTime initialized with local microseconds since the Posix
epoch and the current seconds offset from GMT in the local time zone."

+ | posixUtcValue |
-
- | utcValue |
self primPosixMicrosecondClockWithOffset: aDateAndTime.
+ posixUtcValue := aDateAndTime utcMicroseconds.
+ (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+ self primPosixMicrosecondClockWithOffset: aDateAndTime .
+ posixUtcValue := aDateAndTime utcMicroseconds ].
ClockPolicy caseOf: {
[#acceptPlatformTime] -> [^ aDateAndTime] .
[#monotonicAllowDuplicates] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := aDateAndTime utcMicroseconds.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
ifFalse: [aDateAndTime utcMicroseconds: LastClockTick]] .
[#monotonicForceMicrosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := aDateAndTime utcMicroseconds.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
aDateAndTime utcMicroseconds: LastClockTick]] .
[#monotonicForceNanosecondIncrement] -> [
+ posixUtcValue > LastClockTick
+ ifTrue: [LastClockTick := posixUtcValue]
- utcValue := aDateAndTime utcMicroseconds.
- utcValue > LastClockTick
- ifTrue: [LastClockTick := utcValue]
ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
aDateAndTime utcMicroseconds: LastClockTick]]
} otherwise: [].
+ ^aDateAndTime!
- ^aDateAndTime
- !

Item was added:
+ ----- Method: Time class>>posixUtcMicrosecondClock (in category 'clock') -----
+ posixUtcMicrosecondClock
+ "Answer the UTC microseconds since the POSIX epoch (January 1st 1970 00:00:00 UTC)."
+
+ ^self utcMicrosecondClock - MicrosecondsBetweenPosixEpochAndSqueakEpoch!

Item was added:
+ ----- Method: Time class>>primitiveUpdateTimeZone (in category 'clock') -----
+ primitiveUpdateTimeZone
+ "Update the VMs notion of the current time zone. The VM sets its notion
+ of the time zone once at start-up. If one wants the VM to keep its notion
+ up-to-date arrange to invoke this primitive periodically."
+
+
+ ^nil "Return nil instead of self to indicate that the primitive failed."!

Item was added:
+ ----- Method: Time class>>startUp: (in category 'system startup') -----
+ startUp: resuming
+
+ resuming ifTrue: [
+ LastClockTick := 0 ]!

Item was added:
+ ----- Method: Time class>>updateTimeZoneCacheAt: (in category 'clock') -----
+ updateTimeZoneCacheAt: posixUtcMicrosecondClock
+ "Tell the VM to update its cached time zone value if the POSIX UTC time reached the valute stored in UpdateVMTimeZoneCacheAt has been reached. Assume that posixUtcMicrosecondClock is an integer with the current POSIX UTC microsecond clock value. Return true when the cache was updated to indicate that the time zone may have changed."
+
+ | updateInterval |
+ UpdateVMTimeZoneCacheAt ifNil: [
+ "Automatic update is disabled."
+ ^false ].
+ posixUtcMicrosecondClock < updatevmtimezonecacheat="" iftrue:="" [="" ^false="">
+ self primitiveUpdateTimeZone ifNil: [
+ "The primitive failed."
+ ^false ].
+ updateInterval := 1800000000. "This could be a preference but 30 minutes matches all upcoming DST change times."
+ UpdateVMTimeZoneCacheAt := posixUtcMicrosecondClock // updateInterval + 1 * updateInterval "Round up posixUtcMicrosecondClock to the next multiple of updateInterval.".
+ ^true!

Item was changed:
+ (PackageInfo named: 'Chronology-Core') postscript: '"Make sure UpdateVMTimeZoneCacheAt of Time is initialized."
+ Time classPool at: #UpdateVMTimeZoneCacheAt put: 0.
+ "Separated Time''s startup duties from DateAndTime."
+ Smalltalk addToStartUpList: Time before: DateAndTime'!
- (PackageInfo named: 'Chronology-Core') postscript: 'DateAndTime startUp: true.
- HashedCollection rehashAll.
- '!




Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

Levente Uzonyi
Hi Marcel,

On Mon, 4 May 2020, Marcel Taeumel wrote:

> Hi Levente!
> +1
>
> Just curious: There is DaysInMonth ... where is LeapDaysInMonth ? That fixed 28 seems suspicious... :-)

I did not change that code. It only appears in the diff because it's not
the last statement of that method anymore.
So, I don't know how leap days are handled, but if it worked before,
it'll still work.


Levente

>
> Best,
> Marcel
>
>       Am 03.05.2020 20:46:01 schrieb [hidden email] <[hidden email]>:
>
>       Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
>       http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
>
>       ==================== Summary ====================
>
>       Name: Chronology-Core-ul.54
>       Author: ul
>       Time: 3 May 2020, 8:45:27.099465 pm
>       UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
>       Ancestors: Chronology-Core-nice.52
>
>       Automatically update the time zone cached by the VM every 30 minutes:
>       - copied and modified Eliot's accessor to primitive 243 to do the actual updating (Time class >> #primitiveUpdateTimeZone)
>       - update happens when Time class >> #posixMicrosecondClockWithOffset or Time class >> #posixMicrosecondClockWithOffset: are sent. Other low-level accessors of the VM time zone are not updating the cached time zone
>       value (e.g. Time class >> #localMicrosecondClockPrimitive)
>       - actual update happens in Time class >> #updateTimeZoneCacheAt:
>       - postscript activates the update mechanism which can be turned off by evaluating [ Time classPool at: #UpdateVMTimeZoneCacheAt put: nil ]
>
>       Other changes:
>       - added Time class >> #posixUtcMicrosecondClock to have a fast way to access that raw timestamp
>       - added MicrosecondsBetweenPosixEpochAndSqueakEpoch to ChronologyConstants to support the above
>       - do not send Time class >> #initialize from DateAndTime class >> #startUp:. Let Time have its own #startUp: method instead. Postscript adds Time to the startUpList before DateAndTime.
>       - do not send super initialize from DateAndTime class >> #initialize
>       - do not send #automaticTimezone to DateAndTime on startup, because that's just an accessor with no side effects
>       - do not overwrite set variables in Time class >> #initialize
>       - assert that the value passed to Time class >> #clockPolicy: is one of the known values
>
>       =============== Diff against Chronology-Core-nice.52 ===============
>
>       Item was changed:
>       SharedPool subclass: #ChronologyConstants
>       instanceVariableNames: ''
>       + classVariableNames: 'DayNames DaysInMonth MicrosecondsBetweenPosixEpochAndSqueakEpoch MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch
>       Zero'
>       - classVariableNames: 'DayNames DaysInMonth MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
>       poolDictionaries: ''
>       category: 'Chronology-Core'!
>
>       !ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0!
>       ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.!
>
>       Item was changed:
>       ----- Method: ChronologyConstants class>>initialize (in category 'class initialization') -----
>       initialize
>       "ChronologyConstants initialize"
>
>       SqueakEpoch := 2415386. "Julian day number of 1 Jan 1901"
>       SecondsInDay := 86400.
>       SecondsInHour := 3600.
>       SecondsInMinute := 60.
>       MicrosecondsInDay := 24 * 60 * 60 * 1000000.
>       NanosInSecond := 10 raisedTo: 9.
>       NanosInMillisecond := 10 raisedTo: 6.
>       DayNames := #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday).
>
>       MonthNames := #( January February March April May June
>       July August September October November December).
>       + DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31).
>       + MicrosecondsBetweenPosixEpochAndSqueakEpoch := 2177452800000000!
>       - DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31)!
>
>       Item was changed:
>       ----- Method: DateAndTime class>>initialize (in category 'initialize-release') -----
>       initialize
>
>       - super initialize.
>       -
>       ClockProvider := Time.
>       PosixEpochJulianDays := 2440588.
>       InitializeFromPrimitive := self canInitializeFromPrimitive.
>       Smalltalk addToStartUpList: self.
>       self startUp: true
>       !
>
>       Item was changed:
>       ----- Method: DateAndTime class>>startUp: (in category 'system startup') -----
>       startUp: startingAfresh
>       "Set local timezone"
>       startingAfresh
>       ifTrue: [InitializeFromPrimitive := self canInitializeFromPrimitive.
>       Time initialize. "set LastClockTick to 0".
>       + self now]!
>       - self now.
>       - self automaticTimezone]!
>
>       Item was changed:
>       Magnitude subclass: #Time
>       instanceVariableNames: 'seconds nanos'
>       + classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UpdateVMTimeZoneCacheAt UseHighResClockForTiming'
>       - classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UseHighResClockForTiming'
>       poolDictionaries: 'ChronologyConstants'
>       category: 'Chronology-Core'!
>
>       !Time commentStamp: 'dew 10/23/2004 17:58' prior: 0!
>       This represents a particular point in time during any given day. For example, '5:19:45 pm'.
>
>       If you need a point in time on a particular day, use DateAndTime. If you need a duration of time, use Duration.
>       !
>
>       Item was changed:
>       ----- Method: Time class>>clockPolicy: (in category 'class initialization') -----
>       clockPolicy: aSymbol
>       "When sequencial calls are made to DateAndTime now, it may be desirable to
>       force the system clock to be monotonic, and it may be desirable for the clock
>       to appear to be strictly increasing with no repeat values. The ClockPolicy
>       identifies which of several possible strategies to use.
>
>       Allowable values are
>       #acceptPlatformTime
>       #monotonicAllowDuplicates
>       #monotonicForceMicrosecondIncrement
>       #monotonicForceNanosecondIncrement "
>
>       + self assert: (
>       + #(
>       + acceptPlatformTime
>       + monotonicAllowDuplicates
>       + monotonicForceMicrosecondIncrement
>       + monotonicForceNanosecondIncrement) includes: aSymbol).
>       ClockPolicy := aSymbol!
>
>       Item was changed:
>       ----- Method: Time class>>initialize (in category 'class initialization') -----
>       initialize
>       + " self initialize "
>       - "Time initialize"
>
>       + LastClockTick ifNil: [ LastClockTick := 0 ].
>       - "Initialize at startup time to protect for the case of an image saved with bad LastClockTick"
>       - LastClockTick := 0.
>
>       + HighResClockTicksPerMillisecond ifNil: [ HighResClockTicksPerMillisecond := 0 ].
>       - HighResClockTicksPerMillisecond := 0.
>
>       + ClockPolicy ifNil: [
>       + "self clockPolicy: #acceptPlatformTime."
>       + self clockPolicy: #monotonicAllowDuplicates.
>       + "self clockPolicy: #monotonicForceMicrosecondIncrement."
>       + "self clockPolicy: #monotonicForceNanosecondIncrement." ]!
>       - "self clockPolicy: #acceptPlatformTime."
>       - self clockPolicy: #monotonicAllowDuplicates.
>       - "self clockPolicy: #monotonicForceMicrosecondIncrement."
>       - "self clockPolicy: #monotonicForceNanosecondIncrement."
>       - !
>
>       Item was changed:
>       ----- Method: Time class>>posixMicrosecondClockWithOffset (in category 'clock') -----
>       posixMicrosecondClockWithOffset
>       "Answer an array with local microseconds since the Posix epoch and the
>       current seconds offset from GMT in the local time zone."
>
>       + | array posixUtcValue |
>       - | array utcValue |
>       array := self primPosixMicrosecondClockWithOffset.
>       + posixUtcValue := array at: 1.
>       + (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
>       + self primPosixMicrosecondClockWithOffset: array.
>       + posixUtcValue := array at: 1 ].
>       ClockPolicy caseOf: {
>       [#acceptPlatformTime] -> [^ array] .
>       [#monotonicAllowDuplicates] -> [
>       + posixUtcValue > LastClockTick
>       + ifTrue: [LastClockTick := posixUtcValue]
>       - utcValue := array at: 1.
>       - utcValue > LastClockTick
>       - ifTrue: [LastClockTick := utcValue]
>       ifFalse: [array at: 1 put: LastClockTick]] .
>       [#monotonicForceMicrosecondIncrement] -> [
>       + posixUtcValue > LastClockTick
>       + ifTrue: [LastClockTick := posixUtcValue]
>       - utcValue := array at: 1.
>       - utcValue > LastClockTick
>       - ifTrue: [LastClockTick := utcValue]
>       ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
>       array at: 1 put: LastClockTick]] .
>       [#monotonicForceNanosecondIncrement] -> [
>       + posixUtcValue > LastClockTick
>       + ifTrue: [LastClockTick := posixUtcValue]
>       - utcValue := array at: 1.
>       - utcValue > LastClockTick
>       - ifTrue: [LastClockTick := utcValue]
>       ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
>       array at: 1 put: LastClockTick]]
>       } otherwise: [].
>       + ^array!
>       - ^array
>       -
>       - !
>
>       Item was changed:
>       ----- Method: Time class>>posixMicrosecondClockWithOffset: (in category 'clock') -----
>       posixMicrosecondClockWithOffset: aDateAndTime
>       "Initialize aDateAndTime initialized with local microseconds since the Posix
>       epoch and the current seconds offset from GMT in the local time zone."
>
>       + | posixUtcValue |
>       -
>       - | utcValue |
>       self primPosixMicrosecondClockWithOffset: aDateAndTime.
>       + posixUtcValue := aDateAndTime utcMicroseconds.
>       + (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
>       + self primPosixMicrosecondClockWithOffset: aDateAndTime .
>       + posixUtcValue := aDateAndTime utcMicroseconds ].
>       ClockPolicy caseOf: {
>       [#acceptPlatformTime] -> [^ aDateAndTime] .
>       [#monotonicAllowDuplicates] -> [
>       + posixUtcValue > LastClockTick
>       + ifTrue: [LastClockTick := posixUtcValue]
>       - utcValue := aDateAndTime utcMicroseconds.
>       - utcValue > LastClockTick
>       - ifTrue: [LastClockTick := utcValue]
>       ifFalse: [aDateAndTime utcMicroseconds: LastClockTick]] .
>       [#monotonicForceMicrosecondIncrement] -> [
>       + posixUtcValue > LastClockTick
>       + ifTrue: [LastClockTick := posixUtcValue]
>       - utcValue := aDateAndTime utcMicroseconds.
>       - utcValue > LastClockTick
>       - ifTrue: [LastClockTick := utcValue]
>       ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
>       aDateAndTime utcMicroseconds: LastClockTick]] .
>       [#monotonicForceNanosecondIncrement] -> [
>       + posixUtcValue > LastClockTick
>       + ifTrue: [LastClockTick := posixUtcValue]
>       - utcValue := aDateAndTime utcMicroseconds.
>       - utcValue > LastClockTick
>       - ifTrue: [LastClockTick := utcValue]
>       ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
>       aDateAndTime utcMicroseconds: LastClockTick]]
>       } otherwise: [].
>       + ^aDateAndTime!
>       - ^aDateAndTime
>       - !
>
>       Item was added:
>       + ----- Method: Time class>>posixUtcMicrosecondClock (in category 'clock') -----
>       + posixUtcMicrosecondClock
>       + "Answer the UTC microseconds since the POSIX epoch (January 1st 1970 00:00:00 UTC)."
>       +
>       + ^self utcMicrosecondClock - MicrosecondsBetweenPosixEpochAndSqueakEpoch!
>
>       Item was added:
>       + ----- Method: Time class>>primitiveUpdateTimeZone (in category 'clock') -----
>       + primitiveUpdateTimeZone
>       + "Update the VMs notion of the current time zone. The VM sets its notion
>       + of the time zone once at start-up. If one wants the VM to keep its notion
>       + up-to-date arrange to invoke this primitive periodically."
>       +
>       +
>       + ^nil "Return nil instead of self to indicate that the primitive failed."!
>
>       Item was added:
>       + ----- Method: Time class>>startUp: (in category 'system startup') -----
>       + startUp: resuming
>       +
>       + resuming ifTrue: [
>       + LastClockTick := 0 ]!
>
>       Item was added:
>       + ----- Method: Time class>>updateTimeZoneCacheAt: (in category 'clock') -----
>       + updateTimeZoneCacheAt: posixUtcMicrosecondClock
>       + "Tell the VM to update its cached time zone value if the POSIX UTC time reached the valute stored in UpdateVMTimeZoneCacheAt has been reached. Assume that posixUtcMicrosecondClock is an integer with the current
>       POSIX UTC microsecond clock value. Return true when the cache was updated to indicate that the time zone may have changed."
>       +
>       + | updateInterval |
>       + UpdateVMTimeZoneCacheAt ifNil: [
>       + "Automatic update is disabled."
>       + ^false ].
>       + posixUtcMicrosecondClock < updatevmtimezonecacheat="" iftrue:="" [="" ^false="">
>       + self primitiveUpdateTimeZone ifNil: [
>       + "The primitive failed."
>       + ^false ].
>       + updateInterval := 1800000000. "This could be a preference but 30 minutes matches all upcoming DST change times."
>       + UpdateVMTimeZoneCacheAt := posixUtcMicrosecondClock // updateInterval + 1 * updateInterval "Round up posixUtcMicrosecondClock to the next multiple of updateInterval.".
>       + ^true!
>
>       Item was changed:
>       + (PackageInfo named: 'Chronology-Core') postscript: '"Make sure UpdateVMTimeZoneCacheAt of Time is initialized."
>       + Time classPool at: #UpdateVMTimeZoneCacheAt put: 0.
>       + "Separated Time''s startup duties from DateAndTime."
>       + Smalltalk addToStartUpList: Time before: DateAndTime'!
>       - (PackageInfo named: 'Chronology-Core') postscript: 'DateAndTime startUp: true.
>       - HashedCollection rehashAll.
>       - '!
>
>
>
>

Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

Chris Muller-3
In reply to this post by commits-2
Hi Levente,

This at least provides my bare-minimum requirement to be able capture "now" efficiently, thread-safely, and without garbage.

However, it still suffers, at least as badly, from that API pimple that is not only misleading but even insidious from the aspect that it would silently calculate wrong values if used in the "obvious" way.  Please "zoom out" and look at the API as a whole, the nomenclature it's employing, and *pretend you're a new user interpreting it for the first time*.  You would see:

     Time class>>#utcMicrosecondClock
     Time class>>#posixUtcMicrosecondClock

one of which can be used with the constructor on DateAndTime, which is:

    DateAndTime class>>#utcMicroseconds:offset:

There's not even a comment on that method.  So which one is the new user going to use?  And when should the offset: argument be other than 0 when doing that use-case?  These questions reveal a "hole" in the API, IMO.

You at least agreed this dichotomy is "unfortunate" -- so why can't we fix it?  I don't believe your worries about "backward compatibility".  Benchmarks don't care where they start, just the delta.  Plus, you never objected to prior API's, renames, or even the introduction of very incompatible UTCDateAndTime in the first place.  It's just a little work (and if you feel it's "bad" API, why would you have any senders anyway?).  Literally, just the other day, I went though > 150(!) test cases just to change a bunch of senders from #includesSubString: to #includesSubstring:.  Ring a bell?  You're right, it was boring, and my hands were aching afterward, but I wouldn't even consider asking you or anyone here to keep Squeak held back because of that.  You said you don't find the API fantastic, but this doesn't seem any more serious than that, so why not improve it?

There are other ways to fix it (e.g., rename #utcMicroseconds:offset: to #posixUtcMicroseconds:offset:).  I don't really care how we fix it but, IMO, we really should.  Squeak has very smart implementors, but IMO, we should care more about  -- d e s i g n --  and UX.  People say Steve Jobs was a "jerk," but I think it may be he just cared a lot about UX but found himself among those who didn't.

 - Chris

On Sun, May 3, 2020 at 1:45 PM <[hidden email]> wrote:
Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz

==================== Summary ====================

Name: Chronology-Core-ul.54
Author: ul
Time: 3 May 2020, 8:45:27.099465 pm
UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
Ancestors: Chronology-Core-nice.52

Automatically update the time zone cached by the VM every 30 minutes:
- copied and modified Eliot's accessor to primitive 243 to do the actual updating (Time class >> #primitiveUpdateTimeZone)
- update happens when Time class >> #posixMicrosecondClockWithOffset or Time class >> #posixMicrosecondClockWithOffset: are sent. Other low-level accessors of the VM time zone are not updating the cached time zone value (e.g. Time class >> #localMicrosecondClockPrimitive)
- actual update happens in Time class >> #updateTimeZoneCacheAt:
- postscript activates the update mechanism which can be turned off by evaluating [ Time classPool at: #UpdateVMTimeZoneCacheAt put: nil ]

Other changes:
- added Time class >> #posixUtcMicrosecondClock to have a fast way to access that raw timestamp
- added MicrosecondsBetweenPosixEpochAndSqueakEpoch to ChronologyConstants to support the above
- do not send Time class >> #initialize from DateAndTime class >> #startUp:. Let Time have its own #startUp: method instead. Postscript adds Time to the startUpList before DateAndTime.
- do not send super initialize from DateAndTime class >> #initialize
- do not send #automaticTimezone to DateAndTime on startup, because that's just an accessor with no side effects
- do not overwrite set variables in Time class >> #initialize
- assert that the value passed to Time class >> #clockPolicy: is one of the known values

=============== Diff against Chronology-Core-nice.52 ===============

Item was changed:
  SharedPool subclass: #ChronologyConstants
        instanceVariableNames: ''
+       classVariableNames: 'DayNames DaysInMonth MicrosecondsBetweenPosixEpochAndSqueakEpoch MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
-       classVariableNames: 'DayNames DaysInMonth MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
        poolDictionaries: ''
        category: 'Chronology-Core'!

  !ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0!
  ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.!

Item was changed:
  ----- Method: ChronologyConstants class>>initialize (in category 'class initialization') -----
  initialize
        "ChronologyConstants initialize"       

        SqueakEpoch := 2415386.                 "Julian day number of 1 Jan 1901"
        SecondsInDay := 86400.
        SecondsInHour := 3600.
        SecondsInMinute := 60.
        MicrosecondsInDay := 24 * 60 * 60 * 1000000.
        NanosInSecond := 10 raisedTo: 9.
        NanosInMillisecond := 10 raisedTo: 6.
        DayNames := #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday).

        MonthNames := #(        January February March April May June
                                                July August September October November December).
+       DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31).
+       MicrosecondsBetweenPosixEpochAndSqueakEpoch := 2177452800000000!
-       DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31)!

Item was changed:
  ----- Method: DateAndTime class>>initialize (in category 'initialize-release') -----
  initialize

-       super initialize.
-
        ClockProvider := Time.
        PosixEpochJulianDays := 2440588.
        InitializeFromPrimitive := self canInitializeFromPrimitive.
        Smalltalk addToStartUpList: self.
        self startUp: true
  !

Item was changed:
  ----- Method: DateAndTime class>>startUp: (in category 'system startup') -----
  startUp: startingAfresh
        "Set local timezone"
        startingAfresh
                ifTrue: [InitializeFromPrimitive := self canInitializeFromPrimitive.
                        Time initialize. "set LastClockTick to 0".
+                       self now]!
-                       self now.
-                       self automaticTimezone]!

Item was changed:
  Magnitude subclass: #Time
        instanceVariableNames: 'seconds nanos'
+       classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UpdateVMTimeZoneCacheAt UseHighResClockForTiming'
-       classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UseHighResClockForTiming'
        poolDictionaries: 'ChronologyConstants'
        category: 'Chronology-Core'!

  !Time commentStamp: 'dew 10/23/2004 17:58' prior: 0!
  This represents a particular point in time during any given day.  For example, '5:19:45 pm'.

  If you need a point in time on a particular day, use DateAndTime.  If you need a duration of time, use Duration.
  !

Item was changed:
  ----- Method: Time class>>clockPolicy: (in category 'class initialization') -----
  clockPolicy: aSymbol
        "When sequencial calls are made to DateAndTime now, it may be desirable to
        force the system clock to be monotonic, and it may be desirable for the clock
        to appear to be strictly increasing with no repeat values. The ClockPolicy
        identifies which of several possible strategies to use.

        Allowable values are
                #acceptPlatformTime
                #monotonicAllowDuplicates
                #monotonicForceMicrosecondIncrement
                #monotonicForceNanosecondIncrement "

+       self assert: (
+               #(
+                       acceptPlatformTime
+                       monotonicAllowDuplicates
+                       monotonicForceMicrosecondIncrement
+                       monotonicForceNanosecondIncrement) includes: aSymbol).
        ClockPolicy := aSymbol!

Item was changed:
  ----- Method: Time class>>initialize (in category 'class initialization') -----
  initialize
+       " self initialize "
-       "Time initialize"

+       LastClockTick ifNil: [ LastClockTick := 0 ].
-       "Initialize at startup time to protect for the case of an image saved with bad LastClockTick"
-       LastClockTick := 0.

+       HighResClockTicksPerMillisecond ifNil: [ HighResClockTicksPerMillisecond := 0 ].
-       HighResClockTicksPerMillisecond := 0.

+       ClockPolicy ifNil: [
+               "self clockPolicy: #acceptPlatformTime."
+               self clockPolicy: #monotonicAllowDuplicates.
+               "self clockPolicy: #monotonicForceMicrosecondIncrement."
+               "self clockPolicy: #monotonicForceNanosecondIncrement." ]!
-       "self clockPolicy: #acceptPlatformTime."
-       self clockPolicy: #monotonicAllowDuplicates.
-       "self clockPolicy: #monotonicForceMicrosecondIncrement."
-       "self clockPolicy: #monotonicForceNanosecondIncrement."
- !

Item was changed:
  ----- Method: Time class>>posixMicrosecondClockWithOffset (in category 'clock') -----
  posixMicrosecondClockWithOffset
        "Answer an array with local microseconds since the Posix epoch and the
        current seconds offset from GMT in the local time zone."

+       | array posixUtcValue |
-       | array utcValue |
        array := self primPosixMicrosecondClockWithOffset.
+       posixUtcValue := array at: 1.
+       (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+               self primPosixMicrosecondClockWithOffset: array.
+               posixUtcValue := array at: 1 ].
        ClockPolicy caseOf: {
                [#acceptPlatformTime] -> [^ array] .
                [#monotonicAllowDuplicates] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := array at: 1.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [array at: 1 put: LastClockTick]] .
                [#monotonicForceMicrosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := array at: 1.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
                                        array at: 1 put: LastClockTick]] .
                [#monotonicForceNanosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := array at: 1.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
                                        array at: 1 put: LastClockTick]]
        } otherwise: [].
+       ^array!
-       ^array
-
- !

Item was changed:
  ----- Method: Time class>>posixMicrosecondClockWithOffset: (in category 'clock') -----
  posixMicrosecondClockWithOffset: aDateAndTime
        "Initialize aDateAndTime initialized with local microseconds since the Posix
        epoch and the current seconds offset from GMT in the local time zone."

+       | posixUtcValue |
-
-       | utcValue |
        self primPosixMicrosecondClockWithOffset: aDateAndTime.
+       posixUtcValue := aDateAndTime utcMicroseconds.
+       (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+               self primPosixMicrosecondClockWithOffset: aDateAndTime .
+               posixUtcValue := aDateAndTime utcMicroseconds ].
        ClockPolicy caseOf: {
                [#acceptPlatformTime] -> [^ aDateAndTime] .
                [#monotonicAllowDuplicates] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := aDateAndTime utcMicroseconds.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [aDateAndTime utcMicroseconds: LastClockTick]] .
                [#monotonicForceMicrosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := aDateAndTime utcMicroseconds.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
                                        aDateAndTime utcMicroseconds: LastClockTick]] .
                [#monotonicForceNanosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := aDateAndTime utcMicroseconds.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
                                        aDateAndTime utcMicroseconds: LastClockTick]]
        } otherwise: [].
+       ^aDateAndTime!
-       ^aDateAndTime
- !

Item was added:
+ ----- Method: Time class>>posixUtcMicrosecondClock (in category 'clock') -----
+ posixUtcMicrosecondClock
+       "Answer the UTC microseconds since the POSIX epoch (January 1st 1970 00:00:00 UTC)."
+
+       ^self utcMicrosecondClock - MicrosecondsBetweenPosixEpochAndSqueakEpoch!

Item was added:
+ ----- Method: Time class>>primitiveUpdateTimeZone (in category 'clock') -----
+ primitiveUpdateTimeZone
+       "Update the VMs notion of the current time zone.  The VM sets its notion
+        of the time zone once at start-up.  If one wants the VM to keep its notion
+        up-to-date arrange to invoke this primitive periodically."
+       
+       <primitive: 243>
+       ^nil "Return nil instead of self to indicate that the primitive failed."!

Item was added:
+ ----- Method: Time class>>startUp: (in category 'system startup') -----
+ startUp: resuming
+
+       resuming ifTrue: [
+               LastClockTick := 0 ]!

Item was added:
+ ----- Method: Time class>>updateTimeZoneCacheAt: (in category 'clock') -----
+ updateTimeZoneCacheAt: posixUtcMicrosecondClock
+       "Tell the VM to update its cached time zone value if the POSIX UTC time reached the valute stored in UpdateVMTimeZoneCacheAt has been reached. Assume that posixUtcMicrosecondClock is an integer with the current POSIX UTC microsecond clock value. Return true when the cache was updated to indicate that the time zone may have changed."
+
+       | updateInterval |
+       UpdateVMTimeZoneCacheAt ifNil: [
+               "Automatic update is disabled."
+               ^false ].
+       posixUtcMicrosecondClock < UpdateVMTimeZoneCacheAt ifTrue: [ ^false ].
+       self primitiveUpdateTimeZone ifNil: [
+               "The primitive failed."
+               ^false ].
+       updateInterval := 1800000000. "This could be a preference but 30 minutes matches all upcoming DST change times."
+       UpdateVMTimeZoneCacheAt := posixUtcMicrosecondClock // updateInterval + 1 * updateInterval "Round up posixUtcMicrosecondClock to the next multiple of updateInterval.".
+       ^true!

Item was changed:
+ (PackageInfo named: 'Chronology-Core') postscript: '"Make sure UpdateVMTimeZoneCacheAt of Time is initialized."
+ Time classPool at: #UpdateVMTimeZoneCacheAt put: 0.
+ "Separated Time''s startup duties from DateAndTime."
+ Smalltalk addToStartUpList: Time before: DateAndTime'!
- (PackageInfo named: 'Chronology-Core') postscript: 'DateAndTime startUp: true.
- HashedCollection rehashAll.
- '!




Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

Chris Muller-3
It seems like this new API actually broadens the burden on the user to have to reckon with the internal private attribute of Chronology -- its underlying epoch.  I just want something that provides efficient access to the precision already present in the domain, but not increase the API surface area with additional concerns the user should not have to care about, and which further break encapsulation.

So, I'm -1 on this as-is.  I really think we need a better solution that moves toward Chronology being more encapsulated, instead of less.

Best,
  Chris

On Mon, May 4, 2020 at 5:19 PM Chris Muller <[hidden email]> wrote:
Hi Levente,

This at least provides my bare-minimum requirement to be able capture "now" efficiently, thread-safely, and without garbage.

However, it still suffers, at least as badly, from that API pimple that is not only misleading but even insidious from the aspect that it would silently calculate wrong values if used in the "obvious" way.  Please "zoom out" and look at the API as a whole, the nomenclature it's employing, and *pretend you're a new user interpreting it for the first time*.  You would see:

     Time class>>#utcMicrosecondClock
     Time class>>#posixUtcMicrosecondClock

one of which can be used with the constructor on DateAndTime, which is:

    DateAndTime class>>#utcMicroseconds:offset:

There's not even a comment on that method.  So which one is the new user going to use?  And when should the offset: argument be other than 0 when doing that use-case?  These questions reveal a "hole" in the API, IMO.

You at least agreed this dichotomy is "unfortunate" -- so why can't we fix it?  I don't believe your worries about "backward compatibility".  Benchmarks don't care where they start, just the delta.  Plus, you never objected to prior API's, renames, or even the introduction of very incompatible UTCDateAndTime in the first place.  It's just a little work (and if you feel it's "bad" API, why would you have any senders anyway?).  Literally, just the other day, I went though > 150(!) test cases just to change a bunch of senders from #includesSubString: to #includesSubstring:.  Ring a bell?  You're right, it was boring, and my hands were aching afterward, but I wouldn't even consider asking you or anyone here to keep Squeak held back because of that.  You said you don't find the API fantastic, but this doesn't seem any more serious than that, so why not improve it?

There are other ways to fix it (e.g., rename #utcMicroseconds:offset: to #posixUtcMicroseconds:offset:).  I don't really care how we fix it but, IMO, we really should.  Squeak has very smart implementors, but IMO, we should care more about  -- d e s i g n --  and UX.  People say Steve Jobs was a "jerk," but I think it may be he just cared a lot about UX but found himself among those who didn't.

 - Chris

On Sun, May 3, 2020 at 1:45 PM <[hidden email]> wrote:
Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz

==================== Summary ====================

Name: Chronology-Core-ul.54
Author: ul
Time: 3 May 2020, 8:45:27.099465 pm
UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
Ancestors: Chronology-Core-nice.52

Automatically update the time zone cached by the VM every 30 minutes:
- copied and modified Eliot's accessor to primitive 243 to do the actual updating (Time class >> #primitiveUpdateTimeZone)
- update happens when Time class >> #posixMicrosecondClockWithOffset or Time class >> #posixMicrosecondClockWithOffset: are sent. Other low-level accessors of the VM time zone are not updating the cached time zone value (e.g. Time class >> #localMicrosecondClockPrimitive)
- actual update happens in Time class >> #updateTimeZoneCacheAt:
- postscript activates the update mechanism which can be turned off by evaluating [ Time classPool at: #UpdateVMTimeZoneCacheAt put: nil ]

Other changes:
- added Time class >> #posixUtcMicrosecondClock to have a fast way to access that raw timestamp
- added MicrosecondsBetweenPosixEpochAndSqueakEpoch to ChronologyConstants to support the above
- do not send Time class >> #initialize from DateAndTime class >> #startUp:. Let Time have its own #startUp: method instead. Postscript adds Time to the startUpList before DateAndTime.
- do not send super initialize from DateAndTime class >> #initialize
- do not send #automaticTimezone to DateAndTime on startup, because that's just an accessor with no side effects
- do not overwrite set variables in Time class >> #initialize
- assert that the value passed to Time class >> #clockPolicy: is one of the known values

=============== Diff against Chronology-Core-nice.52 ===============

Item was changed:
  SharedPool subclass: #ChronologyConstants
        instanceVariableNames: ''
+       classVariableNames: 'DayNames DaysInMonth MicrosecondsBetweenPosixEpochAndSqueakEpoch MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
-       classVariableNames: 'DayNames DaysInMonth MicrosecondsInDay MonthNames NanosInMillisecond NanosInSecond OneDay SecondsInDay SecondsInHour SecondsInMinute SqueakEpoch Zero'
        poolDictionaries: ''
        category: 'Chronology-Core'!

  !ChronologyConstants commentStamp: 'brp 3/12/2004 14:34' prior: 0!
  ChronologyConstants is a SharedPool for the constants used by the Kernel-Chronology classes.!

Item was changed:
  ----- Method: ChronologyConstants class>>initialize (in category 'class initialization') -----
  initialize
        "ChronologyConstants initialize"       

        SqueakEpoch := 2415386.                 "Julian day number of 1 Jan 1901"
        SecondsInDay := 86400.
        SecondsInHour := 3600.
        SecondsInMinute := 60.
        MicrosecondsInDay := 24 * 60 * 60 * 1000000.
        NanosInSecond := 10 raisedTo: 9.
        NanosInMillisecond := 10 raisedTo: 6.
        DayNames := #(Sunday Monday Tuesday Wednesday Thursday Friday Saturday).

        MonthNames := #(        January February March April May June
                                                July August September October November December).
+       DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31).
+       MicrosecondsBetweenPosixEpochAndSqueakEpoch := 2177452800000000!
-       DaysInMonth := #(31 28 31 30 31 30 31 31 30 31 30 31)!

Item was changed:
  ----- Method: DateAndTime class>>initialize (in category 'initialize-release') -----
  initialize

-       super initialize.
-
        ClockProvider := Time.
        PosixEpochJulianDays := 2440588.
        InitializeFromPrimitive := self canInitializeFromPrimitive.
        Smalltalk addToStartUpList: self.
        self startUp: true
  !

Item was changed:
  ----- Method: DateAndTime class>>startUp: (in category 'system startup') -----
  startUp: startingAfresh
        "Set local timezone"
        startingAfresh
                ifTrue: [InitializeFromPrimitive := self canInitializeFromPrimitive.
                        Time initialize. "set LastClockTick to 0".
+                       self now]!
-                       self now.
-                       self automaticTimezone]!

Item was changed:
  Magnitude subclass: #Time
        instanceVariableNames: 'seconds nanos'
+       classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UpdateVMTimeZoneCacheAt UseHighResClockForTiming'
-       classVariableNames: 'ClockPolicy HighResClockTicksPerMillisecond LastClockTick UseHighResClockForTiming'
        poolDictionaries: 'ChronologyConstants'
        category: 'Chronology-Core'!

  !Time commentStamp: 'dew 10/23/2004 17:58' prior: 0!
  This represents a particular point in time during any given day.  For example, '5:19:45 pm'.

  If you need a point in time on a particular day, use DateAndTime.  If you need a duration of time, use Duration.
  !

Item was changed:
  ----- Method: Time class>>clockPolicy: (in category 'class initialization') -----
  clockPolicy: aSymbol
        "When sequencial calls are made to DateAndTime now, it may be desirable to
        force the system clock to be monotonic, and it may be desirable for the clock
        to appear to be strictly increasing with no repeat values. The ClockPolicy
        identifies which of several possible strategies to use.

        Allowable values are
                #acceptPlatformTime
                #monotonicAllowDuplicates
                #monotonicForceMicrosecondIncrement
                #monotonicForceNanosecondIncrement "

+       self assert: (
+               #(
+                       acceptPlatformTime
+                       monotonicAllowDuplicates
+                       monotonicForceMicrosecondIncrement
+                       monotonicForceNanosecondIncrement) includes: aSymbol).
        ClockPolicy := aSymbol!

Item was changed:
  ----- Method: Time class>>initialize (in category 'class initialization') -----
  initialize
+       " self initialize "
-       "Time initialize"

+       LastClockTick ifNil: [ LastClockTick := 0 ].
-       "Initialize at startup time to protect for the case of an image saved with bad LastClockTick"
-       LastClockTick := 0.

+       HighResClockTicksPerMillisecond ifNil: [ HighResClockTicksPerMillisecond := 0 ].
-       HighResClockTicksPerMillisecond := 0.

+       ClockPolicy ifNil: [
+               "self clockPolicy: #acceptPlatformTime."
+               self clockPolicy: #monotonicAllowDuplicates.
+               "self clockPolicy: #monotonicForceMicrosecondIncrement."
+               "self clockPolicy: #monotonicForceNanosecondIncrement." ]!
-       "self clockPolicy: #acceptPlatformTime."
-       self clockPolicy: #monotonicAllowDuplicates.
-       "self clockPolicy: #monotonicForceMicrosecondIncrement."
-       "self clockPolicy: #monotonicForceNanosecondIncrement."
- !

Item was changed:
  ----- Method: Time class>>posixMicrosecondClockWithOffset (in category 'clock') -----
  posixMicrosecondClockWithOffset
        "Answer an array with local microseconds since the Posix epoch and the
        current seconds offset from GMT in the local time zone."

+       | array posixUtcValue |
-       | array utcValue |
        array := self primPosixMicrosecondClockWithOffset.
+       posixUtcValue := array at: 1.
+       (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+               self primPosixMicrosecondClockWithOffset: array.
+               posixUtcValue := array at: 1 ].
        ClockPolicy caseOf: {
                [#acceptPlatformTime] -> [^ array] .
                [#monotonicAllowDuplicates] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := array at: 1.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [array at: 1 put: LastClockTick]] .
                [#monotonicForceMicrosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := array at: 1.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
                                        array at: 1 put: LastClockTick]] .
                [#monotonicForceNanosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := array at: 1.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
                                        array at: 1 put: LastClockTick]]
        } otherwise: [].
+       ^array!
-       ^array
-
- !

Item was changed:
  ----- Method: Time class>>posixMicrosecondClockWithOffset: (in category 'clock') -----
  posixMicrosecondClockWithOffset: aDateAndTime
        "Initialize aDateAndTime initialized with local microseconds since the Posix
        epoch and the current seconds offset from GMT in the local time zone."

+       | posixUtcValue |
-
-       | utcValue |
        self primPosixMicrosecondClockWithOffset: aDateAndTime.
+       posixUtcValue := aDateAndTime utcMicroseconds.
+       (self updateTimeZoneCacheAt: posixUtcValue) ifTrue: [ "Time zone may have changed: fetch again."
+               self primPosixMicrosecondClockWithOffset: aDateAndTime .
+               posixUtcValue := aDateAndTime utcMicroseconds ].
        ClockPolicy caseOf: {
                [#acceptPlatformTime] -> [^ aDateAndTime] .
                [#monotonicAllowDuplicates] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := aDateAndTime utcMicroseconds.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [aDateAndTime utcMicroseconds: LastClockTick]] .
                [#monotonicForceMicrosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := aDateAndTime utcMicroseconds.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + 1. "add one microsecond"
                                        aDateAndTime utcMicroseconds: LastClockTick]] .
                [#monotonicForceNanosecondIncrement] -> [
+                       posixUtcValue > LastClockTick
+                               ifTrue: [LastClockTick := posixUtcValue]
-                       utcValue := aDateAndTime utcMicroseconds.
-                       utcValue > LastClockTick
-                               ifTrue: [LastClockTick := utcValue]
                                ifFalse: [LastClockTick := LastClockTick + (1 / 1000). "add one nanosecond"
                                        aDateAndTime utcMicroseconds: LastClockTick]]
        } otherwise: [].
+       ^aDateAndTime!
-       ^aDateAndTime
- !

Item was added:
+ ----- Method: Time class>>posixUtcMicrosecondClock (in category 'clock') -----
+ posixUtcMicrosecondClock
+       "Answer the UTC microseconds since the POSIX epoch (January 1st 1970 00:00:00 UTC)."
+
+       ^self utcMicrosecondClock - MicrosecondsBetweenPosixEpochAndSqueakEpoch!

Item was added:
+ ----- Method: Time class>>primitiveUpdateTimeZone (in category 'clock') -----
+ primitiveUpdateTimeZone
+       "Update the VMs notion of the current time zone.  The VM sets its notion
+        of the time zone once at start-up.  If one wants the VM to keep its notion
+        up-to-date arrange to invoke this primitive periodically."
+       
+       <primitive: 243>
+       ^nil "Return nil instead of self to indicate that the primitive failed."!

Item was added:
+ ----- Method: Time class>>startUp: (in category 'system startup') -----
+ startUp: resuming
+
+       resuming ifTrue: [
+               LastClockTick := 0 ]!

Item was added:
+ ----- Method: Time class>>updateTimeZoneCacheAt: (in category 'clock') -----
+ updateTimeZoneCacheAt: posixUtcMicrosecondClock
+       "Tell the VM to update its cached time zone value if the POSIX UTC time reached the valute stored in UpdateVMTimeZoneCacheAt has been reached. Assume that posixUtcMicrosecondClock is an integer with the current POSIX UTC microsecond clock value. Return true when the cache was updated to indicate that the time zone may have changed."
+
+       | updateInterval |
+       UpdateVMTimeZoneCacheAt ifNil: [
+               "Automatic update is disabled."
+               ^false ].
+       posixUtcMicrosecondClock < UpdateVMTimeZoneCacheAt ifTrue: [ ^false ].
+       self primitiveUpdateTimeZone ifNil: [
+               "The primitive failed."
+               ^false ].
+       updateInterval := 1800000000. "This could be a preference but 30 minutes matches all upcoming DST change times."
+       UpdateVMTimeZoneCacheAt := posixUtcMicrosecondClock // updateInterval + 1 * updateInterval "Round up posixUtcMicrosecondClock to the next multiple of updateInterval.".
+       ^true!

Item was changed:
+ (PackageInfo named: 'Chronology-Core') postscript: '"Make sure UpdateVMTimeZoneCacheAt of Time is initialized."
+ Time classPool at: #UpdateVMTimeZoneCacheAt put: 0.
+ "Separated Time''s startup duties from DateAndTime."
+ Smalltalk addToStartUpList: Time before: DateAndTime'!
- (PackageInfo named: 'Chronology-Core') postscript: 'DateAndTime startUp: true.
- HashedCollection rehashAll.
- '!




Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

David T. Lewis
In reply to this post by Levente Uzonyi
On Mon, May 04, 2020 at 04:39:41PM +0200, Levente Uzonyi wrote:

> Hi Marcel,
>
> On Mon, 4 May 2020, Marcel Taeumel wrote:
>
> >Hi Levente!
> >+1
> >
> >Just curious: There is??DaysInMonth ... where is LeapDaysInMonth ? That
> >fixed 28 seems suspicious... :-)
>
> I did not change that code. It only appears in the diff because it's not
> the last statement of that method anymore.
> So, I don't know how leap days are handled, but if it worked before,
> it'll still work.
>

Oh well, don't worry too much amount the number of days in February. Even
if we get that right, we will still have to figure out the days in some
other months. Here in Michigan, we had approximately 30.96 days in the month
of March this year.

  "First load TZ-Olson from SqueakMap"
  april := DateAndTime year: 2020 month: 4 day: 1 hour: 0 minute: 0 second: 0 inTimeZoneNamed: 'America/Detroit'.
      "==> 2020-04-01T00:00:00-04:00"
  march := DateAndTime year: 2020 month: 3 day: 1 hour: 0 minute: 0 second: 0 inTimeZoneNamed: 'America/Detroit'.
      "==> 2020-03-01T00:00:00-05:00"
  monthOfMarch := april - march. "==> 30:23:00:00"
  daysInMonthOfMarch := (monthOfMarch asSeconds / (3600 * 24)). "==> (743/24)"
  daysInMonthOfMarch asFloat.  "==> 30.958333333333332"

After we get that sorted out, we can work on the number of seconds in
an hour whenever a leap second is declared.

;-)


But to be serious, the 28 days in February is used in Month class>>daysInMonth:forYear:
which checks Year class>>isLeapYear. These are Brent's original methods, and are
correct for any year ranges that we might reasonably be interested in using.

Dave


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

timrowledge


> On 2020-05-04, at 7:57 PM, David T. Lewis <[hidden email]> wrote:
>
> Here in Michigan, we had approximately 30.96 days in the month
> of March this year.

That's nothing. In the rest of the world I think we had about 3456875 days in March this time. Or as I've heard said "March was the longest decade in ages"


tim
--
tim Rowledge; [hidden email]; http://www.rowledge.org/tim
granary - old folks home



Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

David T. Lewis
In reply to this post by commits-2
On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email] wrote:

> Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
> http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
>
> ==================== Summary ====================
>
> Name: Chronology-Core-ul.54
> Author: ul
> Time: 3 May 2020, 8:45:27.099465 pm
> UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
> Ancestors: Chronology-Core-nice.52
>
> Automatically update the time zone cached by the VM every 30 minutes:


I am concerned that we are adding kludges on top of kludges, while
overlooking a very simple underlying problem.

The intent of primitiveUtcWithOffset is to answer, in a single atomic
operation, the current time UTC along with the current GMT offset. It
should be reported directly from the operating system, and not otherwise
cached or manipulated in the VM. The intended use case is for getting the
current system time and offset to be used in the creation of DateAndTime now,
nothing else.

The original and still correct implementation of ioUtcWithOffset for
Unix is a direct call to gettimeofday(). On any reasonably modern Unix
system it provides the two values directly in a single OS system call.

/* implementation of ioLocalSecondsOffset(), defined in config.h to
 * override default definition in src/vm/interp.h
 */
sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
{
  struct timeval timeval;
  if (gettimeofday(&timeval, NULL) == -1) return -1;
  time_t seconds= timeval.tv_sec;
  suseconds_t usec= timeval.tv_usec;
  *microSeconds= seconds * 1000000 + usec;
#if defined(HAVE_TM_GMTOFF)
  *offset= localtime(&seconds)->tm_gmtoff;
#else
  {
    struct tm *local= localtime(&seconds);
    struct tm *gmt= gmtime(&seconds);
    int d= local->tm_yday - gmt->tm_yday;
    int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour - gmt->tm_hour);
    int m= h * 60 + local->tm_min - gmt->tm_min;
    *offset= m * 60;
  }
#endif
  return 0;
}

The oscog code base does not have ioUtcWithOffset in platforms/Cross/vm/sq.h,
and the actual implementation of the primitive uses a workaround involving
ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
ioLocalSecondsOffset.

I am personally responsible for putting that kludge into Cog in earlier days,
and I apologize for my lapse in judgement. It is horrible, and that's my fault.

It now looks to me like we are trying to have the image keep track of current
GMT offset, and then inform the VM when it changes so that the VM can update
its cached offset value, which it then turns around and uses for my kludgy
workaround for the lack of an ioUtcWithOffset implementation.

Maybe it is time to add a proper ioUtcWithOffset, and get rid of the various
workarounds in both the VM and the image?

Dave


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

Levente Uzonyi
Hi Dave,

On Tue, 5 May 2020, David T. Lewis wrote:

> On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email] wrote:
>> Levente Uzonyi uploaded a new version of Chronology-Core to project The Inbox:
>> http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
>>
>> ==================== Summary ====================
>>
>> Name: Chronology-Core-ul.54
>> Author: ul
>> Time: 3 May 2020, 8:45:27.099465 pm
>> UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
>> Ancestors: Chronology-Core-nice.52
>>
>> Automatically update the time zone cached by the VM every 30 minutes:
>
>
> I am concerned that we are adding kludges on top of kludges, while
> overlooking a very simple underlying problem.
>
> The intent of primitiveUtcWithOffset is to answer, in a single atomic
> operation, the current time UTC along with the current GMT offset. It
> should be reported directly from the operating system, and not otherwise
> cached or manipulated in the VM. The intended use case is for getting the
> current system time and offset to be used in the creation of DateAndTime now,
> nothing else.
>
> The original and still correct implementation of ioUtcWithOffset for
> Unix is a direct call to gettimeofday(). On any reasonably modern Unix
> system it provides the two values directly in a single OS system call.
>
> /* implementation of ioLocalSecondsOffset(), defined in config.h to
> * override default definition in src/vm/interp.h
> */
> sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
> {
>  struct timeval timeval;
>  if (gettimeofday(&timeval, NULL) == -1) return -1;
>  time_t seconds= timeval.tv_sec;
>  suseconds_t usec= timeval.tv_usec;
>  *microSeconds= seconds * 1000000 + usec;
> #if defined(HAVE_TM_GMTOFF)
>  *offset= localtime(&seconds)->tm_gmtoff;
> #else
>  {
>    struct tm *local= localtime(&seconds);
>    struct tm *gmt= gmtime(&seconds);
>    int d= local->tm_yday - gmt->tm_yday;
>    int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour - gmt->tm_hour);
>    int m= h * 60 + local->tm_min - gmt->tm_min;
>    *offset= m * 60;
>  }
> #endif
>  return 0;
> }
>
> The oscog code base does not have ioUtcWithOffset in platforms/Cross/vm/sq.h,
> and the actual implementation of the primitive uses a workaround involving
> ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
> ioLocalSecondsOffset.
>
> I am personally responsible for putting that kludge into Cog in earlier days,
> and I apologize for my lapse in judgement. It is horrible, and that's my fault.
>
> It now looks to me like we are trying to have the image keep track of current
> GMT offset, and then inform the VM when it changes so that the VM can update
> its cached offset value, which it then turns around and uses for my kludgy
> workaround for the lack of an ioUtcWithOffset implementation.
>
> Maybe it is time to add a proper ioUtcWithOffset, and get rid of the various
> workarounds in both the VM and the image?

Eliot always says that asking the OS about the current time zone is too
costly for something like DateAndTime now.
I haven't benchmarked the C code, but I have a Smalltalk benchmark to
demonstrate Eliot is right:


"primitiveUtcWithOffset uses the cached time zone offset"
a := Array new: 2.
[ Time primPosixMicrosecondClockWithOffset: a ] benchFor: 1 seconds.
'28,200,000 per second. 35.5 nanoseconds per run. 0 % GC time.'.

"LocalePlugin's primitiveTimezoneOffset asks the OS about the offset via localtime()
https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/Cog/platforms/unix/plugins/LocalePlugin/sqUnixLocale.c#L675 "

l := Locale current.
[ l primTimezone ] benchFor: 1 seconds.
'773,000 per second. 1.29 microseconds per run. 0 % GC time.'


The latter takes 363 times longer according to this benchmark.


Levente

>
> Dave

Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

David T. Lewis
Hi Levente,

On Wed, May 06, 2020 at 02:39:28AM +0200, Levente Uzonyi wrote:

> Hi Dave,
>
> On Tue, 5 May 2020, David T. Lewis wrote:
>
> >On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email] wrote:
> >>Levente Uzonyi uploaded a new version of Chronology-Core to project The
> >>Inbox:
> >>http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
> >>
> >>==================== Summary ====================
> >>
> >>Name: Chronology-Core-ul.54
> >>Author: ul
> >>Time: 3 May 2020, 8:45:27.099465 pm
> >>UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
> >>Ancestors: Chronology-Core-nice.52
> >>
> >>Automatically update the time zone cached by the VM every 30 minutes:
> >
> >
> >I am concerned that we are adding kludges on top of kludges, while
> >overlooking a very simple underlying problem.
> >
> >The intent of primitiveUtcWithOffset is to answer, in a single atomic
> >operation, the current time UTC along with the current GMT offset. It
> >should be reported directly from the operating system, and not otherwise
> >cached or manipulated in the VM. The intended use case is for getting the
> >current system time and offset to be used in the creation of DateAndTime
> >now,
> >nothing else.
> >
> >The original and still correct implementation of ioUtcWithOffset for
> >Unix is a direct call to gettimeofday(). On any reasonably modern Unix
> >system it provides the two values directly in a single OS system call.
> >
> >/* implementation of ioLocalSecondsOffset(), defined in config.h to
> >* override default definition in src/vm/interp.h
> >*/
> >sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
> >{
> > struct timeval timeval;
> > if (gettimeofday(&timeval, NULL) == -1) return -1;
> > time_t seconds= timeval.tv_sec;
> > suseconds_t usec= timeval.tv_usec;
> > *microSeconds= seconds * 1000000 + usec;
> >#if defined(HAVE_TM_GMTOFF)
> > *offset= localtime(&seconds)->tm_gmtoff;
> >#else
> > {
> >   struct tm *local= localtime(&seconds);
> >   struct tm *gmt= gmtime(&seconds);
> >   int d= local->tm_yday - gmt->tm_yday;
> >   int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour -
> >   gmt->tm_hour);
> >   int m= h * 60 + local->tm_min - gmt->tm_min;
> >   *offset= m * 60;
> > }
> >#endif
> > return 0;
> >}
> >
> >The oscog code base does not have ioUtcWithOffset in
> >platforms/Cross/vm/sq.h,
> >and the actual implementation of the primitive uses a workaround involving
> >ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
> >ioLocalSecondsOffset.
> >
> >I am personally responsible for putting that kludge into Cog in earlier
> >days,
> >and I apologize for my lapse in judgement. It is horrible, and that's my
> >fault.
> >
> >It now looks to me like we are trying to have the image keep track of
> >current
> >GMT offset, and then inform the VM when it changes so that the VM can
> >update
> >its cached offset value, which it then turns around and uses for my kludgy
> >workaround for the lack of an ioUtcWithOffset implementation.
> >
> >Maybe it is time to add a proper ioUtcWithOffset, and get rid of the
> >various
> >workarounds in both the VM and the image?
>
> Eliot always says that asking the OS about the current time zone is too
> costly for something like DateAndTime now.
> I haven't benchmarked the C code, but I have a Smalltalk benchmark to
> demonstrate Eliot is right:
>
>
> "primitiveUtcWithOffset uses the cached time zone offset"
> a := Array new: 2.
> [ Time primPosixMicrosecondClockWithOffset: a ] benchFor: 1 seconds.
> '28,200,000 per second. 35.5 nanoseconds per run. 0 % GC time.'.
>
> "LocalePlugin's primitiveTimezoneOffset asks the OS about the offset via
> localtime()
> https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/Cog/platforms/unix/plugins/LocalePlugin/sqUnixLocale.c#L675 "
>
> l := Locale current.
> [ l primTimezone ] benchFor: 1 seconds.
> '773,000 per second. 1.29 microseconds per run. 0 % GC time.'
>
>
> The latter takes 363 times longer according to this benchmark.
>

It is OK to cheat if you don't get caught.

On a slow interpreter VM, with the IMHO correct primitive:

   oc := OrderedCollection new.
   stopTime := DateAndTime now + 1 second.
   [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime basicNew)) < stopTime]
    whileTrue.
   oc size.  "==> 242827"
   oc asSet size. "==> 242827"

On a fast Spur 64 VM:

   oc := OrderedCollection new.
   stopTime := DateAndTime now + 1 second.
   [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime basicNew)) < stopTime]
    whileTrue.
   oc size.  "==> 3098713"
   oc asSet size. "==> 106"

So yes it is fast if you cache the values, but the results are wrong.
That in turn leads to the perceived need for the logic in
Time class>>posixMicrosecondClockWithOffset: to artificially generate
unique time values. Those values are even more incorrect, and the overall
process is almost certainly slower than just doing it right in the first place.

Dave


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

Levente Uzonyi
Hi Dave,

On Tue, 5 May 2020, David T. Lewis wrote:

> Hi Levente,
>
> On Wed, May 06, 2020 at 02:39:28AM +0200, Levente Uzonyi wrote:
>> Hi Dave,
>>
>> On Tue, 5 May 2020, David T. Lewis wrote:
>>
>> >On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email] wrote:
>> >>Levente Uzonyi uploaded a new version of Chronology-Core to project The
>> >>Inbox:
>> >>http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
>> >>
>> >>==================== Summary ====================
>> >>
>> >>Name: Chronology-Core-ul.54
>> >>Author: ul
>> >>Time: 3 May 2020, 8:45:27.099465 pm
>> >>UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
>> >>Ancestors: Chronology-Core-nice.52
>> >>
>> >>Automatically update the time zone cached by the VM every 30 minutes:
>> >
>> >
>> >I am concerned that we are adding kludges on top of kludges, while
>> >overlooking a very simple underlying problem.
>> >
>> >The intent of primitiveUtcWithOffset is to answer, in a single atomic
>> >operation, the current time UTC along with the current GMT offset. It
>> >should be reported directly from the operating system, and not otherwise
>> >cached or manipulated in the VM. The intended use case is for getting the
>> >current system time and offset to be used in the creation of DateAndTime
>> >now,
>> >nothing else.
>> >
>> >The original and still correct implementation of ioUtcWithOffset for
>> >Unix is a direct call to gettimeofday(). On any reasonably modern Unix
>> >system it provides the two values directly in a single OS system call.
>> >
>> >/* implementation of ioLocalSecondsOffset(), defined in config.h to
>> >* override default definition in src/vm/interp.h
>> >*/
>> >sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
>> >{
>> > struct timeval timeval;
>> > if (gettimeofday(&timeval, NULL) == -1) return -1;
>> > time_t seconds= timeval.tv_sec;
>> > suseconds_t usec= timeval.tv_usec;
>> > *microSeconds= seconds * 1000000 + usec;
>> >#if defined(HAVE_TM_GMTOFF)
>> > *offset= localtime(&seconds)->tm_gmtoff;
>> >#else
>> > {
>> >   struct tm *local= localtime(&seconds);
>> >   struct tm *gmt= gmtime(&seconds);
>> >   int d= local->tm_yday - gmt->tm_yday;
>> >   int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour -
>> >   gmt->tm_hour);
>> >   int m= h * 60 + local->tm_min - gmt->tm_min;
>> >   *offset= m * 60;
>> > }
>> >#endif
>> > return 0;
>> >}
>> >
>> >The oscog code base does not have ioUtcWithOffset in
>> >platforms/Cross/vm/sq.h,
>> >and the actual implementation of the primitive uses a workaround involving
>> >ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
>> >ioLocalSecondsOffset.
>> >
>> >I am personally responsible for putting that kludge into Cog in earlier
>> >days,
>> >and I apologize for my lapse in judgement. It is horrible, and that's my
>> >fault.
>> >
>> >It now looks to me like we are trying to have the image keep track of
>> >current
>> >GMT offset, and then inform the VM when it changes so that the VM can
>> >update
>> >its cached offset value, which it then turns around and uses for my kludgy
>> >workaround for the lack of an ioUtcWithOffset implementation.
>> >
>> >Maybe it is time to add a proper ioUtcWithOffset, and get rid of the
>> >various
>> >workarounds in both the VM and the image?
>>
>> Eliot always says that asking the OS about the current time zone is too
>> costly for something like DateAndTime now.
>> I haven't benchmarked the C code, but I have a Smalltalk benchmark to
>> demonstrate Eliot is right:
>>
>>
>> "primitiveUtcWithOffset uses the cached time zone offset"
>> a := Array new: 2.
>> [ Time primPosixMicrosecondClockWithOffset: a ] benchFor: 1 seconds.
>> '28,200,000 per second. 35.5 nanoseconds per run. 0 % GC time.'.
>>
>> "LocalePlugin's primitiveTimezoneOffset asks the OS about the offset via
>> localtime()
>> https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/Cog/platforms/unix/plugins/LocalePlugin/sqUnixLocale.c#L675 "
>>
>> l := Locale current.
>> [ l primTimezone ] benchFor: 1 seconds.
>> '773,000 per second. 1.29 microseconds per run. 0 % GC time.'
>>
>>
>> The latter takes 363 times longer according to this benchmark.
>>
>
> It is OK to cheat if you don't get caught.
>
> On a slow interpreter VM, with the IMHO correct primitive:
>
>   oc := OrderedCollection new.
>   stopTime := DateAndTime now + 1 second.
>   [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime basicNew)) < stopTime]
>   whileTrue.
>   oc size.  "==> 242827"
>   oc asSet size. "==> 242827"
>
> On a fast Spur 64 VM:
>
>   oc := OrderedCollection new.
>   stopTime := DateAndTime now + 1 second.
>   [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime basicNew)) < stopTime]
>   whileTrue.
>   oc size.  "==> 3098713"
>   oc asSet size. "==> 106"
>
> So yes it is fast if you cache the values, but the results are wrong.
> That in turn leads to the perceived need for the logic in
> Time class>>posixMicrosecondClockWithOffset: to artificially generate
> unique time values. Those values are even more incorrect, and the overall
> process is almost certainly slower than just doing it right in the first place.


Right, that looks really bad. But, primitve 240 seems to do the right
thing:

oc := OrderedCollection new.
stopTime := Time utcMicrosecondClock + 1000000.
[ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
{ oc size. oc asSet size }. #(10485762 489582).

If I preallocate the right sized OrderedCollection to avoid GCs, it's
almost perfect in the sense that almost all possible values are there:

oc := OrderedCollection new: 23000000.
stopTime := Time utcMicrosecondClock + 1000000.
[ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
{ oc size. oc asSet size }.
  #(22899376 996417)

The missing values are probably the VM doing something else. Most of them
come in 2-3 microsecond long gaps starting exactly 4000 microseconds apart
from each other. The longest gap was 33 microseconds long when I measured.

So, it seems reasonable to make primitiveUtcWithOffset do the same thing
primitive 240 does to get the clock value.


Levente

>
> Dave

Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

David T. Lewis
On Wed, May 06, 2020 at 01:03:02PM +0200, Levente Uzonyi wrote:

> Hi Dave,
>
> On Tue, 5 May 2020, David T. Lewis wrote:
>
> >Hi Levente,
> >
> >On Wed, May 06, 2020 at 02:39:28AM +0200, Levente Uzonyi wrote:
> >>Hi Dave,
> >>
> >>On Tue, 5 May 2020, David T. Lewis wrote:
> >>
> >>>On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email]
> >>wrote:
> >>>>Levente Uzonyi uploaded a new version of Chronology-Core to project The
> >>>>Inbox:
> >>>>http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
> >>>>
> >>>>==================== Summary ====================
> >>>>
> >>>>Name: Chronology-Core-ul.54
> >>>>Author: ul
> >>>>Time: 3 May 2020, 8:45:27.099465 pm
> >>>>UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
> >>>>Ancestors: Chronology-Core-nice.52
> >>>>
> >>>>Automatically update the time zone cached by the VM every 30 minutes:
> >>>
> >>>
> >>>I am concerned that we are adding kludges on top of kludges, while
> >>>overlooking a very simple underlying problem.
> >>>
> >>>The intent of primitiveUtcWithOffset is to answer, in a single atomic
> >>>operation, the current time UTC along with the current GMT offset. It
> >>>should be reported directly from the operating system, and not otherwise
> >>>cached or manipulated in the VM. The intended use case is for getting the
> >>>current system time and offset to be used in the creation of DateAndTime
> >>>now,
> >>>nothing else.
> >>>
> >>>The original and still correct implementation of ioUtcWithOffset for
> >>>Unix is a direct call to gettimeofday(). On any reasonably modern Unix
> >>>system it provides the two values directly in a single OS system call.
> >>>
> >>>/* implementation of ioLocalSecondsOffset(), defined in config.h to
> >>>* override default definition in src/vm/interp.h
> >>>*/
> >>>sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
> >>>{
> >>> struct timeval timeval;
> >>> if (gettimeofday(&timeval, NULL) == -1) return -1;
> >>> time_t seconds= timeval.tv_sec;
> >>> suseconds_t usec= timeval.tv_usec;
> >>> *microSeconds= seconds * 1000000 + usec;
> >>>#if defined(HAVE_TM_GMTOFF)
> >>> *offset= localtime(&seconds)->tm_gmtoff;
> >>>#else
> >>> {
> >>>   struct tm *local= localtime(&seconds);
> >>>   struct tm *gmt= gmtime(&seconds);
> >>>   int d= local->tm_yday - gmt->tm_yday;
> >>>   int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour -
> >>>   gmt->tm_hour);
> >>>   int m= h * 60 + local->tm_min - gmt->tm_min;
> >>>   *offset= m * 60;
> >>> }
> >>>#endif
> >>> return 0;
> >>>}
> >>>
> >>>The oscog code base does not have ioUtcWithOffset in
> >>>platforms/Cross/vm/sq.h,
> >>>and the actual implementation of the primitive uses a workaround
> >>involving
> >>>ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
> >>>ioLocalSecondsOffset.
> >>>
> >>>I am personally responsible for putting that kludge into Cog in earlier
> >>>days,
> >>>and I apologize for my lapse in judgement. It is horrible, and that's my
> >>>fault.
> >>>
> >>>It now looks to me like we are trying to have the image keep track of
> >>>current
> >>>GMT offset, and then inform the VM when it changes so that the VM can
> >>>update
> >>>its cached offset value, which it then turns around and uses for my
> >>kludgy
> >>>workaround for the lack of an ioUtcWithOffset implementation.
> >>>
> >>>Maybe it is time to add a proper ioUtcWithOffset, and get rid of the
> >>>various
> >>>workarounds in both the VM and the image?
> >>
> >>Eliot always says that asking the OS about the current time zone is too
> >>costly for something like DateAndTime now.
> >>I haven't benchmarked the C code, but I have a Smalltalk benchmark to
> >>demonstrate Eliot is right:
> >>
> >>
> >>"primitiveUtcWithOffset uses the cached time zone offset"
> >>a := Array new: 2.
> >>[ Time primPosixMicrosecondClockWithOffset: a ] benchFor: 1 seconds.
> >>'28,200,000 per second. 35.5 nanoseconds per run. 0 % GC time.'.
> >>
> >>"LocalePlugin's primitiveTimezoneOffset asks the OS about the offset via
> >>localtime()
> >>https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/Cog/platforms/unix/plugins/LocalePlugin/sqUnixLocale.c#L675 "
> >>
> >>l := Locale current.
> >>[ l primTimezone ] benchFor: 1 seconds.
> >>'773,000 per second. 1.29 microseconds per run. 0 % GC time.'
> >>
> >>
> >>The latter takes 363 times longer according to this benchmark.
> >>
> >
> >It is OK to cheat if you don't get caught.
> >
> >On a slow interpreter VM, with the IMHO correct primitive:
> >
> >  oc := OrderedCollection new.
> >  stopTime := DateAndTime now + 1 second.
> >  [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime
> >  basicNew)) < stopTime]
> >   whileTrue.
> >  oc size.  "==> 242827"
> >  oc asSet size. "==> 242827"
> >
> >On a fast Spur 64 VM:
> >
> >  oc := OrderedCollection new.
> >  stopTime := DateAndTime now + 1 second.
> >  [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime
> >  basicNew)) < stopTime]
> >   whileTrue.
> >  oc size.  "==> 3098713"
> >  oc asSet size. "==> 106"
> >
> >So yes it is fast if you cache the values, but the results are wrong.
> >That in turn leads to the perceived need for the logic in
> >Time class>>posixMicrosecondClockWithOffset: to artificially generate
> >unique time values. Those values are even more incorrect, and the overall
> >process is almost certainly slower than just doing it right in the first
> >place.
>
>
> Right, that looks really bad. But, primitve 240 seems to do the right
> thing:
>
> oc := OrderedCollection new.
> stopTime := Time utcMicrosecondClock + 1000000.
> [ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
> { oc size. oc asSet size }. #(10485762 489582).
>
> If I preallocate the right sized OrderedCollection to avoid GCs, it's
> almost perfect in the sense that almost all possible values are there:
>
> oc := OrderedCollection new: 23000000.
> stopTime := Time utcMicrosecondClock + 1000000.
> [ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
> { oc size. oc asSet size }.
>  #(22899376 996417)
>
> The missing values are probably the VM doing something else. Most of them
> come in 2-3 microsecond long gaps starting exactly 4000 microseconds apart
> from each other. The longest gap was 33 microseconds long when I measured.
>
> So, it seems reasonable to make primitiveUtcWithOffset do the same thing
> primitive 240 does to get the clock value.
>

That might be just a matter of calling ioUTCMicrosecondsNow() rather than
ioUTCMicroseconds() in primitiveUtcWithOffset.

I am travelling today so I can't follow up to confirm.

It would also be worth measuring the performance of the gettimeofday()
implementation, which handles the GMT offset properly. My expectation
is that it will be plenty fast enough, but I have not measured it on Spur.

Dave


Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

David T. Lewis
On Wed, May 06, 2020 at 09:31:33AM -0400, David T. Lewis wrote:

> On Wed, May 06, 2020 at 01:03:02PM +0200, Levente Uzonyi wrote:
> > Hi Dave,
> >
> > On Tue, 5 May 2020, David T. Lewis wrote:
> >
> > >Hi Levente,
> > >
> > >On Wed, May 06, 2020 at 02:39:28AM +0200, Levente Uzonyi wrote:
> > >>Hi Dave,
> > >>
> > >>On Tue, 5 May 2020, David T. Lewis wrote:
> > >>
> > >>>On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email]
> > >>wrote:
> > >>>>Levente Uzonyi uploaded a new version of Chronology-Core to project The
> > >>>>Inbox:
> > >>>>http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
> > >>>>
> > >>>>==================== Summary ====================
> > >>>>
> > >>>>Name: Chronology-Core-ul.54
> > >>>>Author: ul
> > >>>>Time: 3 May 2020, 8:45:27.099465 pm
> > >>>>UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
> > >>>>Ancestors: Chronology-Core-nice.52
> > >>>>
> > >>>>Automatically update the time zone cached by the VM every 30 minutes:
> > >>>
> > >>>
> > >>>I am concerned that we are adding kludges on top of kludges, while
> > >>>overlooking a very simple underlying problem.
> > >>>
> > >>>The intent of primitiveUtcWithOffset is to answer, in a single atomic
> > >>>operation, the current time UTC along with the current GMT offset. It
> > >>>should be reported directly from the operating system, and not otherwise
> > >>>cached or manipulated in the VM. The intended use case is for getting the
> > >>>current system time and offset to be used in the creation of DateAndTime
> > >>>now,
> > >>>nothing else.
> > >>>
> > >>>The original and still correct implementation of ioUtcWithOffset for
> > >>>Unix is a direct call to gettimeofday(). On any reasonably modern Unix
> > >>>system it provides the two values directly in a single OS system call.
> > >>>
> > >>>/* implementation of ioLocalSecondsOffset(), defined in config.h to
> > >>>* override default definition in src/vm/interp.h
> > >>>*/
> > >>>sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
> > >>>{
> > >>> struct timeval timeval;
> > >>> if (gettimeofday(&timeval, NULL) == -1) return -1;
> > >>> time_t seconds= timeval.tv_sec;
> > >>> suseconds_t usec= timeval.tv_usec;
> > >>> *microSeconds= seconds * 1000000 + usec;
> > >>>#if defined(HAVE_TM_GMTOFF)
> > >>> *offset= localtime(&seconds)->tm_gmtoff;
> > >>>#else
> > >>> {
> > >>>   struct tm *local= localtime(&seconds);
> > >>>   struct tm *gmt= gmtime(&seconds);
> > >>>   int d= local->tm_yday - gmt->tm_yday;
> > >>>   int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour -
> > >>>   gmt->tm_hour);
> > >>>   int m= h * 60 + local->tm_min - gmt->tm_min;
> > >>>   *offset= m * 60;
> > >>> }
> > >>>#endif
> > >>> return 0;
> > >>>}
> > >>>
> > >>>The oscog code base does not have ioUtcWithOffset in
> > >>>platforms/Cross/vm/sq.h,
> > >>>and the actual implementation of the primitive uses a workaround
> > >>involving
> > >>>ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
> > >>>ioLocalSecondsOffset.
> > >>>
> > >>>I am personally responsible for putting that kludge into Cog in earlier
> > >>>days,
> > >>>and I apologize for my lapse in judgement. It is horrible, and that's my
> > >>>fault.
> > >>>
> > >>>It now looks to me like we are trying to have the image keep track of
> > >>>current
> > >>>GMT offset, and then inform the VM when it changes so that the VM can
> > >>>update
> > >>>its cached offset value, which it then turns around and uses for my
> > >>kludgy
> > >>>workaround for the lack of an ioUtcWithOffset implementation.
> > >>>
> > >>>Maybe it is time to add a proper ioUtcWithOffset, and get rid of the
> > >>>various
> > >>>workarounds in both the VM and the image?
> > >>
> > >>Eliot always says that asking the OS about the current time zone is too
> > >>costly for something like DateAndTime now.
> > >>I haven't benchmarked the C code, but I have a Smalltalk benchmark to
> > >>demonstrate Eliot is right:
> > >>
> > >>
> > >>"primitiveUtcWithOffset uses the cached time zone offset"
> > >>a := Array new: 2.
> > >>[ Time primPosixMicrosecondClockWithOffset: a ] benchFor: 1 seconds.
> > >>'28,200,000 per second. 35.5 nanoseconds per run. 0 % GC time.'.
> > >>
> > >>"LocalePlugin's primitiveTimezoneOffset asks the OS about the offset via
> > >>localtime()
> > >>https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/Cog/platforms/unix/plugins/LocalePlugin/sqUnixLocale.c#L675 "
> > >>
> > >>l := Locale current.
> > >>[ l primTimezone ] benchFor: 1 seconds.
> > >>'773,000 per second. 1.29 microseconds per run. 0 % GC time.'
> > >>
> > >>
> > >>The latter takes 363 times longer according to this benchmark.
> > >>
> > >
> > >It is OK to cheat if you don't get caught.
> > >
> > >On a slow interpreter VM, with the IMHO correct primitive:
> > >
> > >  oc := OrderedCollection new.
> > >  stopTime := DateAndTime now + 1 second.
> > >  [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime
> > >  basicNew)) < stopTime]
> > >   whileTrue.
> > >  oc size.  "==> 242827"
> > >  oc asSet size. "==> 242827"
> > >
> > >On a fast Spur 64 VM:
> > >
> > >  oc := OrderedCollection new.
> > >  stopTime := DateAndTime now + 1 second.
> > >  [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime
> > >  basicNew)) < stopTime]
> > >   whileTrue.
> > >  oc size.  "==> 3098713"
> > >  oc asSet size. "==> 106"
> > >
> > >So yes it is fast if you cache the values, but the results are wrong.
> > >That in turn leads to the perceived need for the logic in
> > >Time class>>posixMicrosecondClockWithOffset: to artificially generate
> > >unique time values. Those values are even more incorrect, and the overall
> > >process is almost certainly slower than just doing it right in the first
> > >place.
> >
> >
> > Right, that looks really bad. But, primitve 240 seems to do the right
> > thing:
> >
> > oc := OrderedCollection new.
> > stopTime := Time utcMicrosecondClock + 1000000.
> > [ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
> > { oc size. oc asSet size }. #(10485762 489582).
> >
> > If I preallocate the right sized OrderedCollection to avoid GCs, it's
> > almost perfect in the sense that almost all possible values are there:
> >
> > oc := OrderedCollection new: 23000000.
> > stopTime := Time utcMicrosecondClock + 1000000.
> > [ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
> > { oc size. oc asSet size }.
> >  #(22899376 996417)
> >
> > The missing values are probably the VM doing something else. Most of them
> > come in 2-3 microsecond long gaps starting exactly 4000 microseconds apart
> > from each other. The longest gap was 33 microseconds long when I measured.
> >
> > So, it seems reasonable to make primitiveUtcWithOffset do the same thing
> > primitive 240 does to get the clock value.
> >
>
> That might be just a matter of calling ioUTCMicrosecondsNow() rather than
> ioUTCMicroseconds() in primitiveUtcWithOffset.
>
> I am travelling today so I can't follow up to confirm.
>
> It would also be worth measuring the performance of the gettimeofday()
> implementation, which handles the GMT offset properly. My expectation
> is that it will be plenty fast enough, but I have not measured it on Spur.
>

OK, here are some actual measurements. I built a Spur64 VM locally, generating
the VMMaker code, and adding two new primitives.

The first primitive that I added is primitiveUtcWithOffsetNOW, which is identical
to primitiveUtcWithOffset except that it calls ioUTCMicrosecondsNow rather
than ioUTCMicroseconds. This is the minimal fix to produce correct sampling
for DateAndTime now.

The second primitive that I added is primitiveUtcWithOffsetDTL, which is an
exact copy of my original implementation in interpreter VM trunk. I also added
ioUtcWithOffset() from interpreter VM trunk to the support code in sqUnixMain.c.

Here are the results that I measured:

"Pre-allocate the storage"
baselinePrimitve240 := OrderedCollection new: 40000000.
usingOscogPrimitive := OrderedCollection new: 20000000.
usingOscogPrimitiveWithFix := OrderedCollection new: 20000000.
usingGettimeofdayPrimitive := OrderedCollection new: 20000000.
dt := DateAndTime basicNew. "reused by the primitives, so no allocation"

Smalltalk garbageCollect.

"Baseline - call primitive 240 for utcMicrosecondClock"
stop := Time utcMicrosecondClock + (1 second asMicroSeconds).
[ (baselinePrimitve240 add: Time utcMicrosecondClock) < stop ] whileTrue.

"Call the oscog primitive saving only small integers in the arrays"
stop := DateAndTime now + 1 second.
[ ( usingOscogPrimitive add: (Time primPosixMicrosecondClockWithOffset: dt ) utcMicroseconds ). dt < stop ] whileTrue.

"Call the oscog primitive with fix for NOW saving only small integers in the arrays"
stop := DateAndTime now + 1 second.
[ ( usingOscogPrimitiveWithFix add: (Time primPosixMicrosecondClockWithOffsetNow: dt ) utcMicroseconds ). dt < stop ] whileTrue.

"Call the gettimeofday primitive from interpreter VM saving only small integers in the arrays"
stop := DateAndTime now + 1 second.
[ ( usingGettimeofdayPrimitive add: (Time primPosixMicrosecondClockWithOffsetDTL: dt ) utcMicroseconds ). dt < stop ] whileTrue.

"Look at only the saved values"
baselinePrimitve240 := baselinePrimitve240 select: [:e | e notNil].
usingOscogPrimitive := usingOscogPrimitive select: [:e | e notNil].
usingOscogPrimitiveWithFix := usingOscogPrimitiveWithFix select: [:e | e notNil].
usingGettimeofdayPrimitive := usingGettimeofdayPrimitive select: [ :e | e notNil].

"Compare the results"
results :={
#baselinePrimitive240 -> { baselinePrimitve240 size . baselinePrimitve240 asSet size } .
#primPosixMicrosecondClockWithOffset: -> { usingOscogPrimitive size . usingOscogPrimitive asSet size }.
#primPosixMicrosecondClockWithOffsetNow: -> { usingOscogPrimitiveWithFix size . usingOscogPrimitiveWithFix asSet size }.
#primPosixMicrosecondClockWithOffsetDTL: -> { usingGettimeofdayPrimitive size . usingGettimeofdayPrimitive asSet size }
}.

results.
"==> {
        #baselinePrimitive240->#(
                21426822
                959518
        ) .
        #primPosixMicrosecondClockWithOffset:->#(
                18542112
                496
        ) .
        #primPosixMicrosecondClockWithOffsetNow:->#(
                13032095
                959842
        ) .
        #primPosixMicrosecondClockWithOffsetDTL:->#(
                692791
                692791
        )
}"



Reply | Threaded
Open this post in threaded view
|

Re: The Inbox: Chronology-Core-ul.54.mcz

David T. Lewis
Hi Levente,

Some additional measurements below, this time comparing DateAndTime now
speed for three versions of the primitive, and for different clock policy
settings.

The current primitive clearly needs to be fixed to get good sampling, and
there is minimal performance impact for the fix.

The gettimeofday() version of the primitive is much slower, but produces
correct results without caching the GMT offset. The speed difference is
larger than I expected. I am not sure which is better, I guess it depends
on how much the speed of DateAndTime now matters in practice.

In any case, this gives some numbers for comparison.

Dave

On Sat, May 09, 2020 at 04:46:55PM -0400, David T. Lewis wrote:

> On Wed, May 06, 2020 at 09:31:33AM -0400, David T. Lewis wrote:
> > On Wed, May 06, 2020 at 01:03:02PM +0200, Levente Uzonyi wrote:
> > > Hi Dave,
> > >
> > > On Tue, 5 May 2020, David T. Lewis wrote:
> > >
> > > >Hi Levente,
> > > >
> > > >On Wed, May 06, 2020 at 02:39:28AM +0200, Levente Uzonyi wrote:
> > > >>Hi Dave,
> > > >>
> > > >>On Tue, 5 May 2020, David T. Lewis wrote:
> > > >>
> > > >>>On Sun, May 03, 2020 at 06:45:52PM +0000, [hidden email]
> > > >>wrote:
> > > >>>>Levente Uzonyi uploaded a new version of Chronology-Core to project The
> > > >>>>Inbox:
> > > >>>>http://source.squeak.org/inbox/Chronology-Core-ul.54.mcz
> > > >>>>
> > > >>>>==================== Summary ====================
> > > >>>>
> > > >>>>Name: Chronology-Core-ul.54
> > > >>>>Author: ul
> > > >>>>Time: 3 May 2020, 8:45:27.099465 pm
> > > >>>>UUID: 770bbc91-fb33-46c2-a283-57f7163b672e
> > > >>>>Ancestors: Chronology-Core-nice.52
> > > >>>>
> > > >>>>Automatically update the time zone cached by the VM every 30 minutes:
> > > >>>
> > > >>>
> > > >>>I am concerned that we are adding kludges on top of kludges, while
> > > >>>overlooking a very simple underlying problem.
> > > >>>
> > > >>>The intent of primitiveUtcWithOffset is to answer, in a single atomic
> > > >>>operation, the current time UTC along with the current GMT offset. It
> > > >>>should be reported directly from the operating system, and not otherwise
> > > >>>cached or manipulated in the VM. The intended use case is for getting the
> > > >>>current system time and offset to be used in the creation of DateAndTime
> > > >>>now,
> > > >>>nothing else.
> > > >>>
> > > >>>The original and still correct implementation of ioUtcWithOffset for
> > > >>>Unix is a direct call to gettimeofday(). On any reasonably modern Unix
> > > >>>system it provides the two values directly in a single OS system call.
> > > >>>
> > > >>>/* implementation of ioLocalSecondsOffset(), defined in config.h to
> > > >>>* override default definition in src/vm/interp.h
> > > >>>*/
> > > >>>sqInt sqUnixUtcWithOffset(sqLong *microSeconds, int *offset)
> > > >>>{
> > > >>> struct timeval timeval;
> > > >>> if (gettimeofday(&timeval, NULL) == -1) return -1;
> > > >>> time_t seconds= timeval.tv_sec;
> > > >>> suseconds_t usec= timeval.tv_usec;
> > > >>> *microSeconds= seconds * 1000000 + usec;
> > > >>>#if defined(HAVE_TM_GMTOFF)
> > > >>> *offset= localtime(&seconds)->tm_gmtoff;
> > > >>>#else
> > > >>> {
> > > >>>   struct tm *local= localtime(&seconds);
> > > >>>   struct tm *gmt= gmtime(&seconds);
> > > >>>   int d= local->tm_yday - gmt->tm_yday;
> > > >>>   int h= ((d < -1 ? 24 : 1 < d ? -24 : d * 24) + local->tm_hour -
> > > >>>   gmt->tm_hour);
> > > >>>   int m= h * 60 + local->tm_min - gmt->tm_min;
> > > >>>   *offset= m * 60;
> > > >>> }
> > > >>>#endif
> > > >>> return 0;
> > > >>>}
> > > >>>
> > > >>>The oscog code base does not have ioUtcWithOffset in
> > > >>>platforms/Cross/vm/sq.h,
> > > >>>and the actual implementation of the primitive uses a workaround
> > > >>involving
> > > >>>ioUTCMicroseconds and ioLocalSecondsOffset rather than the single call to
> > > >>>ioLocalSecondsOffset.
> > > >>>
> > > >>>I am personally responsible for putting that kludge into Cog in earlier
> > > >>>days,
> > > >>>and I apologize for my lapse in judgement. It is horrible, and that's my
> > > >>>fault.
> > > >>>
> > > >>>It now looks to me like we are trying to have the image keep track of
> > > >>>current
> > > >>>GMT offset, and then inform the VM when it changes so that the VM can
> > > >>>update
> > > >>>its cached offset value, which it then turns around and uses for my
> > > >>kludgy
> > > >>>workaround for the lack of an ioUtcWithOffset implementation.
> > > >>>
> > > >>>Maybe it is time to add a proper ioUtcWithOffset, and get rid of the
> > > >>>various
> > > >>>workarounds in both the VM and the image?
> > > >>
> > > >>Eliot always says that asking the OS about the current time zone is too
> > > >>costly for something like DateAndTime now.
> > > >>I haven't benchmarked the C code, but I have a Smalltalk benchmark to
> > > >>demonstrate Eliot is right:
> > > >>
> > > >>
> > > >>"primitiveUtcWithOffset uses the cached time zone offset"
> > > >>a := Array new: 2.
> > > >>[ Time primPosixMicrosecondClockWithOffset: a ] benchFor: 1 seconds.
> > > >>'28,200,000 per second. 35.5 nanoseconds per run. 0 % GC time.'.
> > > >>
> > > >>"LocalePlugin's primitiveTimezoneOffset asks the OS about the offset via
> > > >>localtime()
> > > >>https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/Cog/platforms/unix/plugins/LocalePlugin/sqUnixLocale.c#L675 "
> > > >>
> > > >>l := Locale current.
> > > >>[ l primTimezone ] benchFor: 1 seconds.
> > > >>'773,000 per second. 1.29 microseconds per run. 0 % GC time.'
> > > >>
> > > >>
> > > >>The latter takes 363 times longer according to this benchmark.
> > > >>
> > > >
> > > >It is OK to cheat if you don't get caught.
> > > >
> > > >On a slow interpreter VM, with the IMHO correct primitive:
> > > >
> > > >  oc := OrderedCollection new.
> > > >  stopTime := DateAndTime now + 1 second.
> > > >  [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime
> > > >  basicNew)) < stopTime]
> > > >   whileTrue.
> > > >  oc size.  "==> 242827"
> > > >  oc asSet size. "==> 242827"
> > > >
> > > >On a fast Spur 64 VM:
> > > >
> > > >  oc := OrderedCollection new.
> > > >  stopTime := DateAndTime now + 1 second.
> > > >  [ (oc add: (Time primPosixMicrosecondClockWithOffset: DateAndTime
> > > >  basicNew)) < stopTime]
> > > >   whileTrue.
> > > >  oc size.  "==> 3098713"
> > > >  oc asSet size. "==> 106"
> > > >
> > > >So yes it is fast if you cache the values, but the results are wrong.
> > > >That in turn leads to the perceived need for the logic in
> > > >Time class>>posixMicrosecondClockWithOffset: to artificially generate
> > > >unique time values. Those values are even more incorrect, and the overall
> > > >process is almost certainly slower than just doing it right in the first
> > > >place.
> > >
> > >
> > > Right, that looks really bad. But, primitve 240 seems to do the right
> > > thing:
> > >
> > > oc := OrderedCollection new.
> > > stopTime := Time utcMicrosecondClock + 1000000.
> > > [ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
> > > { oc size. oc asSet size }. #(10485762 489582).
> > >
> > > If I preallocate the right sized OrderedCollection to avoid GCs, it's
> > > almost perfect in the sense that almost all possible values are there:
> > >
> > > oc := OrderedCollection new: 23000000.
> > > stopTime := Time utcMicrosecondClock + 1000000.
> > > [ (oc add: Time utcMicrosecondClock) < stopTime ] whileTrue.
> > > { oc size. oc asSet size }.
> > >  #(22899376 996417)
> > >
> > > The missing values are probably the VM doing something else. Most of them
> > > come in 2-3 microsecond long gaps starting exactly 4000 microseconds apart
> > > from each other. The longest gap was 33 microseconds long when I measured.
> > >
> > > So, it seems reasonable to make primitiveUtcWithOffset do the same thing
> > > primitive 240 does to get the clock value.
> > >
> >
> > That might be just a matter of calling ioUTCMicrosecondsNow() rather than
> > ioUTCMicroseconds() in primitiveUtcWithOffset.
> >
> > I am travelling today so I can't follow up to confirm.
> >
> > It would also be worth measuring the performance of the gettimeofday()
> > implementation, which handles the GMT offset properly. My expectation
> > is that it will be plenty fast enough, but I have not measured it on Spur.
> >
>
> OK, here are some actual measurements. I built a Spur64 VM locally, generating
> the VMMaker code, and adding two new primitives.
>
> The first primitive that I added is primitiveUtcWithOffsetNOW, which is identical
> to primitiveUtcWithOffset except that it calls ioUTCMicrosecondsNow rather
> than ioUTCMicroseconds. This is the minimal fix to produce correct sampling
> for DateAndTime now.
>
> The second primitive that I added is primitiveUtcWithOffsetDTL, which is an
> exact copy of my original implementation in interpreter VM trunk. I also added
> ioUtcWithOffset() from interpreter VM trunk to the support code in sqUnixMain.c.
>
> Here are the results that I measured:
>
> "Pre-allocate the storage"
> baselinePrimitve240 := OrderedCollection new: 40000000.
> usingOscogPrimitive := OrderedCollection new: 20000000.
> usingOscogPrimitiveWithFix := OrderedCollection new: 20000000.
> usingGettimeofdayPrimitive := OrderedCollection new: 20000000.
> dt := DateAndTime basicNew. "reused by the primitives, so no allocation"
>
> Smalltalk garbageCollect.
>
> "Baseline - call primitive 240 for utcMicrosecondClock"
> stop := Time utcMicrosecondClock + (1 second asMicroSeconds).
> [ (baselinePrimitve240 add: Time utcMicrosecondClock) < stop ] whileTrue.
>
> "Call the oscog primitive saving only small integers in the arrays"
> stop := DateAndTime now + 1 second.
> [ ( usingOscogPrimitive add: (Time primPosixMicrosecondClockWithOffset: dt ) utcMicroseconds ). dt < stop ] whileTrue.
>
> "Call the oscog primitive with fix for NOW saving only small integers in the arrays"
> stop := DateAndTime now + 1 second.
> [ ( usingOscogPrimitiveWithFix add: (Time primPosixMicrosecondClockWithOffsetNow: dt ) utcMicroseconds ). dt < stop ] whileTrue.
>
> "Call the gettimeofday primitive from interpreter VM saving only small integers in the arrays"
> stop := DateAndTime now + 1 second.
> [ ( usingGettimeofdayPrimitive add: (Time primPosixMicrosecondClockWithOffsetDTL: dt ) utcMicroseconds ). dt < stop ] whileTrue.
>
> "Look at only the saved values"
> baselinePrimitve240 := baselinePrimitve240 select: [:e | e notNil].
> usingOscogPrimitive := usingOscogPrimitive select: [:e | e notNil].
> usingOscogPrimitiveWithFix := usingOscogPrimitiveWithFix select: [:e | e notNil].
> usingGettimeofdayPrimitive := usingGettimeofdayPrimitive select: [ :e | e notNil].
>
> "Compare the results"
> results :={
> #baselinePrimitive240 -> { baselinePrimitve240 size . baselinePrimitve240 asSet size } .
> #primPosixMicrosecondClockWithOffset: -> { usingOscogPrimitive size . usingOscogPrimitive asSet size }.
> #primPosixMicrosecondClockWithOffsetNow: -> { usingOscogPrimitiveWithFix size . usingOscogPrimitiveWithFix asSet size }.
> #primPosixMicrosecondClockWithOffsetDTL: -> { usingGettimeofdayPrimitive size . usingGettimeofdayPrimitive asSet size }
> }.
>
> results.
> "==> {
> #baselinePrimitive240->#(
> 21426822
> 959518
> ) .
> #primPosixMicrosecondClockWithOffset:->#(
> 18542112
> 496
> ) .
> #primPosixMicrosecondClockWithOffsetNow:->#(
> 13032095
> 959842
> ) .
> #primPosixMicrosecondClockWithOffsetDTL:->#(
> 692791
> 692791
> )
> }"
>
>

Additional measurements comparing DateAndTime now speed for three versions
of the primitive, and for different clock policy settings:

"With current primPosixMicrosecondClockWithOffset:"

Time clockPolicy: #monotonicAllowDuplicates.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 1022"

Time clockPolicy: #acceptPlatformTime.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 874"

Time clockPolicy: #monotonicForceMicrosecondIncrement.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 1056"

Time clockPolicy: #monotonicForceNanosecondIncrement.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 9348"


"Using the primitive with ioUTCMicrosecondsNow() rather than ioUTCMicroseconds() to fix sampling error:"

Time clockPolicy: #monotonicAllowDuplicates.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 1659"

Time clockPolicy: #acceptPlatformTime.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 1482"

Time clockPolicy: #monotonicForceMicrosecondIncrement.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 1739"

Time clockPolicy: #monotonicForceNanosecondIncrement.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 10195"


"Using the gettimeofday() primitive from interpreter VM trunk:"

Time clockPolicy: #monotonicAllowDuplicates.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]].  "==> 15330"

Time clockPolicy: #acceptPlatformTime.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]].  "==> 15138"

Time clockPolicy: #monotonicForceMicrosecondIncrement.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]]. "==> 15377"

Time clockPolicy: #monotonicForceNanosecondIncrement.
Time millisecondsToRun: [10000000 timesRepeat: [DateAndTime now]].  "==> 15549"