Coding for Fast Copper

While attempting to program the image of a mandelbrot, I unwittingly chose the easy, convenient, yet very slow way of writing my complex number class. Let’s have a look.

The complex number was coded as follows:

Complex = [
	real = 0
	imag = 0
	add = [p] {
		super.real = +(super.real: p.real:)
		super.imag = +(super.imag: p.imag:)
	}
	mult = [p] {
		r = super.real:
		super.real = -( *(r: p.real:) *(super.imag: p.imag:))
		super.imag = +( *(r: p.imag:) *(super.imag: p.real:))
	}
]

The problem isn’t obvious. The class is perfectly acceptable for most practical cases, but the problem arises in its usage.

z = Complex
c = Complex

loop {
# ... #
z = c
# ... #
} # end loop #

The important part of the loop is shown. In a loop in the program, there occurs a copy. But the copy isn’t merely of the members “Complex.real” and “Complex.imag”. It’s also a copy of “Complex.add” and “Complex.mult”, which includes their parameters lists and some other data associated with functions. Even if the copy of a member isn’t too slow, we are in fact copying twice as much as we need to. And if we were to add more members to Complex, the copying of Complex would be even slower!

What’s the solution? Unlike C++, we don’t have the luxury of having class definitions in which class methods are only saved once, but we can simulate that structure by making a single object, Complex, that acts on objects created as copies of a special member of Complex. Let’s see this at work:

Complex = [
	data = [ real = 0, imag = 0 ] # Copy me #
	add = [_1, _2] {
		_1.real = +( _1.real: _2.real: )
		_1.imag = +( _1.imag: _2.imag: )
	}
	mult = [_1, _2] {
		r = _1.real
		_1.real = -( *(r: _2.real:) *(_1.imag: _2.imag:) )
		_1.imag = +( *(r: _2.imag:) *(_1.imag: _2.real:) )
	}
]

z = Complex.data
c = Complex.data

# Example: #
Complex.add(z,c) # z = z + c #

In short, in order to speed up Copper code, we make a base class that is never copied as a whole but contains a member that IS copied AND whose structure the other member functions (of the base class) presume when working on arguments given them. That is, the other member functions expect to be given identical copies of the special member.

General Advice

Copper makes it really easy to copy. This avoids the hassle of other languages (like Java) that require a member function for every non-primitive/class that needs to be copied. The downside is that copying is not a fast operation, so you’re code should be designed to minimize copying.

Here are some other tips:

  1. Prefer using pointing/pointers (using the ~ ) over assignment where possible. Pointers never copy and are guaranteed to never be null, so they are always safe to use.
  2. Prefer using loop over hash table function calling / mapping. (By mapping, I mean f~member(“route_name”) f: )
  3. Prefer the following types of functions in order: 1) native engine functions (e.g. ++(), xwsv(),…), 2) foreign functions implemented in C++, 3) Copper-implemented functions. The reasons being this is the order the virtual machine is designed to check when searching for a function to match a name and the implementations of the call functions in the virtual machine are such that this happens to be how fast each one is relatively speaking.
  4. Use short variable names.

And lastly, don’t forget today’s lesson: minimize the number of members in objects that need to be copied frequently.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s