[comp.lang.eiffel] Benchmark article - Eiffel vs. C++ vs. Smalltalk-80

kimr@eiffel.UUCP (Kim Rochat) (03/23/90)

The following article, based on a presentation at the Eiffel User's group
meeting last October,  was intended for the Eiffel User's Group newletter.
Circumstances have prevented its being published to date but inquiries on the 
net about Eiffel performance prompt me to post it here.

When this article was prepared, the authors were employed by Tektronix,
Beaverton, Oregon.

Kim Rochat
kimr@eiffel.com

-------------------------------------------------------------------------------
		A Comparison of Mosaic in Three Languages

			
			Kim Rochat
			Interactive Software, Inc.
			270 Storke Rd. Suite 7
			Goleta, CA 93117
			E-mail: kimr@eiffel.com

			Roxanna Rochat
			Interactive Software, Inc.
			270 Storke Rd. Suite 7
			Goleta, CA 93117
			E-mail: roxier@eiffel.com


Introduction

Mosaic is a small program originally written in Smalltalk-80(TM) to
generate patterns for mosaic knitting.  The program has subsequently
been manually translated to Eiffel(TM) and C++.  This article presents
performance benchmarks and observations about the three implementations,
as well as complete listings of the program code.  It is shown that the 
choice of C compiler used with Eiffel can affect execution speed by up 
to 30 percent.


Mosaic Description

The Mosaic program generates a pattern for mosaic knitting.  The
pattern is rectangular, consisting of a specific number of rows, each
having the same number of stitches.  Each stitch may be one of two
colors.  The base color of each row alternates.  The first and last
stitch of each row must be the base color.  The intermediate stitches
in the first row have their colors determined randomly to start off the
pattern.  Intermediate stitches in following rows are the base color
unless the corresponding stitch in the row before is of the contrasting
color, in which case the contrasting color may be pulled up to the
current row.  No more than two adjacent stitches may be of the
contrasting color.  In each case where the contrasting color might
appear in a row, a random decision is made whether to pull the stitch
up from the preceding row or not.


Mosaic Algorithm

Each of the three programs uses an array of integers to represent a row
of stitches.  Each stitch is represented by the integer zero for a
white stitch, one for a black stitch.  A mosaic pattern is an array of
rows.  The pattern is displayed by printing out the integer values for
each stitch, one row per line.

The programs are written to dynamically adapt to the number of rows and 
stitches supplied by the user at execution time. To simplify the 
comparison by eliminating command line interface code, the number
of rows and stitches are hard coded into the programs shown here.

Each program dynamically allocates the first row, selects a base color,
and randomly fills in the intermediate stitches with the contrasting
color.  Additional rows are dynamically allocated one at a time, each
row having its stitches filled in based on the stitches in the previous
row.

Two primary classes are used.  One class (Mosaic or RowArray, depending
on the program) contains a dynamically sized array of row objects and
operations to create and print rows.

Row objects contain a dynamically sized integer array representing the
stitches in the row and the operations to fill in the stitches based on
the stitches in a previous row.


History

The Smalltalk program provided is a subset of one written to generate
actual knitting designs.  The original program was designed to use
graphics and allow interactive control over the pattern generation.

The solution, although expressed in an object-oriented language, does
not make use of polymorphism or garbage collection.  In fact, it would
have been about as easy to write a solution in a procedural language,
such as C.  While the mosaic program doesn't exercise method lookup or
other object-oriented features, it does test fundamental behavior of
almost any program; integer and array operations.

Once we had the Smalltalk implementation, we tried to preserve its
semantics during translation to Eiffel and C++ to provide a fair
comparison of the three languages.  Any of the implementations could be
tuned to provide better performance, but we tried to keep the
comparison more equal by using the same obvious algorithm. 

C++ Implementation Observations

We wanted a class which was a dynamically sized array of integers.
Since C++ lacks a generic array class, we wrote an intArray class.

The implementation of intArray uses a pointer to access the dynamically
allocated storage for the array.  An indirection could be eliminated in
accessing array elements if the array were stored in the intArray class
itself.  This can be accomplished by having the class allocate space
for itself, a common technique involving assigning to "this" allowed in
Cfront version 1.2 but considered an anachronism in Cfront 2.0.

Eiffel Implementation Observations

The external C function "random()" is used by the Eiffel implementation
to generate random colors.  The class RANDOM encapsulates this C
routine, and is used by both the MOSAIC class to determine the base
color of the first row and by the ROW class to determine what color a
stitch should be.  Since this was our first Eiffel program, we
experimented with two techniques for gaining access to the features of
RANDOM.

In ROW, we simply inherited from RANDOM.  In MOSAIC, we used a "once"
routine to create a single instance of the RANDOM class which could be
used subsequently.  Of course, it's only called once in MOSAIC. Another
choice would have been to declare an attribute of type RANDOM in MOSAIC
and called the Create routine for that attribute in MOSAIC's Create routine.

