Here's my first bit of code.
Actually, web accessibility has nothing to do with this! It's more about convenience (though some of my modules do make use of this as a resource).
So, save this as "css_colours.php". Yes, for my American friends, you'll need to learn how to not spell quite so lazily... :-P .
You'll note there's no ?> at the end of the code. This is because it ensures that, as include'd (or require'd), the parent file will stay in PHP mode and not drop back to HTML mode (which may not happen, depending on various things... but it at least apparently removes the possibility!
<?php
$css_colours = array (
'AliceBlue' => '#F0F8FF',
'AntiqueWhite' => '#FAEBD7',
'Aqua' => '#00FFFF',
'Aquamarine' => '#7FFFD4',
'Azure' => '#F0FFFF',
'Beige' => '#F5F5DC',
'Bisque' => '#FFE4C4',
'Black' => '#000000',
'BlanchedAlmond' => '#FFEBCD',
'Blue' => '#0000FF',
'BlueViolet' => '#8A2BE2',
'Brown' => '#A52A2A',
'BurlyWood' => '#DEB887',
'CadetBlue' => '#5F9EA0',
'Chartreuse' => '#7FFF00',
'Chocolate' => '#D2691E',
'Coral' => '#FF7F50',
'CornflowerBlue' => '#6495ED',
'Cornsilk' => '#FFF8DC',
'Crimson' => '#DC143C',
'Cyan' => '#00FFFF',
'DarkBlue' => '#00008B',
'DarkCyan' => '#008B8B',
'DarkGoldenRod' => '#B8860B',
'DarkGray' => '#A9A9A9',
'DarkGrey' => '#A9A9A9',
'DarkGreen' => '#006400',
'DarkKhaki' => '#BDB76B',
'DarkMagenta' => '#8B008B',
'DarkOliveGreen' => '#556B2F',
'Darkorange' => '#FF8C00',
'DarkOrchid' => '#9932CC',
'DarkRed' => '#8B0000',
'DarkSalmon' => '#E9967A',
'DarkSeaGreen' => '#8FBC8F',
'DarkSlateBlue' => '#483D8B',
'DarkSlateGray' => '#2F4F4F',
'DarkSlateGrey' => '#2F4F4F',
'DarkTurquoise' => '#00CED1',
'DarkViolet' => '#9400D3',
'DeepPink' => '#FF1493',
'DeepSkyBlue' => '#00BFFF',
'DimGray' => '#696969',
'DimGrey' => '#696969',
'DodgerBlue' => '#1E90FF',
'FireBrick' => '#B22222',
'FloralWhite' => '#FFFAF0',
'ForestGreen' => '#228B22',
'Fuchsia' => '#FF00FF',
'Gainsboro' => '#DCDCDC',
'GhostWhite' => '#F8F8FF',
'Gold' => '#FFD700',
'GoldenRod' => '#DAA520',
'Gray' => '#808080',
'Grey' => '#808080',
'Green' => '#008000',
'GreenYellow' => '#ADFF2F',
'HoneyDew' => '#F0FFF0',
'HotPink' => '#FF69B4',
'IndianRed' => '#CD5C5C',
'Indigo' => '#4B0082',
'Ivory' => '#FFFFF0',
'Khaki' => '#F0E68C',
'Lavender' => '#E6E6FA',
'LavenderBlush' => '#FFF0F5',
'LawnGreen' => '#7CFC00',
'LemonChiffon' => '#FFFACD',
'LightBlue' => '#ADD8E6',
'LightCoral' => '#F08080',
'LightCyan' => '#E0FFFF',
'LightGoldenRodYellow' => '#FAFAD2',
'LightGray' => '#D3D3D3',
'LightGrey' => '#D3D3D3',
'LightGreen' => '#90EE90',
'LightPink' => '#FFB6C1',
'LightSalmon' => '#FFA07A',
'LightSeaGreen' => '#20B2AA',
'LightSkyBlue' => '#87CEFA',
'LightSlateGray' => '#778899',
'LightSlateGrey' => '#778899',
'LightSteelBlue' => '#B0C4DE',
'LightYellow' => '#FFFFE0',
'Lime' => '#00FF00',
'LimeGreen' => '#32CD32',
'Linen' => '#FAF0E6',
'Magenta' => '#FF00FF',
'Maroon' => '#800000',
'MediumAquaMarine' => '#66CDAA',
'MediumBlue' => '#0000CD',
'MediumOrchid' => '#BA55D3',
'MediumPurple' => '#9370D8',
'MediumSeaGreen' => '#3CB371',
'MediumSlateBlue' => '#7B68EE',
'MediumSpringGreen' => '#00FA9A',
'MediumTurquoise' => '#48D1CC',
'MediumVioletRed' => '#C71585',
'MidnightBlue' => '#191970',
'MintCream' => '#F5FFFA',
'MistyRose' => '#FFE4E1',
'Moccasin' => '#FFE4B5',
'NavajoWhite' => '#FFDEAD',
'Navy' => '#000080',
'OldLace' => '#FDF5E6',
'Olive' => '#808000',
'OliveDrab' => '#6B8E23',
'Orange' => '#FFA500',
'OrangeRed' => '#FF4500',
'Orchid' => '#DA70D6',
'PaleGoldenRod' => '#EEE8AA',
'PaleGreen' => '#98FB98',
'PaleTurquoise' => '#AFEEEE',
'PaleVioletRed' => '#D87093',
'PapayaWhip' => '#FFEFD5',
'PeachPuff' => '#FFDAB9',
'Peru' => '#CD853F',
'Pink' => '#FFC0CB',
'Plum' => '#DDA0DD',
'PowderBlue' => '#B0E0E6',
'Purple' => '#800080',
'Red' => '#FF0000',
'RosyBrown' => '#BC8F8F',
'RoyalBlue' => '#4169E1',
'SaddleBrown' => '#8B4513',
'Salmon' => '#FA8072',
'SandyBrown' => '#F4A460',
'SeaGreen' => '#2E8B57',
'SeaShell' => '#FFF5EE',
'Sienna' => '#A0522D',
'Silver' => '#C0C0C0',
'SkyBlue' => '#87CEEB',
'SlateBlue' => '#6A5ACD',
'SlateGray' => '#708090',
'SlateGrey' => '#708090',
'Snow' => '#FFFAFA',
'SpringGreen' => '#00FF7F',
'SteelBlue' => '#4682B4',
'Tan' => '#D2B48C',
'Teal' => '#008080',
'Thistle' => '#D8BFD8',
'Tomato' => '#FF6347',
'Turquoise' => '#40E0D0',
'Violet' => '#EE82EE',
'Wheat' => '#F5DEB3',
'White' => '#FFFFFF',
'WhiteSmoke' => '#F5F5F5',
'Yellow' => '#FFFF00',
'YellowGreen' => '#9ACD32');
Wings of Change
Friday, 24 June 2016
Oh, the inaccessible humanity!
Background
I've recently started a new position as an accessibility tester. This means testing web-based software and web pages against the Web Content Accessibility Guidelines (https://www.w3.org/TR/WCAG20/).An obsession???
I've been told by my lovely other half that I'm a bit obsessed by WCAG. It's hardly surprising, really... many of the guidelines in WCAG are applicable to me in one manner or another! (I should point out that by no means all of them, and some of them that are applicable you might be surprised by!).It's all about the audience!
But it's also a question of caring for others. If a web designer fails to take into account people with various access issues, they're effectively cutting off those people from using your site, meaning that what you want to convey to them is missed - but just to those people.Exclusion is a pretty big experience that many people with disabilities have, frequently, regularly and often very subtly. Consider a wheelchair user, who has to use a "side" entrance to access a ramp to enter a building. It might take them an extra couple of minutes when this happens... but if this happens many times in the course of a day, that time adds up. And that's time these people are (in our terms) "unproductive" - but this isn't their fault (it's ours because our culture did not consider just putting said ramps in place at the front of buildings to begin with!)
The same is true of web-based information. If your site comes at the top of google rankings for a particular set of search terms, but some users can't use your site, these users have to go back and find other resources (probably the second-top ranking site). If that site also suffers from the same problem of inaccessibility, that can be repeated.
If no-one bothers, then how do these people access that information? Given the big shift to on-line publication, these people will end up with the digital equivalent of stairs at the front of a building!
OK, it's about you, too!
You can see from this, that it's about you, the web author, as well. If you have a pay-per-view site, but some users can't use your site, you're cutting off a portion of your potential revenue stream.Some think this is not a big deal. But apparently it is, as this example demonstrates (for the record, I neither endorse nor dis-endorse any of the companies listed).
An example: Paypal and "bootstrap"
The people at Twitter make a web page framework called "bootstrap". Out-of-the-box bootstrap is not WCAG compliant... and while it can be made kind-of compliant, it is difficult to do so (lots of things to override, modify, etc., for it all to happen).Paypal wanted to use bootstrap. Most of their competitors also use bootstrap. This means that the vast majority of on-line sales are not able to be accessed by, for example, vision-impaired people.
Paypal have created their own bootstrap "fix". It is mostly WCAG compliant - it has some bits needing to be ironed out, but it is considerably better than native bootstrap. It's cost them a considerable amount of investment to do so... but their sales projections are about 10% higher than would otherwise be the case: this system gives them a significant competitive advantage! The investment will pay for itself in about a year or so.
On-line examples
I often make use of code from the net in my projects. If someone has solved the problem already - and is freely making their code available for use! - why re-invent the wheel?However, a very large proportion of these examples don't include accessibility considerations (I'm talking PHP, JavaScript, CSS and HTML in particular here). Which means they haven't really solved the problem.
My response?
I'm going to re-invent the wheel. With accessibility considered.I'm going to start publishing some updated examples and some (in particular PHP) modules that will help make sites to not be inaccessible.
Note I'm not saying that it will make your site accessible! Some guideline rules (e.g. 2.1.2: "no keyboard trap") cover the entirety of the page, so just dropping a module or two into the middle of it won't make the page accessible.
What these modules will do is to ensure that the places where they are used will meet the requirements as much as possible.
Oh, and each module will also have a list of sufficient techniques that they satisfy (in case you wish to make a conformance claim).
Monday, 4 January 2016
Updating our Christmas lights
Part #3
Firmware
The firmware (embedded software within a microprocessor/microcontroller) is done. Well, the first cut, anyway. As is the first cut of the board.But I have a huge problem to figure out: how to test the firmware? I particularly wish to test the I2C interactions, as I've never written full-on I2C interaction code (though, in truth, I've pulled much from a Microchip PIC application note... but the issue is that the AN is written for a different chip and the memory layout is different, resulting in different code in many places!).
The cause of my dilemma is that the simulator within the MPLAB X development environment does not include simulation of signal-level communications devices, including I2C - meaning the first time my firmware code looks to verify a response from the target memory, an infinite loop occurs as the response is never received. I can prod the I2C module by injecting anticipated target memory responses, but that's not the same. And my preference is to test the firmware separate to the hardware if at all possible - so that any issues with the board can be pinned down to the board specifically, rather than a possible combination of hardware/firmware errors.
I've looked around to see if there were packages for my particular controller of choice for this circuit, but alas there aren't (though one company did have a nice special that ended early this morning our time on similar but not quite the same chips, so I didn't buy it... Part of me is regretting that already since some of the other devices I'd been looking at using the in past were in that deal, and the package included I2C simulation specifically with one of the EEPROM memories I'm looking at).
Another alternative is to write my own, possibly using the VLAB tools (written by a former workplace of mine, ASTC). The only problem there is that I'd still have to write a whole bunch of models for each of the PIC on-chip peripherals, and then I'd have to test the models individually in order to ensure the models were accurate before running my potentially dodgy code over an integrated set of them.
Alternately, I just do what I'd least like to do: finalise the board and build it (using something like ZIF sockets, particularly for the PIC chip to allow its extraction for re-programming). My PIC programmer, however, doesn't include the capacity to program the I2C EEPROMs... so I'll also need to buy a programmer board for them, too. Found a free board/PIC-based program at http://openprog.altervista.org/OP_eng.html, so have downloaded it and will proceed to get the components, make that board and see how I go.
My plan is to program one address in one chip, and write a small program that simply extracts that one value, and dump that 8-bit value out of port C on the PIC (via a bunch of LEDs). If that's successful, then I can proceed with the full version as the full firmware basically does the same job throughout.
Moreover, the 6 hardware PWM modules are pretty easy to configure, and the interrupts for the software PWM and the 0.56s and 2s periodic flashing lights I can simulate using the package (I will need to inject the I2C responses in order to get to that part of the code, though).
Saturday, 2 January 2016
Updating our Christmas lights
Part #2
PWM configuration
In my previous post, I'd selected the PIC16F18344 as the baseline controller, but mentioned it had an issue with the internal EEPROM memory (that's memory that isn't erased when the controller loses power... an important consideration since we run our lights off a timer switch, so when they go off at night the controller will lose power!): it had nowhere near enough.Running the controller flat out at 32MHz internal clock (instructions are executed every 4 clock cycles) means that the PWM modules can have a base frequency of not quite 500Hz (488.28125Hz, actually). This is the TMR2 (timer 2) module with the 1:64 prescaler and rolling over once every 256 cycles (since TMR2 is an 8-bit timer) at that /64 clock. TMR2 can be used as a period timer for the PWM modules, and 488Hz gives a period far shorter than the human eye's sample rate (PWM period is approx 2ms, human eye's sample rate is approx 32ms).
Given that I'm after 8 PWM outputs, 2 of which need software interrupt control, I need to run the chip as fast as possible, otherwise there'll be out-of-synch issues with the software PWM timing. Given that the PWM values need to be extracted from somewhere, I can of course compensate for that to some extent by factoring in a small buffer for values between full off and full on.
Here's what the 8 PWM outputs will do:
- CCP1 (put into PWM mode) will run the blue section for blue/green/red/orange sets
- CCP2 (put into PWM mode) will run the green section for blue/green/red/orange sets
- CCP3 (put into PWM mode) will run the red section for blue/green/red/orange sets
- CCP4 (put into PWM mode) will run the orange section for blue/green/red/orange sets
- PWM5 (dedicated PWM module) will run section 1 of the icicle lights
- PWM6 (dedicated PWM module) will run section 2 of the icicle lights
- software PWM using TMR1 will run one set of monochrome lights
- software PWM using TMR3 will run a second set of monochrome lights
- TMR1's and TMR3's interrupts fire when they overflow. Thus, with each timer pre-loaded with a duty-cycle value, the overflow interrupt handler toggles a separate GPIO pin low.
- TMR2's interrupt also fires on overflow; this overflow co-incides with the CCP and PWM modules also beginning a new period and their dedicated hardware PWM outputs going high. So the TMR2 interrupt also fires a handler to set the TMR1 and TMR3 GPIO pins high.
Port C on the PIC16F18344 is an 8-bit port, and can easily be configured to have the 6 hardware PWMs route directly out, so will simply manually trigger RC6 and RC7 off the software PWMs. So there we go: 8 dedicated PWM output pins!
Periodic half-duty-cycle flashing
I've also had a thought about periodic flashing lights of a different sort: every half second and every 2 seconds. These'll run off TMR5, which will roll over every 0.5s, and an external counter in a variable will count every time it rolls over; once this count hits 4, the 2s output is toggled. No interrupts will handle this, as the main program is otherwise just clearing the watch-dog timer and getting set up for the next duty cycle values for the PWMs, so having it poll the TMR5 overflow flag status bit will be OK as well (none of the other tasks - including the interrupts - actually takes more than a few milliseconds, so you won't see any deviation in the period. I hope).
Will have to sort out which pins to use, once I solve the memory issues...
Memories, all alone with my memories...
(sorry, couldn't resist that).I've discovered that Microchip/Atmel make a nice set of EEPROM chips with an I2C interface. The chips - regardless of the amount of memory on board - have only 8 pins. More importantly, any up to the 512kbit memories have 3 of their pins able to set a hardware address, so that you can have 8 different chips on the one I2C bus which can be selectable by the I2C control word sent as the first part of the I2C transaction. 8 EEPROM chips: one for each PWM channel! Sweet.
And, with 512kbits and 16-bit duty cycle values per PWM channel, I've got 32768 separate values I can use.
I'm not sure I want to have a re-load of the duty cycle every time the PWM rolls over. That gives me a complete pattern cycle of only just over a minute!
A better option is to have it trigger over 10th of a second, or as close to possible. This allows for significantly fast flashing if I need, but also allows for less chunky steps in the gradual bright/dim pattern as well (some early controllers with only 4 bit PWM resolution clearly show the steps and they look horrible).
I can configure TMR0 of the PIC16F13844 to fire every 0.098s, which is close enough. 512kbit EEPROMs thus give me a full pattern cycle of around 57 minutes. Nice :-) .
The PIC16F18344 defaults to the SCL I/O pin on RB4 and SDA on RB6 (clock and data lines of the two-wire I2C bus). That leaves me RB5 and RB7 to use for the 0.5s and 2s period outputs, and leave port A completely free for other stuff if I need (though RA3 I'll use as a manual reset input - probably not used on the final product but at least I can use it during hardware debugging!).
Off to design the circuit, board layout and firmware for the PIC16F18344. Stay tuned...
Friday, 25 December 2015
Updating our Christmas lights
Transitioning our Christmas lights to a central controller
Those who know us know that we have a reasonably decent Christmas lights display. Our home is blessed with a very large front gable: the entire width of the house!And because we've got a corner block with the house at an angle to the streets, we've actually got 2.5 sides of the house able to support lights (the 0.5 being the western end where, part-way along the carport, the neighbour's carport butts onto our roofline).
So, we've currently got 8 sets of deep blue icicle lights stretched all the way around almost to the back of the house. We've got a few more sets (e.g. some stars and shooting stars) on the house proper and in the garden. We've recently added some lights to the tree in our front yard: warm white around the trunk and partway up the two main branches, and some pink in the foliage (the pink matching the colour of the blossoms in mid December).
The Problem
These lights all run off separate controllers. There are 8 identical but unique controllers for the icicles, for example, and while these controllers state that they are supposed to remember their pattern (I endeavoured to have them all on the same pattern), they simply don't. This leaves wierd strobing effects at the set boundaries in places and makes me hope none of the neighbours suffer from epilepsy!And then there's the fact that none of the other sets work in tandem.
It all looks a jumbled mess to be honest - and I'd like something different
My Plan
I've had a look at some computer-controlled systems, and they all seem to rely on music input. I'd rather not do that, since we have a community hall on the street and we (along with the neighbours) are pretty sick of loud music most of the night in some occasions (even if it is nice).I've discovered a nice newish microcontroller, the PIC16F18344. It has 2 dedicated PWM (pulse width modulation) outputs along with 4 additional possible PWM outputs in a set of multi-function modules. And there are two additional timer modules which can be configured together for another couple of additional software PWM outputs. 8 PWM outputs; yum!
Why all those outputs?
2 of the outputs will be used to drive the two halves of the icicle lights. All of the strands can be linked together and run off them, so they'll all be properly synchronised: all the way around the house! Our waterfall-style Christmas tree on the front of the house has 2 channels, too, as do the pink lights in our tree... so I can run these off the same channels for really nice synch of the major display pieces.4 of the outputs will be for the RGBO style lights (red-green-blue-orange), of which we've got a few sets (including my very favourite mini shooting stars, bought from Coles of all places all the way back in 2008 and still mostly going strong).
The last 2 channels will be for any monochromatic pieces that we might like to link in (there are some that we'll leave permanently on, for example the long warm white strand around the tree... it's simply stunning as-is and making it flash, etc., I think will detract from the spectacle). Actually, I'm not sure we've got any of these at the moment, but that might change... and I'd be silly to not include it in the chip when I program it since it's not terribly hard!
Why PWM?
PWM allows the signal to quickly switch on-off the light strings (works really well with LEDs in particular). This on-off occurs much faster than the human eye can detect, but the amount on and off modulates the apparent LED brightness... so if the lights are on only 10% of the time, they'll appear to have 10% of the brightness compared to constantly on. Or something like that.Where to from here?
Well, I need to start working on the program for the chip. In fact, it's not terribly hard, since the dedicated PWM modules require only a few instructions to configure and set going... ditto the CCP modules (an additional instruction required to put them in PWM mode compared to the dedicated PWM modules). The 2 software PWM modules utilise the same base timer as the PWM/CCP modules but require pre-loading the timer modules with particular values, and handling the interrupts with dedicated code to toggle some general purpose I/O pins (the PWM and CCP modules toggle their outputs automatically... once set up, they can pretty much run forever without monitoring).The only issue is working out the timer control values to pump into the various PWM modules to make them flash, dim, etc. And then having some way of stringing the values together to make a set of patterns... which could be a problem since the chip has only 256 bytes of EEPROM. At 8 channels needing 2 bytes per channel per value, 256 bytes leaves only 16 combinations of value! I'd need probably 10 times that... Looks like some kind of external EEPROM interface, though the chip does have UART, SPI and IIC inter-chip communications modules to help...
Hopefully, more on this later!
Monday, 30 March 2015
Gluten-free, sugar-free Part #1
My gluten-free, sugar-free journey.
Part 1
For many years I've known that my body has an issue with gluten and sugar. But there's a big problem if you try to find pre-made foods: you can (sometimes) find gluten-free products, or (more rarely) sugar-free products, but both? Not a hope.So I've decided enough is enough and I'm embarking on a journey to find GF+SF recipes, or invent my own. The main reason is that now I have a little girl and I'd like to be around longer for her if I can... so looking after myself better is more a priority. Besides, I'm hoping that as she grows she'll pick up the desire to cook/bake like I do (though it is the case that I've struggled to be organised enough to actually follow through on it), and that she'll be healthier for it in the long run, too.
Given that I'm suffering from a severely sore throat and lost voice at the time of writing (not to mention that it's become hot-ish again), I thought I'd start with ice cream. Just plain vanilla to start with.
I own an ice cream maker with a freezable base. I bought it before I got married (I gave my lovely missus one as well at the time, so technically I own two... though one base has gone missing in the interim...), and have used it precisely once. The ice cream was a flop and I didn't ever try again.
So, here's the ingredients I used:
- 250ml A2 cream
- 300ml A2 skim milk
- 1/2 cup Natvia
- 1/2 tsp vanilla essence
- The recipe came from the instructions for the ice cream maker, but I'm tailoring it to my needs. It's the "simple plain vanilla".
- I'm using A2 dairy (since I'm alpha-1-beta-casein intolerant as well); regular dairy will work fine.
- Natvia is a stevia-based sweetener, that's normally a 1-for-1 replacement with sugar. I say normally because my experiences previously are that it doesn't always square away that easily
- I omitted the salt; I saw no reason for it.
Simply mix the ingredients together and pour into ice cream maker and leave to churn for 20-40 minutes. In theory.
The first problem was one I should have predicted. Natvia is crystals of stevia with a bulking agent since stevia is about 1000x sweeter than sugar. However, it does not dissolve the way sugar does; I know this because I sometimes have stevia-based strawberry and chocolate milk mixes (think Quik but without the sugar) and the sweetener doesn't dissolve unless you heat the drink a bit.
To solve this, I dragged out the electric beaters and used that on a low setting for a while. It worked to some extent but left a wierd slurry in the bottom of the bowl.
The second problem is, I think, a reflection of the first. Having made the ice cream yesterday, I went to eat some today and it was rock hard. The slurry mixed through the cream mixture seems to have set quite firmly indeed. After allowing it to thaw, though, scooping out of the container became easier (it still seemed like cutting it worked better, and it was rather brittle).
The third problem was the stevia crystals themselves. Again a reflection of the first, the crystals were detectable in the ice cream so it didn't have a smooth texture. But I did use regular Natvia...
Finally, it is waaaay too sweet.
Apart from those issues, it was still very creamy but not overly so, and with some tweaks could be very morish (though because it's got only fat and no sugar other than the lactose from the dairy, it satisfies faster than regular ice cream).
So, over the coming long weekend, I intend to try it again but insteasd use 1/4 cup of the icing mixture version of Natvia (this is powdered, not crystalline, so shouldn't have the problems with the crystals or mixing).
I'll let you know how it goes!
The next job after thatis GF+SF donuts. I miss donuts. Lots.
And no, I'm not Homer Simpson ;-)
Friday, 20 March 2015
C++ integer event handling
Background
I've recently been looking into event handling in C++; the particular case is SDL (a C library for cross-platform GUI, etc., implementations). The event is identified as some kind of integer, and the handling of each different integer is done via a specialised virtual function (allowing sub-classes to override the default behaviour).Most cases I've seen have event integers as non-contiguous, and SDL is no exception. In straight C the best approach is to use a switch statement since there's little chance (short of creating your own struct) of making a sparse matrix handler to limit the amount of memory contained (SDL has a maximum event ID of 65536, though only a limited number of numbers below 4096 are actually assigned as event IDs).
In C++, there's a few different options for sparse matrix handling, but the two best out-of-the-box ones are std::map and boost:unordered_map.
I wanted to evaluate the execution speeds (read: overhead) of the different mechanisms (i.e. std::map, boost::unordered_map and switch). For completeness, I'll throw in a pure array and an if-else method.
Note: I used BOOST 1.47.0, which is not the latest (at the time of writing, that's 1.53.0), so there may be speed improvements between library versions.
The mechanism
The idea was to push 1,000,000 randomly-generated numbers between 0-99 inclusive (i.e. there are 100 different event IDs), using a normal distribution to mimic the fact that some events occur more frequently than others. To achieve this, I used the BOOST random library.Each event ID is routed via the particular mechanism to a single virtual function of the class as the callback, which takes the event ID as a parameter (in lieu of more complex possible callbacks which typically require some kind of argument list). The callback is implemented to do nothing, minimising any overhead of the callback operation in skewing the timing data.
The array implementation was a simple array of member function pointers. The boost::unordered_map implementation was a map of the event id as the key with a member function pointer as the item. Likewise the std::map implementation. These 3 arrays were initialised with the callback pointer assigned in the constructor of the class; the overhead of doing so was not included in the timing calculations.
With a set of numbers fixed per run, each number set is pumped through the different algorithms and the elapsed execution time captured. Since each algorithm is executed sequentially but in the one program, the possibility of statistical anomalies caused by random external processes are reduced. Running the program multiple times and aggregating the results should hopefully further reduce the possibility of statistical anomalies.
The code was compiled under MS VC 2010 Express, in release mode, and run 10 times on my AMD A6 quad-core laptop with 4GB RAM running Win7 x64 home premium. The only other app open other than Visual Studio was Firefox.
The results
All numbers in the below table are in milliseconds.
| Algorithm | Run 1 | Run 2 | Run 3 | Run 4 | Run 5 | Run 6 | Run 7 | Run 8 | Run 9 | Run 10 | Mean |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Array | 6 | 5 | 6 | 6 | 6 | 6 | 6 | 7 | 6 | 5 | 5.9 |
| boost::unordered_map | 36 | 31 | 33 | 35 | 34 | 33 | 32 | 33 | 38 | 31 | 33.6 |
| if | 88 | 87 | 88 | 90 | 88 | 88 | 89 | 85 | 88 | 86 | 87.7 |
| std::map | 77 | 75 | 77 | 76 | 77 | 77 | 76 | 75 | 78 | 74 | 76.2 |
| switch | 22 | 21 | 21 | 21 | 22 | 21 | 21 | 21 | 22 | 21 | 21.3 |
Analysis
At first, some of the results seem a little bizarre. Naturally, the slowness of the if mechanism was unsurprising, as was the absolute raw speed of the straight array implementation.What was surprising was that the switch method was only 2/3 of the execution time of the boost::unordered_map. Likewise, the MSVC implementation of std::map meant it executed more than twice as slowly as the boost::unordered_map. It's probable that different compiler/library implementations of std::map will render different results to that here; I've used MSVC since as a non-open-source compiler/library I'm assuming that it will have the least efficient implementation.
It all needs to be kept in context, however, and that these times in milliseconds are for 1,000,000 events. For each event, then, these times for the callback are in nanoseconds; on the face of it, if you're talking frame rates in the 50-100 fps category, then a processing time of even 88ns per event is not significant. However, this timing doesn't take into account the follow-up processing done by the event handling and the corresponding memory updates required, e.g., for rending the frame using different textures that perhaps aren't cached. It's still therefore good to minimise the time spent doing the actual event isolation detection as fast as possible...
Conclusions
If your program can take the memory hit of a pure array, then clearly this is the single fastest method of handling the events. However, should the IDs of the events change, then there is a significant potential of problematic maintenance with changing the array element assignments (particularly if a sparse assignment is used with some kind of default "not-handled" handler for the rest). There is a significant drawback with this approach, which is the significant memory overhead required (e.g. for SDL, on a 32-bit system, allocating an array for the entire 65536 event ID space requires 256k); the sparse-matrix approach using boost::unordered_map or std::map can reduce this (especially if only a limited subset of events are intended to be handled), however on memory-limited platforms (e.g. mobiles) this approach should probably be avoided (even the sparse-matrix approach).Of the more algorithmic handling (compared to the brute-force of the pure array), either the switch or the boost::unordered_map would be the options. The switch method is still faster, but it does lead to rather ugly code which could have significant maintenance issues unless the event IDs are rendered as constants somewhere. The boost::unordered_map throws up some different potential maintenance issues, in that the linkage between event IDs and the callbacks is not programatic, so changes would not necessarily be in the place a maintainer might expect.
Regardless, the choice of if and std::map should be avoided.
Code
Here's the code used in this experiment:#include <map>
#include <boost/unordered_map.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <ctime>
#include <iostream>
class member_fns
{
public:
member_fns(void);
~member_fns(void);
void HandleIntViaArray(void);
void HandleIntViaHash(void);
void HandleIntViaIf(void);
void HandleIntViaMap(void);
void HandleIntViaSwitch(void);
protected:
static const unsigned int NUM_NUMBERS;
typedef void (member_fns::*mem_func)(int);
virtual void Handler(int n);
std::map<int, mem_func> _map;
boost::unordered_map<int, mem_func> _hashmap;
mem_func _array[100];
int * _nums;
};
const unsigned int member_fns::NUM_NUMBERS = 1000000;
member_fns::member_fns(void)
{
int i = 0;
for ( ; i < 100 ; ++i) {
_map[i] = &member_fns::Handler;
_hashmap[i] = &member_fns::Handler;
_array[i] = &member_fns::Handler;
}
boost::random::mt19937 gen;
boost::random::uniform_int_distribution<> dist(1, 100);
_nums = new int[member_fns::NUM_NUMBERS];
for (i = 0 ; i < member_fns::NUM_NUMBERS ; ++i) {
_nums[i] = dist(gen) - 1;
}
}
member_fns::~member_fns(void)
{
delete [] _nums;
}
void member_fns::HandleIntViaArray(void)
{
int i = 0;
clock_t end = 0;
clock_t start = clock();
for ( ; i < member_fns::NUM_NUMBERS ; ++i) {
(this->*_array[_nums[i]])(_nums[i]);
}
end = clock();
std::cout << "Array " << (end - start)/double(CLOCKS_PER_SEC)*1000 << " ms" << std::endl;
}
void member_fns::HandleIntViaIf(void)
{
int i = 0;
clock_t end = 0;
clock_t start = clock();
for ( ; i < member_fns::NUM_NUMBERS ; ++i) {
if (_nums[i] == 0) {
Handler(_nums[i]);
}
else if (_nums[i] == 1) {
Handler(_nums[i]);
}
else if (_nums[i] == 2) {
Handler(_nums[i]);
}
else if (_nums[i] == 3) {
Handler(_nums[i]);
}
else if (_nums[i] == 4) {
Handler(_nums[i]);
}
else if (_nums[i] == 5) {
Handler(_nums[i]);
}
else if (_nums[i] == 6) {
Handler(_nums[i]);
}
else if (_nums[i] == 7) {
Handler(_nums[i]);
}
else if (_nums[i] == 8) {
Handler(_nums[i]);
}
else if (_nums[i] == 9) {
Handler(_nums[i]);
}
else if (_nums[i] == 10) {
Handler(_nums[i]);
}
else if (_nums[i] == 11) {
Handler(_nums[i]);
}
else if (_nums[i] == 12) {
Handler(_nums[i]);
}
else if (_nums[i] == 13) {
Handler(_nums[i]);
}
else if (_nums[i] == 14) {
Handler(_nums[i]);
}
else if (_nums[i] == 15) {
Handler(_nums[i]);
}
else if (_nums[i] == 16) {
Handler(_nums[i]);
}
else if (_nums[i] == 17) {
Handler(_nums[i]);
}
else if (_nums[i] == 18) {
Handler(_nums[i]);
}
else if (_nums[i] == 19) {
Handler(_nums[i]);
}
else if (_nums[i] == 20) {
Handler(_nums[i]);
}
else if (_nums[i] == 21) {
Handler(_nums[i]);
}
else if (_nums[i] == 22) {
Handler(_nums[i]);
}
else if (_nums[i] == 23) {
Handler(_nums[i]);
}
else if (_nums[i] == 24) {
Handler(_nums[i]);
}
else if (_nums[i] == 25) {
Handler(_nums[i]);
}
else if (_nums[i] == 26) {
Handler(_nums[i]);
}
else if (_nums[i] == 27) {
Handler(_nums[i]);
}
else if (_nums[i] == 28) {
Handler(_nums[i]);
}
else if (_nums[i] == 29) {
Handler(_nums[i]);
}
else if (_nums[i] == 30) {
Handler(_nums[i]);
}
else if (_nums[i] == 31) {
Handler(_nums[i]);
}
else if (_nums[i] == 32) {
Handler(_nums[i]);
}
else if (_nums[i] == 33) {
Handler(_nums[i]);
}
else if (_nums[i] == 34) {
Handler(_nums[i]);
}
else if (_nums[i] == 35) {
Handler(_nums[i]);
}
else if (_nums[i] == 36) {
Handler(_nums[i]);
}
else if (_nums[i] == 37) {
Handler(_nums[i]);
}
else if (_nums[i] == 38) {
Handler(_nums[i]);
}
else if (_nums[i] == 39) {
Handler(_nums[i]);
}
else if (_nums[i] == 40) {
Handler(_nums[i]);
}
else if (_nums[i] == 41) {
Handler(_nums[i]);
}
else if (_nums[i] == 42) {
Handler(_nums[i]);
}
else if (_nums[i] == 43) {
Handler(_nums[i]);
}
else if (_nums[i] == 44) {
Handler(_nums[i]);
}
else if (_nums[i] == 45) {
Handler(_nums[i]);
}
else if (_nums[i] == 46) {
Handler(_nums[i]);
}
else if (_nums[i] == 47) {
Handler(_nums[i]);
}
else if (_nums[i] == 48) {
Handler(_nums[i]);
}
else if (_nums[i] == 49) {
Handler(_nums[i]);
}
else if (_nums[i] == 50) {
Handler(_nums[i]);
}
else if (_nums[i] == 51) {
Handler(_nums[i]);
}
else if (_nums[i] == 52) {
Handler(_nums[i]);
}
else if (_nums[i] == 53) {
Handler(_nums[i]);
}
else if (_nums[i] == 54) {
Handler(_nums[i]);
}
else if (_nums[i] == 55) {
Handler(_nums[i]);
}
else if (_nums[i] == 56) {
Handler(_nums[i]);
}
else if (_nums[i] == 57) {
Handler(_nums[i]);
}
else if (_nums[i] == 58) {
Handler(_nums[i]);
}
else if (_nums[i] == 59) {
Handler(_nums[i]);
}
else if (_nums[i] == 60) {
Handler(_nums[i]);
}
else if (_nums[i] == 61) {
Handler(_nums[i]);
}
else if (_nums[i] == 62) {
Handler(_nums[i]);
}
else if (_nums[i] == 63) {
Handler(_nums[i]);
}
else if (_nums[i] == 64) {
Handler(_nums[i]);
}
else if (_nums[i] == 65) {
Handler(_nums[i]);
}
else if (_nums[i] == 66) {
Handler(_nums[i]);
}
else if (_nums[i] == 67) {
Handler(_nums[i]);
}
else if (_nums[i] == 68) {
Handler(_nums[i]);
}
else if (_nums[i] == 69) {
Handler(_nums[i]);
}
else if (_nums[i] == 70) {
Handler(_nums[i]);
}
else if (_nums[i] == 71) {
Handler(_nums[i]);
}
else if (_nums[i] == 72) {
Handler(_nums[i]);
}
else if (_nums[i] == 73) {
Handler(_nums[i]);
}
else if (_nums[i] == 74) {
Handler(_nums[i]);
}
else if (_nums[i] == 75) {
Handler(_nums[i]);
}
else if (_nums[i] == 76) {
Handler(_nums[i]);
}
else if (_nums[i] == 77) {
Handler(_nums[i]);
}
else if (_nums[i] == 78) {
Handler(_nums[i]);
}
else if (_nums[i] == 79) {
Handler(_nums[i]);
}
else if (_nums[i] == 80) {
Handler(_nums[i]);
}
else if (_nums[i] == 81) {
Handler(_nums[i]);
}
else if (_nums[i] == 82) {
Handler(_nums[i]);
}
else if (_nums[i] == 83) {
Handler(_nums[i]);
}
else if (_nums[i] == 84) {
Handler(_nums[i]);
}
else if (_nums[i] == 85) {
Handler(_nums[i]);
}
else if (_nums[i] == 86) {
Handler(_nums[i]);
}
else if (_nums[i] == 87) {
Handler(_nums[i]);
}
else if (_nums[i] == 88) {
Handler(_nums[i]);
}
else if (_nums[i] == 89) {
Handler(_nums[i]);
}
else if (_nums[i] == 90) {
Handler(_nums[i]);
}
else if (_nums[i] == 91) {
Handler(_nums[i]);
}
else if (_nums[i] == 92) {
Handler(_nums[i]);
}
else if (_nums[i] == 93) {
Handler(_nums[i]);
}
else if (_nums[i] == 94) {
Handler(_nums[i]);
}
else if (_nums[i] == 95) {
Handler(_nums[i]);
}
else if (_nums[i] == 96) {
Handler(_nums[i]);
}
else if (_nums[i] == 97) {
Handler(_nums[i]);
}
else if (_nums[i] == 98) {
Handler(_nums[i]);
}
else if (_nums[i] == 99) {
Handler(_nums[i]);
}
else {
std::cout << "If: Unhandled number " << _nums[i] << std::endl;
}
}
end = clock();
std::cout << "If " << (end - start)/double(CLOCKS_PER_SEC)*1000 << " ms" << std::endl;
}
void member_fns::HandleIntViaHash(void)
{
int i = 0;
clock_t end = 0;
clock_t start = clock();
for ( ; i < member_fns::NUM_NUMBERS ; ++i) {
(this->*_hashmap[_nums[i]])(_nums[i]);
}
end = clock();
std::cout << "Hash " << (end - start)/double(CLOCKS_PER_SEC)*1000 << " ms" << std::endl;
}
void member_fns::HandleIntViaMap(void)
{
int i = 0;
clock_t end = 0;
clock_t start = clock();
for ( ; i < member_fns::NUM_NUMBERS ; ++i) {
(this->*_map[_nums[i]])(_nums[i]);
}
end = clock();
std::cout << "Map " << (end - start)/double(CLOCKS_PER_SEC)*1000 << " ms" << std::endl;
}
void member_fns::HandleIntViaSwitch(void)
{
int i = 0;
clock_t end = 0;
clock_t start = clock();
for ( ; i < member_fns::NUM_NUMBERS ; ++i) {
switch(_nums[i]) {
case 0:
Handler(_nums[i]);
break;
case 1:
Handler(_nums[i]);
break;
case 2:
Handler(_nums[i]);
break;
case 3:
Handler(_nums[i]);
break;
case 4:
Handler(_nums[i]);
break;
case 5:
Handler(_nums[i]);
break;
case 6:
Handler(_nums[i]);
break;
case 7:
Handler(_nums[i]);
break;
case 8:
Handler(_nums[i]);
break;
case 9:
Handler(_nums[i]);
break;
case 10:
Handler(_nums[i]);
break;
case 11:
Handler(_nums[i]);
break;
case 12:
Handler(_nums[i]);
break;
case 13:
Handler(_nums[i]);
break;
case 14:
Handler(_nums[i]);
break;
case 15:
Handler(_nums[i]);
break;
case 16:
Handler(_nums[i]);
break;
case 17:
Handler(_nums[i]);
break;
case 18:
Handler(_nums[i]);
break;
case 19:
Handler(_nums[i]);
break;
case 20:
Handler(_nums[i]);
break;
case 21:
Handler(_nums[i]);
break;
case 22:
Handler(_nums[i]);
break;
case 23:
Handler(_nums[i]);
break;
case 24:
Handler(_nums[i]);
break;
case 25:
Handler(_nums[i]);
break;
case 26:
Handler(_nums[i]);
break;
case 27:
Handler(_nums[i]);
break;
case 28:
Handler(_nums[i]);
break;
case 29:
Handler(_nums[i]);
break;
case 30:
Handler(_nums[i]);
break;
case 31:
Handler(_nums[i]);
break;
case 32:
Handler(_nums[i]);
break;
case 33:
Handler(_nums[i]);
break;
case 34:
Handler(_nums[i]);
break;
case 35:
Handler(_nums[i]);
break;
case 36:
Handler(_nums[i]);
break;
case 37:
Handler(_nums[i]);
break;
case 38:
Handler(_nums[i]);
break;
case 39:
Handler(_nums[i]);
break;
case 40:
Handler(_nums[i]);
break;
case 41:
Handler(_nums[i]);
break;
case 42:
Handler(_nums[i]);
break;
case 43:
Handler(_nums[i]);
break;
case 44:
Handler(_nums[i]);
break;
case 45:
Handler(_nums[i]);
break;
case 46:
Handler(_nums[i]);
break;
case 47:
Handler(_nums[i]);
break;
case 48:
Handler(_nums[i]);
break;
case 49:
Handler(_nums[i]);
break;
case 50:
Handler(_nums[i]);
break;
case 51:
Handler(_nums[i]);
break;
case 52:
Handler(_nums[i]);
break;
case 53:
Handler(_nums[i]);
break;
case 54:
Handler(_nums[i]);
break;
case 55:
Handler(_nums[i]);
break;
case 56:
Handler(_nums[i]);
break;
case 57:
Handler(_nums[i]);
break;
case 58:
Handler(_nums[i]);
break;
case 59:
Handler(_nums[i]);
break;
case 60:
Handler(_nums[i]);
break;
case 61:
Handler(_nums[i]);
break;
case 62:
Handler(_nums[i]);
break;
case 63:
Handler(_nums[i]);
break;
case 64:
Handler(_nums[i]);
break;
case 65:
Handler(_nums[i]);
break;
case 66:
Handler(_nums[i]);
break;
case 67:
Handler(_nums[i]);
break;
case 68:
Handler(_nums[i]);
break;
case 69:
Handler(_nums[i]);
break;
case 70:
Handler(_nums[i]);
break;
case 71:
Handler(_nums[i]);
break;
case 72:
Handler(_nums[i]);
break;
case 73:
Handler(_nums[i]);
break;
case 74:
Handler(_nums[i]);
break;
case 75:
Handler(_nums[i]);
break;
case 76:
Handler(_nums[i]);
break;
case 77:
Handler(_nums[i]);
break;
case 78:
Handler(_nums[i]);
break;
case 79:
Handler(_nums[i]);
break;
case 80:
Handler(_nums[i]);
break;
case 81:
Handler(_nums[i]);
break;
case 82:
Handler(_nums[i]);
break;
case 83:
Handler(_nums[i]);
break;
case 84:
Handler(_nums[i]);
break;
case 85:
Handler(_nums[i]);
break;
case 86:
Handler(_nums[i]);
break;
case 87:
Handler(_nums[i]);
break;
case 88:
Handler(_nums[i]);
break;
case 89:
Handler(_nums[i]);
break;
case 90:
Handler(_nums[i]);
break;
case 91:
Handler(_nums[i]);
break;
case 92:
Handler(_nums[i]);
break;
case 93:
Handler(_nums[i]);
break;
case 94:
Handler(_nums[i]);
break;
case 95:
Handler(_nums[i]);
break;
case 96:
Handler(_nums[i]);
break;
case 97:
Handler(_nums[i]);
break;
case 98:
Handler(_nums[i]);
break;
case 99:
Handler(_nums[i]);
break;
default:
std::cout << "Switch: Unhandled number " << _nums[i] << std::endl;
}
}
end = clock();
std::cout << "Switch " << (end - start)/double(CLOCKS_PER_SEC)*1000 << " ms" << std::endl;
}
void member_fns::Handler(int n)
{
}
int _tmain(int argc, _TCHAR* argv[])
{
member_fns analyser;
analyser.HandleIntViaArray();
analyser.HandleIntViaHash();
analyser.HandleIntViaIf();
analyser.HandleIntViaMap();
analyser.HandleIntViaSwitch();
return 0;
}
Subscribe to:
Posts (Atom)