The dialects mentioned are:
|#foo='foo'||Symbol = String||true||false||sq?||va?||os?|
|1=0 ifTrue:||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)|
ST/X provides single precision real numbers, called
ShortFloat and double precision numbers, called
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.
this will return the same (space filled) string on all systems.String new:n withAll:Character space
#=) 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
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
#removeNonWeakDependent:. These methods are found in the
However, a possible drawback is, that it makes the
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
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.
identityHash, ST/X reserves some bits in the object header which
contain the hash key. Since only 12 bits are currently available, hash-
collisions are to be expected in IdSets/IdDicts with more than 4096 elements
(usually, collisions occure before that many elements are added).
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
identityHash in that class to return
the value of this instance variable.
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.
instanceVariableNames:'... hashKey ...'
!MyClass class methodsFor:'initialization'!
NextHashKey := 1
"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
hashKey isNil ifTrue:[
hashKey := NextHashKey.
NextHashKey := NextHashKey + 1
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.
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.
Varying the number of elements, they shows the following runtime behavior
(both tests were executed with interpreted bytecode - compiled code is
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.
while in ST/X, the corresponding definition is:Collection variableSubclass:#OrderedCollection instanceVariableNames:'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).Collection subclass:#OrderedCollection instanceVariableNames:'contentsArray index1 index2' ...
while in ST/X, the code for grow is:... newCollection := OrderedCollection new:newSize. newCollection replaceFrom:1 with:self startingAt:1. self become:newCollection ...
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;... newContents := Array new:newSize. newContents replaceFrom:1 with:contents startingAt:1. contentsArray := newContents ...
has to be changed to:... self basicAt:index ...
... contentsArray basicAt:index ...
#contentsmethod returns a copy of the currently accumulated contents, while
#resetsimply positions the write pointer back to the beginning.
#contentsmay return the contents directly (i.e. NOT a copy) in some cases and
#resetalways 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
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 categories last class also logically removes that
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>