It seems stylistically preferable to us to use the "once" function to
declare and create an instance of an object for use by a class than to
divide the declaration of an attribute from its creation.  To avoid the
use of inheritance for a "uses" relationship, ROW should be rewritten
to have a once routine which returns an instance of RANDOM rather than
to inherit from RANDOM.


Source Size Comparisons

We compared the source code size of the three implementations, but
didn't arrive at a meaningful conclusion.  The number of code lines is
determined more by the formatting style than by the actual length of
the program.  Excluding command and blank lines, the listings contain 
around 100 lines of code for Smalltalk and about 150 lines each for 
Eiffel and C++.

For those interested in metrics concerning tokens (identifiers and
symbols), there are 421 for Smalltalk, 518 for Eiffel, and 788 for C++.
The only conclusion we feel safe drawing is that the C++ source code is
larger than Eiffel or Smalltalk due to the need to implement an
additional class (intArray) and the redundant function declarations in
the header file.


Performance Comparisons

The three implementations of Mosaic were compiled and executed on two
68020 workstations using a variety of compilers.  Figure 1 shows the
results of these measurements with statistics reported by the Unix
"time" command.  In each case, a design consisting of 1000 rows with
1000 stitches per row was created.  While impractical, this size was
chosen for benchmarking to increase the execution time relative to the
measurement increment.  To avoid biasing the measurements with the I/O
overhead of the workstation, the resulting pattern was not printed.

-------------------------------------------------------------------------------
                Figure 1:  Mosaic Benchmark Results

Host  Language[2]  Compiler[3]  Execution Time (secs)[4]  Executable Size[5]
[1]                             User  System    Wall      Text    Data    BSS
----  -----------  -----------  ----  ------    ----      -----   ----    -----
Tek     Smalltalk  Tek ST-80                    3600
Tek     Eiffel     Greenhills   69.2    1.0       70      40960   11264   36004
Tek     C++        CC1.2/GH     55.3    1.7       57      15360    5120   34508
Tek     C++        CC1.2/gcc    54.2    1.3       56      15360    5120   34498
Tek     C++        g++          52.9    1.3       54      20480    8192   32056
Tek     Eiffel     gcc          49.9    1.4       51      39936   11264   35616

Sun     Eiffel     Sun          54.7    1.0       56      65535    8192    3872
Sun     C++        CC2.0/gcc    41.3    1.2       42      32768    8192       0
Sun     C++        g++          40.6    0.9       41      40960    8192       0
Sun     Eiffel     gcc          40.4    1.1       41      65535    8192     536
Sun     C++        CC2.0/cc     39.9    1.3       41      40960    8192       0

Notes:
1) Platforms are Sun-3/60 running SunOS 4.0.3 and Tektronix 4317 running 
   UTek 4.1.  Both have 12Mb RAM.
2) Eiffel version 2.1 was used.  All checking was turned off and C package 
   generation always used.
3) The g++ compiler was version 1.35.1-.  The gcc compiler was version 1.35.  
   The Greenhills compiler was version 1.8.3A.  Cfront version 1.2 was
   used with the Greenhills and GNU C compilers.  Cfront version 2.0 was
   used with the GNU and Sun C compilers. All compilers were run with the -O 
   option.  Smalltalk was Tektronix Smalltalk version 2.3.0a.
4) All times in seconds as reported by the csh time command for the second 
   execution of the program.
5) All sizes in decimal bytes as reported by the size command.  Sun executables
   were statically linked.
-------------------------------------------------------------------------------

Execution Times

Benchmarking at its best is an uncertain activity, since you are never
sure exactly what you're measuring.  This article intends to provide
enough information for others to duplicate the benchmark results.

If you aren't familiar with the UNIX(TM) "time" command, the wall clock
time in seconds is the easiest one to use for comparison.  User time is
the time the process spent executing user code.  System time is the
amount of time spent in the kernel.  Since loading a program can take a
noticeable time, each program was executed twice in sequence and the
time from the second execution was used.  This allowed the kernel disk
buffer to be used for the second program load and avoided accessing
the disk.

Much of the work done by mosaic for both C++ and Eiffel is in the C
library "random" routine.  This library routine is common to all C++
and Eiffel executions on a host, making even more remarkable the
magnitude of the performance differences seen with Eiffel.


C++ Execution Time

The time required to execute the program in C++ is pretty much the same
on a given host regardless of the compiler used.  CC 1.2 and CC 2.0 are
two versions of the AT&T "cfront" C++ translator, which emits C code.
Gcc is the Free Software Foundation's C compiler, and g++ is the FSF
native code C++ compiler.  In addition, the standard workstation
compilers were used with cfront, the Sun C compiler for Sun and the
Greenhills compiler for Tektronix.


Eiffel Execution Time

