parent previous next question (Smalltalk Textbook 29)

Limitation

Let me talk about limitations in VisualWorks and ObjectWorks. By limitations I mean things like how many objects can exist or how large an object can be. In early implementations of Smalltalk-80, the number of objects was restricted by the size of the object table. Also, there were limits on the number of temporary valuables and literals for an object. Let's check the limitations in VisualWorks and Objectworks of modern Smalltalk.

First check the object size limitation. You can consider the number of instance variables to be the size of an object. Program 29-1 creates a message expression to define a class. The class name is 'EngiDummy' which has 256 instance variables ( I1 - I256 ), no class variables, and no pool variables. The class category of 'EngiDummy' is 'Engi-Dummy'. The program puts a message expression to define the class into a stream, then files it in to Smalltalk.


Program-29-1: (WriteStream, ReadStream; contents, fileIn, object size, 
instance variables, limits)
-------------------------------------------------------
| interval stream |
interval := 1 to: 256.
stream := WriteStream on: String new.
stream nextPutAll: 'Object subclass: #EngiDummy
instanceVariableNames: '''.
interval
        do:
                [:n |
                stream nextPutAll: 'i' , n printString.
                stream space].
stream nextPutAll: '''
classVariableNames: ''''
poolDictionaries: ''''
category: ''Engi-Dummy''!'.
(ReadStream on: stream contents) fileIn
-------------------------------------------------------

When you evaluate program 29-1, you receive an Error notifier: 'Object too large'. You have to change 'interval := 1 to: 256' to 'interval := 1 to: 255' to create the 'EngiDummy' class. So, one object can have up to 255 instance variables. This object size limitation is not defined in the language, it is just in your Smalltalk environment. The reason is that one byte is used for each instance variable as a relative offset in the byte code which is machine code for Smalltalk virtual machine.

Next, check the maximum number of temporary variables for one method. Program 29-2 creates an instance method whose message name is 'dummy'. The method defines 255 temporary variables then uses them.


Program-29-2: (WriteStream, ReadStream; contents, fileIn, nextPutAll:, 
object size, temporary variables, limits)
-----------------------------------------------------------
| interval stream |
interval := 1 to: 255.
stream := WriteStream on: String new.
stream nextPutAll: 'Object subclass: #EngiDummy
        instanceVariableNames: ''''
        classVariableNames: ''''
        poolDictionaries: ''''
        category: ''Engi-Dummy''!

!EngiDummy methodsFor: ''dummy''!

dummy
        | '.
interval
        do:
                [:n |
                stream nextPutAll: 't' , n printString.
                stream space].
stream nextPutAll: ' |'.
stream cr.
interval
        do:
                [:n |
                stream tab.
                stream nextPutAll: 't' , n printString.
                interval first = n
                        ifTrue: [stream nextPutAll: ' := nil.']
                        ifFalse:
                                [stream nextPutAll: ' := '.
                                stream nextPutAll: 't' , (n - 1) printString.
                                stream nextPutAll: '.'].
                stream cr].
stream tab.
stream nextPutAll: '^t' , interval last printString.
stream nextPutAll: '! !'.
(ReadStream on: stream contents) fileIn
-----------------------------------------------------------

Evaluating this program causes a Syntax Error. You have to change the number of temporary variable to 254 to compile it. Arguments are also counted as temporary variables, so the total number of variables defined at the beginning of a method plus the number of arguments must be less than 255.

Check the maximum number of block variables and temporary variables in block closures. Execute Program 29-3


Program-29-3: (WriteStream, ReadStream; contents, fileIn, nextPutAll:, 
object size, temporary variables, limits)
--------------------------------------------------------------
| interval stream |
interval := 1 to: 255.
stream := WriteStream on: String new.
stream nextPutAll: 'Object subclass: #EngiDummy
        instanceVariableNames: ''''
        classVariableNames: ''''
        poolDictionaries: ''''
        category: ''Engi-Dummy''!

!EngiDummy methodsFor: ''dummy''!

