The dialects mentioned are:
Construct | Comment | ST/X | VW | Squeak | V'Age | ObjectStudio |
#foo='foo' | Symbol = String | true | false | sq? | va? | os? |
1=0 ifTrue:[1] | value if a false ifTrue | nil | nil | sq? | va? | false |
[:n|] value:1 | value of empty block | nil | 1 | sq? | va? | nil |
Array new add:1;yourself | can add to array | #(1) (w) | ERROR | sq? | va? | #(1) |
Array new add:1 | answer of add: | 1 (w) | ERROR | sq? | va? | #(1) |
(Array new:1) at:1 put:7 | answer of at:put: | 7 | 7 | sq? | va? | #(7) |
Float
.
Double
.
ST/X provides single precision real numbers, called
ShortFloat
and double precision numbers, called Float
.
An alias named Double is provided for compatibility.
This scheme provides the best compatibility of ST/X
to all of the above dialects. If code is imported, referring to the Float class,
it will work (although the precision of the real will be higher than in VisualWorks).
ST-80 uses dictionaries (or a variant: "MethodDictionary") to
hold the selector-to-method associations.
Unless you access these instance variables directly, the protocol makes
both implementations compatible (i.e. the #methodDictionary method in ST/X
creates a true dictionary from these arrays and the #methodDictionary: method
extracts things from the supplied dictionary).
For portability, you should always use those access methods
- never directly manipulate those instance variables.
Starting with rev. 2.10, ST/X uses a a lightweight
MethodDictionary class. Its protocol looks much like
a dictionary to the outside world, but it is implemented differently
and its layout is known & understood by the VM.
You cannot change the instance layout of MethodDictionary.
String new:n withAll:Character space
this will return the same (space filled) string on all systems.
#=
) of strings does a
case insensitive compare;
'foo' = 'FoO'
-> true).
#sameAs:
message in ST/X.
'foo' = #'foo'
-> true).
It has been reported that some ST-80 programs seem to depend on this
being true, keeping references to some objects only via the dependency
relationship.
To the author, this looks like a questionable design.
For programs which depend on the ST-80 semantics, ST/X offers
an additional non-weak dependency mechanism, which is available via the
messages: #addNonWeakDependent:
, #nonWeakDependents
and #removeNonWeakDependent:
. These methods are found in the
Object
class.
However, a possible drawback is, that it makes the "become:"
operation slower
in some cases, since instead of a simple pointer exchange, the whole memory
may have to be scanned for references (this is the worst case; in many situations,
only a search through a smaller part of the memory is required).
To avoid this, most collection classes have been rewritten to avoid "become:"
,
which may make these classes less compatible for subclassing (more on this below).
It is not guaranteed, that this may hold in future versions - an experimental indirect version is planned to measure the speed (dis)advantage and decide upon these results (due to simplifications in the garbageCollector, it has still to be proved, if there is really a disadvantage).
Another possible problem is identityHashing, which cannot be based upon the
pointer (i.e. address of the object table entry) in ST/X.
To support identityHash
, ST/X reserves some bits in the object header which
contain the hash key. Since only 12 bits are currently available (in 32bit systems), hash-
collisions are to be expected in IdSets/IdDicts with more than 4096 elements
(usually, collisions occure before that many elements are added).
Notice, that more bits are available in 64bit systems, and that 32bit systems
are going to become obsolete sooner or later.
If you plan to hash heavily on instances of some new class and those hashtables
are going to be (much) larger than 4k elements, you can (should)
provide a different identityHash
implementation, which assigns unique
hashKeys (i.e. from a simple counter) to new instances and keep this hashkey in an
instance variable.
Redefine identityHash
in that class to return
the value of this instance variable.
I.e. implement:
If you plan to hash heavily on instances of existing system classes,
there is no easy fix, since the field reserved in the object header cannot
easily be made larger.
... subclass:#MyClass
...
instanceVariableNames:'... hashKey ...'
classVariableNames:'NextHashKey'
...
!MyClass class methodsFor:'initialization'!
initialize
NextHashKey := 1
! !
!MyClass methodsFor:'hashing'!
identityHash
"get my hashKey"
"my key is nil, when asked for the hashKey the very first
time. Then assign a new unique key.
When asked again, return the (now nonNil) hashKey, as assigned
previously.
"
hashKey isNil ifTrue:[
hashKey := NextHashKey.
NextHashKey := NextHashKey + 1
].
^ hashKey
!
Notice, that the expected number of hash collisions is not growing too fast; the default hash provides reasonably good behavior for sizes up to (say) 50000 elements.
Measuring code:
the code above was executed three times on a 100Mhz R4000 (32Mb SGI Indy - no 2nd level cache)
and on a 133 Mhz P5, 32Mb and 256Kb second level cache.
|set names t|
#(5000 10000 50000 100000 200000) do:[:n |
"
only want to measure the time spent in the set;
therefore, create the names before doing the timing:
"
set := IdentitySet new:n.
names := (1 to:n) collect:[:i | i printString asSymbol].
t := Time millisecondsToRun:[
names do:[:nm | set add:nm]
].
Transcript show:'with '; show:n printString; show:' elements; adding -> ';
show:t printString; show:'ms'; endEntry.
t := Time millisecondsToRun:[
names do:[:nm | set includes:nm]
].
Transcript show:' testing -> ';
show:t printString; show:'ms'; cr; endEntry.
].
"get rid of the 200000 new symbols"
set := names := nil.
ObjectMemory reclaimSymbols.
Varying the number of elements, they show the following runtime behavior
(both tests were executed with interpreted bytecode - compiled code is
slightly faster):
R4000:
Pentium:
with 5000 elements; adding -> 103ms testing -> 91ms
with 10000 elements; adding -> 234ms testing -> 195ms
with 50000 elements; adding -> 1132ms testing -> 1037ms
with 100000 elements; adding -> 5115ms testing -> 4642ms
with 200000 elements; adding -> 6455ms testing -> 4296ms
Notice, that the above execution times are more affected by memory reclamation
speed and garbage collector effects than by the raw hashing speed; which explains
the unreasonable result in the 100000 element testrun
(on the tested systems, the newSpace's size is 400k.
Thus, sets with sizes upto about 50k elements fit into it - bigger ones
are allocate in the oldSpace).
with 5000 elements; adding -> 72ms testing -> 54ms
with 10000 elements; adding -> 142ms testing -> 116ms
with 50000 elements; adding -> 764ms testing -> 578ms
with 100000 elements; adding -> 2533ms testing -> 2003ms
with 200000 elements; adding -> 2925ms testing -> 2410ms
I'd be interested in the results on other smalltalk systems.
Collection
. Instead, most collections
have an added instance variable which holds the variable part.
Collection variableSubclass:#OrderedCollection
instanceVariableNames:'index1 index2'
...
while in ST/X, the corresponding definition is:
Collection subclass:#OrderedCollection
instanceVariableNames:'contentsArray index1 index2'
...
Originally, the reason to do so was to avoid the use of #become:, which
can be expensive in direct pointer implementations (see above).
...
newCollection := OrderedCollection new:newSize.
newCollection replaceFrom:1 with:self startingAt:1.
self become:newCollection
...
while in ST/X, the code for grow is:
...
newContents := Array new:newSize.
newContents replaceFrom:1 with:contents startingAt:1.
contentsArray := newContents
...
not arguing which is more elegant, it adds some incompatibility when
subclasses of those collections are filed-in from ST-80.
To port such code to ST/X, you have to replace all
access-messages to the receiver (self) by corresponding messages to the
contents array;
...
self basicAt:index
...
has to be changed to:
...
contentsArray basicAt:index
...
#contents
method returns a copy of
the currently accumulated contents, while #reset
simply positions the
write pointer back to the beginning.
#contents
may return the contents directly
(i.e. NOT a copy) in some cases and #reset
always creates a new empty contents
collection. This has been done (in ST/X)
after investigating the typical uses of a writestream and finding out
that most are for generation of some collection via append operations (to avoid concatenation).
"thisContext sender"
),
cannot assume that these contexts are returnable or restartable.
All view processes are automatically restarted by the system, however their process priority is not restored correctly.
Other processes should be restarted in an #update
method of an object
which is a dependent of ObjectMemory. After restart, these dependents
will be notified by ObjectMemory doing a self changed:#restart
.
Late note:
With rel2.10.4 of ST/X, processes can be marked as restartable.
These processes will automatically be restarted (executing the first statement
of their creation block) when an image is snapped in.
Of course, this still does not continue the process where it left off,
but at least reliefs you from caring about image restart, installing dependents etc.
Your process should decide on some flag (instance variable of some object)
whether it has been restarted and try to continue where it left off.
However, be aware that it is not possible to restart or continue any
context objects which were created in its previous life.
ST-80 holds the class categories in a special organization object,
which keeps category vs. class relations in a dictionary-like fashion.
In that, a category may even exist (and persist) without any classes belonging to
it.
In ST/X, the classCategory is kept in an instance variable of the class
object - therefore, removing a category's last class also logically removes that
category.
As a side effect, categories created in the browser vanish, if no class is created
in that category.
The above is also true for methodCategories - here, the category is kept
in the method object - NOT in the class object (as done in ST-80).
Likewise, methodCategories vanish, if no method is ever created for it,
or the last method within a category is removed.
The reason is that in VW, a class cannot be filedIn again iff it redefines the syntax
of its methods (via #compilerClass); for example, it is not possible to
correctly transport SQL classes via fileOut-fileIn, since those redefine the compilerClass
(in a class method), which comes after the instance methods. Therefore, compilation errors
will arise during fileIn of the instance methods.
Copyright © 1995 Claus Gittinger, all rights reserved
<cg at exept.de>