Using the GNU C compiler instead of the C compiler supplied with the
workstation the mosaic program executes about 30 percent faster.  We
have not analyzed the generated code to determine the cause of the
performance difference.  It may simply be that GNU compiles the
specific code used by Eiffel for array accesses more efficiently.  In
other C applications we have observed that the GNU C compiler provides
a 10 to 20 percent speedup over the C compiler supplied with the
workstation.

Note that Eiffel is just as fast as C++ on the Sun and faster than C++
on the Tektronix workstation.  This seems ironic since C++ is touted as
being efficient and the mosaic program exercises features which should
be efficiently implemented in C.

Smalltalk Execution Time

We expected Smalltalk to be about ten times slower than the compiled
languages and were surprised to find it significantly slower.
Profiling showed that 85 percent of the time was spent computing random
numbers.  Modifying the method or replacing it with a user primitive
would result in performance numbers nearer those expected.  Normally,
mosaic patterns are relatively small.  Smalltalk's performance is
entirely adequate for these cases.

Code Sizes

The code sizes reported by the UNIX "size" command are included, but must
be interpreted carefully.  The sizes reported for the Sun are multiples
of 8K-bytes, and so are only accurate to that increment.  While it
appears that Eiffel code is twice as large as C++ code on the Tektronix
machine, this may be due to a 20K-byte constant overhead in the Eiffel
runtime system.

Conclusions

While bearing in mind that mosaic is a small program exercising a
limited subset of the languages, two major conclusions can be drawn.
First, any perception that C++ has superior performance to Eiffel may
be invalid.  Second, if you're using Eiffel, a different C compiler may
result in significantly increased performance.

By itself, mosaic is too small to obtain reliable measurements for code
complexity, reuse, maintenance and development time, although informal
comparisons of the programs may be useful.  In combination with similar
exercises, a better picture of the strengths and weaknesses of each
language can be achieved.

Resources

For information on the GNU compiler products, contact:

		    Free Software Foundation
		    675 Mass Ave
		    Cambridge, MA 02139


-------------------------------------------------------------------------------
Smalltalk-80 is a trademark of ParcPlace Systems, Inc.
Eiffel is a trademark of Interactive Software Engineering, Inc.
UNIX is a trademark of AT&T.



                Listing 1: Mosaic program in Smalltalk-80.

Array variableSubclass: #Row
        instanceVariableNames: 'previousRow baseColor'
        classVariableNames: 'Rand'
        poolDictionaries: ''
        category: 'Mosaic-Support'

Row comment: 'This class is an Array of Integers (0 and 1) representing a row of two-colored knitting stitches in a mosaic block.

Instance variables:

previousRow             <Row>           the row before this one 

baseColor               <anInteger>     1=black; 0=white as in Form masks.  
                                        The baseColor for this row.


Class variables:

Rand            <Random> number generator for determining the color of unconstrained stitches
'


Row methodsFor: 'accessing'

baseColor
        "1=black; 0=white"

        ^baseColor


Row methodsFor: 'next row generation'

nextRow
        ^self class new: self size lastRow: self blackOrWhite: self contrastColor


Row methodsFor: 'color accessing'

addBaseColorAt: index
        self at: index put: self baseColor

addContrastColorAt: index
        self at: index put: self contrastColor

addRandomColorAt: index
        Rand next < 0.5
                ifTrue: [self addBaseColorAt: index]
                ifFalse: [self addContrastColorAt: index].

contrastColor
        "Assuming the colors are 0 and 1, xor the baseColor with 1 to produce the contrasting color."

        ^(self baseColor bitXor: 1)

contrastColorCanAppearAt: index
        "Indexes 1..index-1 of the receiver have been filled.
        Answer true only if adding a contrast color at the specified index would not violate the following constraints:
                1) the first and last stitches must be the base color.
                3) no more than two adjoining contrast colors
                2) the contrast color must appear in the same spot in the row below since it has to be slipped,
        Otherwise answer false."

        (index <= 1 or: [index >= self size]) 
                ifTrue: [^false].
        "Assert: index is in the range 2 .. self size - 1."
        (previousRow at: index) = self contrastColor
                ifFalse: [^false].
        "Assert: the previous row contains the contrasting color at this index."
        index < 4 
                ifTrue: 
                        ["Assert: index is 2 or 3; no more than 2 consecutive contrasting colors appear."
                        ^true].
        (self at: index - 1) = self baseColor
                ifTrue: 
                        [^true].
        "Assert: previous color was a contrast color.  
        Check the one before that.  If it was the baseColor, answer true; false otherwise."
        ^(self at: index - 2) = self baseColor


Row methodsFor: 'row accessing'

firstRow
        "This is the first row.  Generate a random pattern for all but the first and last stitches which have to be the baseColor."

        self initRow.
        2 to: self size - 1 do: [:index |
                self addRandomColorAt: index]

initRow
        "Fill in the first and last stitches, constrained to be the baseColor."

        self addBaseColorAt: 1.
        self addBaseColorAt: self size