dummy
        ^
        [| '.
interval
        do:
                [:n |
                stream nextPutAll: 't' , n printString.
                stream space].
stream nextPutAll: ' |'.
stream cr.
interval
        do:
                [:n |
                stream tab.
                stream nextPutAll: 't' , n printString.
                interval first = n
                        ifTrue: [stream nextPutAll: ' := nil.']
                        ifFalse:
                                [stream nextPutAll: ' := '.
                                stream nextPutAll: 't' , (n - 1) printString.
                                stream nextPutAll: '.'].
                stream cr].
stream tab.
stream nextPutAll: 't' , interval last printString.
stream nextPutAll: ']! !'.
(ReadStream on: stream contents) fileIn
--------------------------------------------------------------

You should see the same error as Program 29-2. Block closures have the same limits on the number of temporary variables as methods. A method is compiled to 'CompiledMethod', a block closure is compiled to 'CompiledBlock'. Their inheritance tree is:

---------------------------------------------------------
Object ()
        CompiledCode ('bytes')
                CompiledBlock ('outerMethod')
                CompiledMethod ('mclass' 'sourceCode')
---------------------------------------------------------

Therefore, methods and block closures have the same limits. Incidentally, 'MethodContext' and 'BlockContext' are runtime environments for 'CompiledMethod' and 'CompiledBlock'. Their inheritance is:

-------------------------------------------------------------
Object ()
        InstructionClient ()
                InstructionStream ('method' 'pc')
                        Context ('sender' 'receiver' 'stackp' 'stack')
                                BlockContext ()
                                MethodContext ()
-------------------------------------------------------------

Arguments and temporary variables are allocated in the context which is created dynamically to execute a method or a block closure (like an execution stack). They are accessed by relative offset addressing of byte length.

What's the maximum number of literals for a method? Program 29-4 defines a method which has 256 string literals ( instances of 'String' ).


Program-29-4: (WriteStream, ReadStream; contents, fileIn, nextPutAll:, 
object size, temporary variables, literals, limits)
----------------------------------------------------------
| interval stream |
interval := 1 to: 256.
stream := WriteStream on: String new.
stream nextPutAll: 'Object subclass: #EngiDummy
        instanceVariableNames: ''''
        classVariableNames: ''''
        poolDictionaries: ''''
        category: ''Engi-Dummy''!

!EngiDummy methodsFor: ''dummy''!

dummy
        | t |'.
stream cr.
stream tab.
stream nextPutAll: 't := OrderedCollection new.'.
stream cr.
interval
        do:
                [:n |
                stream tab.
                stream nextPutAll: 't add: '.
                stream nextPutAll: n printString printString.
                stream nextPutAll: '.'.
                stream cr].
stream tab.
stream nextPutAll: '^t'.
stream nextPutAll: '! !'.
(ReadStream on: stream contents) fileIn
----------------------------------------------------------

The program also encounters the same error as the above programs. The number of literals for a method is limited to 255. This limitation is also caused by byte length relative offset addressing from the top of literal frame.

How about a nesting limitation for block closures. Program 29-5 creates a method which has 256 nested block closures which answer 'nil', i.e. ^[[[[[ ...[nil] value ] value ] value] value....


Program-29-5: (WriteStream, ReadStream; contents, fileIn, nextPutAll:, 
object size, temporary variables, nesting limit, limits)
----------------------------------------------------------
| interval stream |
interval := 1 to: 256.
stream := WriteStream on: String new.
stream nextPutAll: 'Object subclass: #EngiDummy
        instanceVariableNames: ''''
        classVariableNames: ''''
        poolDictionaries: ''''
        category: ''Engi-Dummy''!

!EngiDummy methodsFor: ''dummy''!

dummy
        ^'.
stream cr.
stream tab.
stream nextPutAll: '['.
stream cr.
interval
        do:
                [:n |
                stream tab.
                n timesRepeat: [stream space].
                stream nextPutAll: '['.
                interval last = n ifTrue: [stream nextPutAll: 'nil'].
                stream cr].
interval
        do:
                [:n |
                stream tab.
                interval last - n + 1 timesRepeat: [stream space].
                stream nextPutAll: '] value'.
                stream cr].
stream tab.
stream nextPutAll: '] value'.
stream nextPutAll: '! !'.
(ReadStream on: stream contents) fileIn
----------------------------------------------------------

It won't compile, because the nesting limit for block closures is 255.

You have probably noticed that many size limitations in Smalltalk are 255; this is simply due to the one-byte addressing scheme.

If you want to exceed the number of instance variables, you have to use a 'has-a' (part-of) relation instead of an 'is-a' (kind-of) relation. But I caution you against trying to overcome these limits, because it is an indicator of poor design. You might be thinking, "Hmmm, how can I make this object have 350 instance variables?" when you should instead be thinking, "Hmmm, how could this hierarchy be resigned so that this object doesn't have all these instance variables?"

The literal limit is a slightly different issue, because it is not all that uncommon to create a big table, bind it to a class variable, and then initialize it with more than 256 literals. It is possible to be able to create a string with 'storeOn:' or 'storeString', that you can't re-compile! The solution is to store literals onto a stream and then, instead of using the 'file-in' method to recreate objects, parse the stream using 'Scanner' or 'scanTokens:' of 'Parser'.

Finally, what is the maximum number of objects in Smalltalk? Actually there is no limit. Smalltalk can create as many objects as memory allows. Let's check your environment. Program 29-6 creates array objects whose size varies from zero to a million.


Program-29-6: (Interval, Array; object limit, limits)
------------------------------------------------------------------
| interval |
interval := 0 to: 1000000 by: 10000.
interval
        do:
                [:n |
                Transcript cr; show: n printString.
                Array new: n]
------------------------------------------------------------------

At first execution proceeds smoothly, then garbage collection starts to be noticable, and finally you'll get an error 'a primitive has failed' or 'No space available to allocate this object' indicating memory is full. If you have lots of memory, you can limit Smalltalk memory before executing Program 29-6 (On the Mac, use 'Get Info.' If you're not using a Mac, then sell your machine and buy a Mac). My Smalltalk memory size is 10M, and I can create arrays of up to 500,000. As another datapoint, a PowerBook 5300 with 10m Smalltalk memory handles arrays of about 280000.

There are other limits besides the ones we covered, but these are the ones you are most likely to encounter.


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves