Number^Power

To increase speed of the engine, I decided to test what would happen if I made certain numbers built-in. Most people don’t need anything more than integer or double. The other types are only for tasks like saving memory (e.g. making bit-flags) or importing data (which will be handled by a different class in Copper anyways). If you want to save memory, generally you don’t use an interpreted language.
That said, what were the results?
The built-in numbers were integer (representing long int) and decimal (representing double). The primary slow-down I expected to be in converting from number to string, which ended up requiring copying the string three times just to do it cleanly with the standard library.
Benchmark code 1:

benchmark = {
 i = int(0)
 loop {
 if ( equal(i: 1000000) ) { stop }
 print(num_to_str(i:) "\n")
 ++(i:)
 }
}

Without the numbers built-in, the analogous function took 18 seconds. With this new version, the total time was alittle over 20 seconds. Just to be certain, I ran it again and got about the same time.
Noting that converting strings is slow, what happens if we wait to print? I tried this code:

benchmark = {
 i = int(0)
 loop {
 if ( equal(i: 1000000) ) { stop }
 ++(i:)
 }
 print(num_to_str(i:) "\n")
}

The result? About 7 seconds. Now THAT is what I like to see. The original version had sacrificed speed of numeric operations for the speed of printing (and it paid off over the course of ONE MILLION iterations). That would be fine if you were printing alot, as in this case. However, it was slow when it came to doing the actual math. My operations could probably be made somewhat faster if I replace my current numeric-to-string conversion code with something custom.

I tried to write a comparable program in Python, but I suspect it got optimized (I’ve seen it do this, and the only way around it is adding certain code which would mess up the direct comparison for a no-print loop).

import time
>>> def benchmark():
... t1 = time.process_time()
... a = 0
... for i in range(1000000):
... a = a + 1
... t2 = time.process_time()
... print((t2-t1)*1000)

The result was 193.218805 (milliseconds). Optimized? I think so. I ran the following in a file:

t1 = time.process_time()
a = 0
for i in range(1000000):
 a = a + 1 
t2 = time.process_time()
print((t2-t1)*1000)

The result was 278.776975 (milliseconds). Notably, Python has a better numeric system than Copper at the moment, but I can’t say it’s giving Python an advantage or not. Most of the time revolves around the activity of the core code of the interpreter than of the extension code.
One of the details slowing the engine down is the fact that numbers aren’t converted to strings by direct means of the strings class. (Then the conversion code could happen within the object itself.) Otherwise, I could completely cut out the num_to_str class and TONS of function calls.

Conclusion

There is more to be done in terms of speeding up the code. I intend to change the entire opcodes system to speed it up alittle. I can’t say it’ll be too much of a speedup, but it’s worth a shot just to make the codebase smaller.
The standard library provides a plethora of features that I’m missing. While I intend for Copper to remain an independent library, the fact that most people have the standard library means that it’s probably acceptable to incorporate it more into the system for the sake of interoperability and cleaner code.

The speed benefit came from adding Integer and Decimal as types that could be easily identified. I’ve devised a plan for making this possible for all user-added types: by having type-identifiers. The bummer is that all extension classes for the foreign functions of a particular type would need to be initialized with an identifier so they could find the type they were supposed to operate on. Notably, this is safer than extending an enum (at least directly). It should add a decent speed advantage too since it’ll avoid the need for strings.

In short, the new number system implementation showed the importance of type identification using only numbers instead of strings. While the current numeric system may not be a permanent resident, at least it has paved the way for some beneficial changes.

Update

That was fast. I decided instead to simply change the print function. Since the types are built-in, I can do that now. Tee hee. Benchmark code:

benchmark = {
 i = 0
 loop {
 if ( equal(i: 1000000) ) { stop }
 print(i: "\n")
 ++(i:)
 }
}
benchmark: # <- Implied in the other test #

Run time? 14 seconds! So I ended up shaving off 4 seconds! Interestingly enough, it took 7 more seconds (or twice as long) to have the print function inside the loop than it did to have it outside (where it’s only called once). I’m not using the fastest code for printing (fprintf), but at least it’s easy and flexible.

Advertisements

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 )

w

Connecting to %s