subsequentRow
        "This is not the first row.  Generate a constrained yet random pattern for all but the first and last stitches which have to be the baseColor."

        self initRow.
        2 to: self size - 1 do: [:index |
                (self contrastColorCanAppearAt: index)
                        ifTrue: [self addRandomColorAt: index]
                        ifFalse: [self addBaseColorAt: index]].

Row methodsFor: 'private'

setLastRow: lastRow blackOrWhite: flag
        "1=black; 0=white"

        previousRow _ lastRow.
        baseColor _ flag.
        Rand _ Random new.
        previousRow isNil
                ifTrue: [self firstRow]
                ifFalse: [self subsequentRow]



Row class methodsFor: 'instance creation'

new: rowSize lastRow: lastRow blackOrWhite: flag

        | row | 
        row _ super new: rowSize.
        row setLastRow: lastRow blackOrWhite: flag.
        ^row


Row class methodsFor: 'class initialization'

initialize
        "Row initialize"

        Rand _ Random new


Row class methodsFor: 'constants'

randomColor
        ^Rand next < 0.5 ifTrue: [0] ifFalse: [1]



Array variableSubclass: #Mosaic
        instanceVariableNames: ''
        classVariableNames: ''
        poolDictionaries: ''
        category: 'Mosaic-Support'

Mosaic comment: 'Mosaic knitting is a special kind of two color
knitting where only one color is actively used in each row.
Contrasting stitches that appear in that row must therefore slipped
from the row below. The active color is alternated with each row and
the first and last stitches must be worked in that color.   Since
slipping a stitch from the row below results in a float of the active
color on the wrong side, another constraint commonly placed on mosaic
knitting is that no more than two consecutive constrasting stitches may
appear together to minimize the length of the floats.

This class is a subclass of Array.  It holds a collection of Rows.'


Mosaic methodsFor: 'filling board'

fillRowsOfSize: rowSize
        "Fill the receiver with rows of size rowSize."

        | aRow | 
        self size = 0 ifTrue: [^self].
        self at: 1 put: (aRow _ Row new: rowSize lastRow: nil blackOrWhite: Row randomColor).
        2 to: self size do: [:index |
                self at: index put: (aRow _ aRow nextRow)]


Mosaic methodsFor: 'printing'

printOn: aStream
        "Print out in reverse order: knitters read the first row on the bottom of the chart."

        self reverse 
                do: [:each |
                        each printOn: aStream]
                andBetweenDo: [aStream cr].



Mosaic class methodsFor: 'instance creation'

rows: numberOfRows columns: rowSize
        "Mosaic rows: 6 columns: 6"

        ^(super new: numberOfRows) fillRowsOfSize: rowSize

		    Listing 2: Mosaic program in C++

			    ::::::::::::
			    : mosaic.h :
			    ::::::::::::

// Mosaic knitting is a special kind of two color knitting where only one
// color is actively used in each row.  Contrasting stitches that appear
// in that row must therefore slipped from the row below. The active color
// is alternated with each row and the first and last stitches must be
// worked in that color.   Since slipping a stitch from the row below
// results in a float of the active color on the wrong side, another
// constraint commonly placed on mosaic knitting is that no more than two
// consecutive constrasting stitches may appear together to minimize the
// length of the floats.

extern "C" int random();

// color definitions representing two contrasting colors
enum color {white, black};

const int nil = 0;

// Class intArray
// Implements a dynamic array of integers.

class intArray {
    public:
        int size;   // size of intArray
        int* contents;  // the intArray itself
        void print();
        intArray(int arraySize);
        ~intArray() {delete contents;};
}; 


// Class Row
// Implements a dynamic array of stitches.

class Row : public intArray {
    private:
        int baseColor;
        Row* previousRow;   // pointer to previousRow
        int contrastColor();
        void firstRow();
        void subsequentRow();
        void addBaseColorAt(int index);
        void addContrastColorAt(int index);
        void addRandomColorAt(int index);
        void initRow();
        int contrastColorCanAppearAt(int index);
    public:
        Row(int rowSize, Row* lastRow, int flag);
        Row* nextRow();
}; 

// class RowArray 
// Implements a dynamic array of pointers to rows

class RowArray {
    public:
        int size;   // size of the Array
        Row** rows; // the rowArray itself
        void print();
        RowArray(int size);
        ~RowArray();
}; 


// class mosaic 
// Adds behavior to RowArray to initialize, fill, and print the mosaic pattern.

class mosaic: public RowArray {
    public:
        void print();
        mosaic(int numberOfRows, int rowSize);
}; 

#include <stream.h>

			    ::::::::::::
			    : mosaic.c :
			    ::::::::::::

// This program produces valid mosaic knitting patterns.  The user is prompted
// for the number of rows in the pattern and the number of stitches in each
// row.  The pattern is influenced randomly subject to certain constraints.

#include "mosaic.h"

// Class intArray
// Implements a dynamic array of integers.

intArray::intArray(int arraySize) { 
    contents = new int[arraySize];
    size = arraySize ;
};

void intArray::print()
{   
    // Print an ascii representation of the receiver on stdout.
    for (int i=0; i<size; i++) {
        cout << contents[i];
    };  
    cout << "\n";
};

// Class Row
// Implements a dynamic array of stitches.

Row* Row::nextRow()
{
    Row* tmp = new Row (size, this, contrastColor());
    return tmp;
};

int Row::contrastColor()
{
    return this->baseColor ^ 1;
};

Row::Row(int rowSize, Row* lastRow, int flag) : (rowSize)
{
    previousRow = lastRow;
    baseColor = flag;
    if (!previousRow)
        firstRow();
    else this->subsequentRow();
};

void Row::firstRow()
// This is the first row.  Generate a random pattern for all but the first 
// and last stitches which have to be the baseColor.
{
    initRow();
    for (int i=1; i<size-1; i++)
        addRandomColorAt(i);
};

void Row::addBaseColorAt(int index)
{
    contents[index] = baseColor;
};

void Row::addContrastColorAt(int index)
{
    contents[index] = contrastColor();
};

void Row::addRandomColorAt(int index)
{
    if (random()&01)
        addBaseColorAt(index);
    else addContrastColorAt(index); 
};

void Row::initRow()
// Fill in the first and last stitches, constrained to be the baseColor.
{
    addBaseColorAt(0);
    addBaseColorAt(size -1);
};

void Row::subsequentRow()
// This is not the first row.  Generate a constrained yet random pattern for 
// all but the first and last stitches which have to be the baseColor.
{
    initRow();
    for (int i=1; i<size-1; i++)
        if (contrastColorCanAppearAt(i))
            addRandomColorAt(i);
	else addBaseColorAt(i);
};      

int Row::contrastColorCanAppearAt(int index)
{
// Indexes 0..index-1 of the receiver have been filled.
// Answer true (1) only if adding a contrast color at the specified index 
// would not violate the following constraints:
//      1) the first and last stitches must be the base color.
//      2) no more than two adjoining contrast colors
//      3) the contrast color must appear in the same spot in the row 
//         below since it has to be slipped,
//  Otherwise answer false (0)

    if ((index <= 0) || (index >= size - 2)) 
        return 0;
    // Assert: index is in the range 2 .. self size - 1.
    if (!(previousRow->contents[index] == contrastColor()))
        return 0;
    // Assert: the previous row contains the contrasting color at this index."
    if (index < 3) 
    // Assert: index is 2 or 3; no more than 2 consecutive contrasting colors appear.
        return 1;
    if (contents[index - 1] == baseColor)
        return 1;
    // Assert: previous color was a contrast color.  
    // Check the one before that.  If it was the baseColor, answer true; false otherwise.
    return contents[index - 2] == baseColor;
};


// class RowArray 
// Implements a dynamic array of pointers to rows

RowArray::RowArray(int arraySize)
{   
    rows = new Row* [arraySize]; 
    size = arraySize;
};

RowArray::~RowArray()
{
    for (int i=0; i<size; i++) 
        delete rows[i];
}


// class mosaic 
// Adds behavior to RowArray to initialize, fill, and print the mosaic pattern.

void mosaic::print()
{
//  Print out in reverse order: knitters expect the first row to be on 
//  the bottom of the chart. 
    for (int i = size - 1; i >= 0; i--) {
        if (i < 9)
            cout << " ";
        cout << i + 1 << " ";
        rows[i]->print();
    }   
    cout << "\n";
};


mosaic::mosaic(int numberOfRows, int rowSize) : (numberOfRows)
{
    if (size == 0) return;

    Row* aRow = new Row((int) rowSize, (Row*) 0, random()&01);
    rows[0] = aRow;
    for (int i=1; i < size; i++) {
        aRow = aRow->nextRow();
        rows[i] = aRow;}
};

main()
// generate and print the mosaic pattern.
{
    mosaic* mos = new mosaic(1000, 1000);
//  mos->print();
};

                Listing 3: Mosaic program in Eiffel

                                :::::::::::::
                                :  mosaic.e :
                                :::::::::::::

class MOSAIC export 
    black, white
inherit 
    ARRAY[ROW] rename Create as array_Create;
    STD_FILES

feature

    black: INTEGER is 1;
    white: INTEGER is 0;

    printBoard is
    local i: INTEGER
    do
        from 
	    i := upper 
        --invariant 
        --    i_2_big: i <= upper; i_2_small: i >= lower - 1;
        --variant 
        --    i_not_decreasing: i 
        until 
            i < lower 
        loop
            if not entry(i).Void then 
                if i < 10 then 
                    output.putchar(' ') 
                end; -- if
                output.putint(i);
                entry(i).printOn(output)
            end; -- if
            i := i - 1
        end -- loop
    end; --printBoard

    rand: RANDOM is
             -- Return a pointer to the sole instance of RANDOM
        once 
            Result.Create;
        end; -- rand

    Create is
        local 
            i: INTEGER;
            color: INTEGER;
            r,previousRow: ROW
        do
            color := rand.dont_care;
            array_Create(1,1000);
            from 
                i := lower 
            --invariant 
            --    i_2_small: i >= lower; i_2_big: i <= upper + 1
            --variant 
            --    i_not_increasing: upper - i + 1     
            until 
                i > upper 
            loop
                r.Create(previousRow, color, size);
                enter(i, r);
                previousRow := r;
                i := i + 1;
                if color = black then 
                    color := white; 
                else color := black end -- if
            end; -- loop
            --printBoard;
        end -- Create

end; -- MOSAIC

                            :::::::::::::
                            :   row.e   :
                            :::::::::::::

class ROW export 
    printOn
inherit 
    ARRAY[INTEGER] rename Create as array_create;
    RANDOM;
    STD_FILES

feature
    previousRow: ROW;
    baseColor: INTEGER;
    
    printOn(io: FILE) is
        local i: INTEGER
        do
            io.putchar(' ');
            from 
                i := lower 
            until 
                i > upper 
            loop
                io.putint(entry(i));
                i := i + 1;
            end; -- loop
            io.new_line
    end; -- printOn

    initRow is
        do
            enter(lower, baseColor);
            enter(upper, baseColor)
        end; -- initRow

    firstRow is
        -- I'm the first row and can be any pattern I like.
        local i: INTEGER
        do
            initRow;
            from 
                i := lower+1 
            until 
                i = upper 
            loop
                enter(i, dont_care);
                i := i + 1
            end -- loop
        end; -- firstRow

    subsequentRow is
        -- I have to color myself based on the pattern of the previous row.
        local i: INTEGER
        do
            initRow;
            from 
                i := lower+1 
            until 
                i = upper 
            loop
                if contrastColorCanAppearAt(i) then 
                    enter(i, dont_care)
                else enter(i, baseColor) end;  -- if
                i := i + 1;
            end; -- loop
    end; -- subsequentRow

contrastColorCanAppearAt(i: INTEGER): BOOLEAN is
    do
        Result := (i > lower and i < upper)
            -- can't change the first or last stitch
        and then (previousRow.entry(i) /= baseColor)
            -- contrast color can only appear here if it's in the row below
        and then (i <= lower + 2 or else
                  (entry(i-1) = baseColor or entry(i-2) = baseColor));
            -- no more than 2 contrast colors together to minimize floats
    end; -- contrastColorCanAppearAt

Create(lastrow: ROW; blackOrWhite: INTEGER, row_size: INTEGER) is
    do
        array_create(1, row_size);
        baseColor := blackOrWhite;
        previousRow := lastrow;
        if lastrow.Void then
            firstRow
        else subsequentRow end -- if
    end -- Create

end -- ROW

                            :::::::::::::
                            :  random.e :
                            :::::::::::::

class RANDOM
  -- Randomly return 1 or 0 each time dont_care is called

export dont_care

feature

dont_care: INTEGER is
    external randbit: INTEGER name "randbit" language "C"
    do
       Result := randbit;
    end -- dont_care;

end -- random

                            :::::::::::::
                            : randbit.c :
                            :::::::::::::

extern int random();

int randbit() 
{
return random() & 01;
}

jjc@jclark.UUCP (James Clark) (03/23/90)

In article <274@eiffel.UUCP>, kimr@eiffel.UUCP (Kim Rochat) presents
implementations of a small program in Eiffel, C++ and Smalltalk-80,
compares the performance of the implementations, and concludes
(amongst other things) that

  any perception that C++ has superior performance to Eiffel may
  be invalid.

It appears to me that the comparison between C++ and Eiffel is
vitiated by a failure to make any use of inline functions in the C++
version of the program.

I modified the C++ version of the program to make use of inline
functions.  I compared the original version of the program against the
version which used inlining. I also compared it with another version
which additionally replaced the heavily used random() function by a
simple inline random number generator. As well as declaring functions
inline, it was necessary to reorder the function definitions and to
make a cosmetic change to one function to work around a limitation in
cfront's inlining capabilities.

I performed the comparison on a Sun 4/370 and on a 386 PC. On the Sun
4 I used both cfront 2.0 and g++ 1.37.1; I found no measurable
difference in performance. On the 386 I used only g++ 1.37.1.  Also on
the 386 I used a 700 by 700 pattern, since the machine had only 4M of
RAM, and the 1000 by 1000 pattern caused it to thrash.

			User	Sys	Real
Sun 4, w/o inline	5.8	.7	6.6 
Sun 4, with inline	4.7	.6	5.4
Sun 4, with inline &	3.8	.6	4.6
  inline random()

386, w/o inline		12.5	.6	13.4
386, with inline	6.4	.6	7.3
386, with inline &	5.8	.6	6.7
  inline random()

The use of inline also resulted in a small decrease in the size of the
executables.

I should be glad to provide the modified version of the program to
anybody who wants it.

James Clark
jjc@jclark.uucp

ark@alice.UUCP (Andrew Koenig) (03/24/90)

In article <274@eiffel.UUCP>, kimr@eiffel.UUCP (Kim Rochat) writes:

> Note that Eiffel is just as fast as C++ on the Sun and faster than C++
> on the Tektronix workstation.  This seems ironic since C++ is touted as
> being efficient and the mosaic program exercises features which should
> be efficiently implemented in C.

I thought it would be interesting to see what could be done trivially
to speed up the C++ version of this program, so I profiled it.
My machine does not have the Berkeley random() function, so I
substituted an equivalent function, nrand() from our local C library.

I found the following front-runners:

	%time	function
	20	Row::contrastColorCanAppearAt
	17	nrand
	15	Row::subsequentRow
	11	Row::contrastColor
	10	mcount	(the C profiler itself!)
	10	Row::addRandomColorAt
	8	Row::addBaseColorAt
	3	Row::addContrastColorAt

Of the six C++ functions shown here, four of them were so short that
many C++ programmers would have written them as inline as a matter of
routine, namely Row::contrastColor, Row::addRandomColorAt,
Row::addBaseColorAt, and Row::addContrastColorAt.  Row::subsequentRow
was not called many times and Row::contrastColorCanAppearAt was fairly
large, so inlining wouldn't help either of these much.

With these trivial changes, the run time on my machine (a MicroVax II)
went from 161 to 100 seconds (timed without profiling).  Profiling the
revised program showed 30% spent in nrand, which suggests that it
would be hard to obtain further dramatic improvement without first
obtaining a faster random number generator.
-- 
				--Andrew Koenig
				  ark@europa.att.com

khaw@parcplace.com (Mike Khaw) (03/27/90)

I'm posting this for Peter Deutsch, ParcPlace Systems' Chief Scientist:

----------

In <274@eiffel.UUCP> of comp.lang.eiffel, Kim and Roxanna Rochat of ISE
(formerly Tektronix) posted some numbers regarding the performance of a
simple integer and array manipulation program in C++, Eiffel, and
Smalltalk-80.  They point out that the choice of C compiler can have as
much as a 30% influence on the measured time for the Eiffel
implementation.  Taking their observation to heart, I measured the
performance of their program on a different Smalltalk-80
implementation: Objectworks for Smalltalk-80, version 2.5, from
ParcPlace Systems.  I used the same hardware (diskless Sun-3/60 with 12
Mb of RAM, running SunOS 4.0.3) as they reported.  The result was:

Sun     Smalltalk  Ow/ST-80,v2.5                240

[Just to confirm that our numbers are comparable to those posted by
the Rochats, I compiled mosaic.c using ATT's 2.0 C++ cfront and Sun's
cc, with "-O -Bstatic" flags, and matched their 41 sec. time on the
same machine that we ran the Smalltalk version on. -- Mike Khaw]

This is approximately a factor of 6 slower than the fastest, and a
factor of about 4 slower than the slowest, of the C++ and Eiffel
results, and a factor of about 12 faster than the Tek Smalltalk figure
(correcting for the roughly 25% difference in machine speed).  Not bad
for a language that is often (inaccurately) slandered as being
"interpretive" and too slow to use for real work.

After reading Andrew Koenig's posting, it seemed reasonable to us that if
he could modify the program in ways that any reasonably experienced C++
programmer would, we could modify it in ways that a reasonably experienced
Smalltalk programmer would.  Most of the time goes into the random number
calculation, and most of that time goes into the floating point arithmetic,
which is very slow in Smalltalk.  So we modified the program by adding a
new message to Random that returns a random boolean, avoiding the floating
point.  We replaced the two uses of 'Rand next < 0.5' in the Mosaic program
by 'Rand nextBoolean'.  Here is the new method, and the revised timing
results:

	Random methodsFor: 'accessing'

		nextBoolean
			"Answer the next random Boolean."

			^self step < (modulus bitShift: -1)

This change cut the measured time from 240 seconds to 175.

Some may argue that adding code to a library class is not a legitimate way
to improve the performance of a program.  We take the view that given the
"all source code available" culture promoted by the Smalltalk-80 system,
adding methods to library classes is natural and appropriate; we believe
that extending this culture to other languages would benefit the user
community greatly.

I would like to thank the authors for posting their test code in the
article.

----------
-- 
Mike Khaw
ParcPlace Systems, Inc., 1550 Plymouth St., Mountain View, CA 94043
Domain=khaw@parcplace.com, UUCP=...!{uunet,sun,decwrl}!parcplace!khaw

jamiller@hpcupt1.HP.COM (Jim Miller) (03/27/90)

>------------------------------------------------------------------------------
>               Figure 1:  Mosaic Benchmark Results
>
>Host Language[2]  Compiler[3]  Execution Time (secs)[4]  Executable Size[5]
>[1]                            User  System    Wall      Text    Data    BSS
>---- -----------  -----------  ----  ------    ----      -----   ----    -----
>Tek    Smalltalk  Tek ST-80                    3600
>Tek    Eiffel     Greenhills   69.2    1.0       70      40960   11264   36004
>Tek    C++        CC1.2/GH     55.3    1.7       57      15360    5120   34508
>Tek    C++        CC1.2/gcc    54.2    1.3       56      15360    5120   34498
>Tek    C++        g++          52.9    1.3       54      20480    8192   32056
>Tek    Eiffel     gcc          49.9    1.4       51      39936   11264   35616
>
>Sun    Eiffel     Sun          54.7    1.0       56      65535    8192    3872
>Sun    C++        CC2.0/gcc    41.3    1.2       42      32768    8192       0
>Sun    C++        g++          40.6    0.9       41      40960    8192       0
>Sun    Eiffel     gcc          40.4    1.1       41      65535    8192     536
>Sun    C++        CC2.0/cc     39.9    1.3       41      40960    8192       0
 
...

>Note that Eiffel is just as fast as C++ on the Sun and faster than C++
>on the Tektronix workstation.  This seems ironic since C++ is touted as
>being efficient and the mosaic program exercises features which should
>be efficiently implemented in C.

Wait A minute.  Eiffel is faster AND slower.  It's just about the BEST and
the WORST on both machines.

It is very interesting data, thanks.
  
     jim miller

kimr@eiffel.UUCP (Kim Rochat) (03/29/90)

In article <10617@alice.UUCP>, ark@alice.UUCP (Andrew Koenig) writes:
> Of the six C++ functions shown (below), four of them were so short that
> many C++ programmers would have written them as inline as a matter of
> routine, namely Row::contrastColor, Row::addRandomColorAt,
> Row::addBaseColorAt, and Row::addContrastColorAt.

The Eiffel and C++ benchmarks were intended to be constructed to make the
same number of function calls, so as not to be comparing the function
call overhead of the various machines. 

In reviewing the benchmarks, it appears that I didn't quite succeed.
Looking only at the inner loop of subsequentRow, where the vast
majority of the time is spent, the following diagram is an attempt to
convey the call structure of each routine.  Horizontal lines indicate
entering a new function.

		C++				Eiffel

subsequentRow				subsequentRow
-----------------------------		-----------------------------
if contrastColorCanAppearAt then	if contrastColorCanAppearAt then
	addRandomColorAt			dont_care
	-----------------------------		-----------------------------
		if random then				randbit
		addBaseColorAt				--------------
		else addContrastColorAt				random
		-----------------------			--------------
			ContrastColor		<no correspondence>
		-----------------------
	-----------------------------
else addBaseColorAt			 else   <no correspondence>
-----------------------------		 -----------------------------

There are no function calls in the Eiffel code corresponding to the C++
calls to ContrastColorAt or the conditional calls to addBaseColorAt by
subsequentRow.  As ark implies, the benchmarks would have been fairer
if these two routines had been inlined.

This entire discussion brings up another interesting topic, which is
how can you tell how many function calls you're generating with your
code?  Under C++, inlining is directly under programmer control.  With
Eiffel, it's under the control of the compiler and optimizer.  For
example, I don't count calls to "enter" and "entry" for the Eiffel code,
since I happen to know that the compiler inlined" them.  

Since I used C package generation for Eiffel, the compiler also ran an
optimizer which analyzes routines and inlines suitable candidates.  In
reviewing what the compiler actually did, it chose to inline the
routines "firstrow" and "subsequentRow".  Their omission accounts for
only 1000 calls out of several million.

As the readers of the original article pointed out, any of the three
versions of the program could be further optimized for improved
performance.  The authors were attempting to benchmark obvious
implementations of the algorithm - i.e. versions which conform to the
spirit and style of each language without any iterations tuning
performance.

Ark might suggest that we didn't conform to the spirit of C++ because
we didn't use inline functions.  I accept that criticism with the
defense that we didn't use them because our local style guidelines
suggested that inlines not be used for C++, both because they violate
encapsulation by placing implementation in the header files, and
because inlining was unreliable under cfront 2.1, under which the C++
version was first developed.

I'd like to thank everyone for their comments on the article and
reports on subsequent experiments.  I regret that I cannot 
revise my benchmark results due to a change in employment.

Kim Rochat
Interactive Software Engineering
kimr@eiffel.com

jos@cs.vu.nl (Jos Warmer) (03/29/90)

kimr@eiffel.UUCP (Kim Rochat) writes:

>       Under C++, inlining is directly under programmer control.

Isn't it more correct to state that it is under more programmer control.
If a programmer makes a function inline, it is only a hint
to the compiler.   The C++ compiler does not have to inline
each occurrence.
This was the situation with version 1.2 of the AT&T translator,
I don't know the situation with newer versions.

	    Jos Warmer
	    jos@cs.vu.nl