A blog about my life, thoughts and work. This blog will consist of programming, philosophy, politics, poetry and anything else that I want to talk about.
December 18, 2012
IntelliJ and General Development on a Windows 7 Corporate Laptop...
I recently started working on a new project with a company and discovered that they were in the middle of a project to move all its developers over to using a new laptop build based on Windows 7.
They have a great team working on this but they were relatively inexperienced at dealing with developers requirements so they needed some guinea pigs and our team fitted the bill nicely.
When the proposed laptop was first trialled a misconfiguration of the anti-virus meant that they were very slow, as a result our boss was able to demand SSDs... Which was nice. I've been running SSDs on my home development machines to provide the best possible build performance though I always make sure my machines and repositories are backed up to spinning rust.
The laptops with SSDs finally arrived and it turned out that I was the first of the development focused guys to get our hands on it.
Some clever tooling meant that while none of us developers had admin privileges, there was a clear and sensible route for installation of any software that we required. The technique involved meant that temporary admin privileges were given to the install process which meant that our normal users were unable to alter the installed files in any way. This had several implications for the installation of software, IntelliJ in particular.
I encountered a number of teething problems - the first one was that our user profiles were configured to be read from a network share. Fine as long as we were plugged into our head office networks, less so when we were on the road. The most visible problem was the corporate software provisioning tools having a snit fit and not wing able to launch various items of software if we were connecting using a VPN. The VPN could only be launched after login for various good reasons.
Fixing this problem was easy as it only required using the Offline Sync capabilities in Windows 7 on the necessary parts of our profile.
This however pointed to a more significant problem for developers. By default IntelliJ, Maven and other tools keep a lot of their configuration and caches in the profile. This means a large volume of data needs to be sync-ed initially and as the IntelliJ caches are updated and new Maven dependencies are added even more sync-ing needs to happen. Not great across a VPN over ADSL.
The solution is to reconfigure your tools to store these high entropy folders in the local drive away for your profile. This also makes the support team happy as it stops the developer profiles getting too large and clogging up the networks.
I created a 'development' folder specifically for these folders and my projects.
Configuring Maven is easy: create a settings.xml file in your profile fold if you haven't already and override the default local repository location. At the same time you may as well be sorting out your proxies or any corporate Nexus repositories. Set the Maven environment properties on the user environment variables and you're done.
IntelliJ is not too hard as well. I copied the idea.properties file from the IntelliJ bin directory (I couldn't edit it directly as it had been written with admin privileges) and edited it to move my .IntellijIDEA folder to my development folder. I replaced the ${user.home} entry in all the relevant properties with a hardcoded value as replacing it with a self declared property like ${configuration.home} caused the logging to end up in a directory under IntelliJ called ${configuration.home}.
I then needed to tell IntelliJ to use the new idea.properties file so I had to set up the IDEA_PROPERTIES environment variable. This could not be done as a system environment variable but fortunately user level environment variables do the job. It is worth noting that you should be able override the Path environment variable that is set in the system environment variables by setting one up in the user ones. Anecdotally this can be a bit temperamental if you don't match the case of the system one and you may have problems if the admins have locked it down too.
The same process of configuration needs to be repeated for any other tools that like to put things in the profile.
The next issue encountered was when starting IntelliJ. IntelliJ 11 would start but not be able to index the project whereas IntelliJ 12 would fail completely, complaining about being unable to start webserver processes. It turned out that the McAfee firewall had been configured too aggressively and local loop back calls (localhost and 127.0.0.1) were blocked. A quick chat with the support team sorted this one out.
The last major issue was with performance due to McAfee Data Loss Prevention (DLP). I'd encountered this before on other corporate sites. You need to ensure that your development folder is excluded. This is due to the number of files typically deleted due to a Maven clean install cycle; DLP just eats CPU and slows disk access to a crawl when this happens. Once more the support team came to the rescue.
Once these issues were sorted I was delighted to have a corporate laptop that was entirely pleasant to develop on. As a bonus we were given widescreen 27" monitors, I was able to rotate mine to landscape mode and view entire classes at a time!
- Posted using BlogPress from my iPad
April 21, 2012
Towards a richer GPGPU Mandelbrot Sets and OpenCL example.
I've decided to work on enhancing my Mandelbrot generator and thought I'd share some of my experience doing so. My intention is to create a richer and more capable Mandelbrot Set generator that properly leverages the capabilities of OpenCL and allows someone to more interactively explore the set.
I'm having to dust off my Swing skills to do this as I want to create a richer UI experience. So first off lets talk about current features in my Mandelbrot Generator:
Current Features
- Single, Double and Fixed Precision (128bit) calculation modes.
- Simple zooming using the mouse left button to zoom in and the mouse right button to zoom out.
- Re-sizable window.
- Simple Benchmarking application using the different calculation modes to demonstrate GPU performance using Single, Double and Integer mathematics.
- Automatic detection of Work Group size and appropriate buffering.
Future Features
- Configurable colour palettes.
- Julia Set generation - for the current location in the Mandelbrot set..
- UI components to be able to configure the calculation mode and a few other options.
- Further, richer Fixed Precision modes (256 and 512 bit seem a natural fit for uint8 and uint16)
- Interactive Benchmarking
- Ability to save specific 'views' and 'journeys' through the Mandelbrot Set and interact with them.
- Rendering of ultra high resolution images, either directly rendered in one pass on the GPU or rendered as tiles and stitched together.
- Render to OpenGL contexts and real-time fly throughs of the set.
- JavaCL
- MandelbrotView Interface
- Fixed Precision algorithm
JavaCL
package co.uk.fvdl.mandelbrot;
import co.uk.fvdl.mathematics.FixedPrecision128;
import co.uk.fvdl.util.TwoTuple;
import com.nativelibs4java.opencl.*;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* Generic class for generating Mandelbrot set using OpenCL. May be able to use as the basis for generating other
* fractals such as Julia sets by making it possible to choose different OpenCL programs as parameters.
*/
public class MandelbrotGenerator {
private static final int ESCAPE_THRESHOLD = 4;
private final CLKernel mandelbrotFloatKernel;
private final CLKernel mandelbrotDoubleKernel;
private final CLKernel mandelbrotFixedPrecision128Kernel;
private final CLQueue queue;
private final CLContext context;
private int workgroupWidth;
private int workgroupHeight;
private boolean profiling;
public MandelbrotGenerator(CLContext context, boolean profiling) throws IOException {
this(context, Utility.detectLargest2DWorkGroupSize(context), profiling);
}
public MandelbrotGenerator(CLContext context, TwoTuple<integer, integer> workgroupSize, boolean profiling) throws IOException {
this.profiling = profiling;
System.out.println("Workgroup size: " + workgroupSize);
this.context = context;
queue = context.createDefaultQueue();
queue.setProperty(CLDevice.QueueProperties.ProfilingEnable, profiling);
//Will always support integers...
String fixedPrecision128Source = MandelbrotViewer.readFully(
new InputStreamReader(this.getClass().getResourceAsStream("opencl/MandelbrotFixedPrecision128.cl")),
new StringBuilder()
).toString();
String fixedPoint128LibrarySource = MandelbrotViewer.readFully(
new InputStreamReader(this.getClass().getResourceAsStream("opencl/FixedPrecision128.cl")),
new StringBuilder()
).toString();
CLProgram fixedPrecision128Program = context.createProgram(fixedPoint128LibrarySource, fixedPrecision128Source);
mandelbrotFixedPrecision128Kernel = fixedPrecision128Program.createKernel("MandelbrotFixedPrecision128");
//Will always support floats...
String floatSource = MandelbrotViewer.readFully(
new InputStreamReader(this.getClass().getResourceAsStream("opencl/MandelbrotFloat.cl")),
new StringBuilder()
).toString();
CLProgram floatProgram = context.createProgram(floatSource);
mandelbrotFloatKernel = floatProgram.createKernel("MandelbrotFloat");
//May not always support doubles...
if(context.isDoubleSupported()){
//Read the source file.
String doubleSource = MandelbrotViewer.readFully(
new InputStreamReader(this.getClass().getResourceAsStream("opencl/MandelbrotDouble.cl")),
new StringBuilder()
).toString();
CLProgram doubleProgram = context.createProgram(doubleSource);
mandelbrotDoubleKernel = doubleProgram.createKernel("MandelbrotDouble");
} else {
mandelbrotDoubleKernel = null;
}
this.workgroupWidth = workgroupSize.getFirstElement();
this.workgroupHeight = workgroupSize.getSecondElement();
}
public GenerationResult generateMandelbrot(MandelbrotView mandelbrotView) throws IOException {
Padded2DBuffer padded2DBuffer = new Padded2DBuffer(mandelbrotView.getRealAxisPixelCount(), mandelbrotView.getImaginaryAxisPixelCount(), workgroupWidth, workgroupHeight);
CLBuffer<integer> outputBuffer = context.createBuffer(CLMem.Usage.Output, padded2DBuffer.getPointer());
CLEvent event;
CLKernel mandelbrotKernel = null;
switch (mandelbrotView.getMandelbrotGenerationStrategy()){
case DOUBLE:
if(context.isDoubleSupported()){
mandelbrotKernel = mandelbrotDoubleKernel;
mandelbrotKernel.setArgs(
new double[]{mandelbrotView.getRealAxisPixelSize().doubleValue(), mandelbrotView.getImaginaryAxisPixelSize().doubleValue()},
new double[]{mandelbrotView.getRealAxisMinimum().doubleValue(), mandelbrotView.getImaginaryAxisMinimum().doubleValue()},
mandelbrotView.getMaxIter(), ESCAPE_THRESHOLD, padded2DBuffer.getActualWidth(), outputBuffer);
break;
}
case SINGLE:
mandelbrotKernel = mandelbrotFloatKernel;
mandelbrotKernel.setArgs(
new float[]{mandelbrotView.getRealAxisPixelSize().floatValue(), mandelbrotView.getImaginaryAxisPixelSize().floatValue()},
new float[]{mandelbrotView.getRealAxisMinimum().floatValue(), mandelbrotView.getImaginaryAxisMinimum().floatValue()},
mandelbrotView.getMaxIter(), ESCAPE_THRESHOLD, padded2DBuffer.getActualWidth(), outputBuffer);
break;
case FP128:
mandelbrotKernel = mandelbrotFixedPrecision128Kernel;
mandelbrotKernel.setArgs(
FixedPrecision128.convertToFixedPrecision128(mandelbrotView.getRealAxisPixelSize()), FixedPrecision128.convertToFixedPrecision128(mandelbrotView.getImaginaryAxisPixelSize()),
FixedPrecision128.convertToFixedPrecision128(mandelbrotView.getRealAxisMinimum()), FixedPrecision128.convertToFixedPrecision128(mandelbrotView.getImaginaryAxisMinimum()),
mandelbrotView.getMaxIter(), ESCAPE_THRESHOLD, padded2DBuffer.getActualWidth(), outputBuffer);
break;
}
event = mandelbrotKernel.enqueueNDRange(queue, new int[]{padded2DBuffer.getActualWidth(), padded2DBuffer.getActualHeight()}, new int[]{workgroupWidth, workgroupHeight});
outputBuffer.read(queue, padded2DBuffer.getPointer(), true, event);
if(profiling){
return new GenerationResult(padded2DBuffer, event.getProfilingCommandEnd() - event.getProfilingCommandStart());
} else {
return new GenerationResult(padded2DBuffer, Long.MIN_VALUE);
}
}
public boolean isDoubleAvailable() {
return context.isDoubleSupported();
}
}
MandelbrotView
package co.uk.fvdl.mandelbrot;
import java.math.BigDecimal;
/**
* Classes implementing this interface represent views onto the Mandelbrot set coordinate space. It is fundamentally
* represented in terms of the centre of the view on the set and in terms of the zoom level represented by the span of a
* pixel in that coordinate space. This representation of the view makes it easier to handle re-sizing of the containing
* window, treating it as a viewport on to the Mandelbrot set.
*/
public interface MandelbrotView {
/**
* Get the size of pixels on the Real Axis.
* @return the size of pixels on the Real Axis.
*/
BigDecimal getRealAxisPixelSize();
/**
* Get the size of pixels on the Imaginary Axis.
* @return the size of pixels on the Imaginary Axis.
*/
BigDecimal getImaginaryAxisPixelSize();
/**
* Get the number of pixels currently used in the view on the real axis.
* @return the number of pixels currently used in the view on the real axis.
*/
int getRealAxisPixelCount();
/**
* Get the number of pixels currently used in the view on the imaginary axis.
* @return the number of pixels currently used in the view on the imaginary axis.
*/
int getImaginaryAxisPixelCount();
/**
* Set the number of pixels to be used in the view on the real axis.
* @param realAxisPixelCount the number of pixels currently used in the view on the real axis.
*/
void setRealAxisPixelCount(int realAxisPixelCount);
/**
* Set the number of pixels to be used in the view on the imaginary axis.
* @param imaginaryAxisPixelCount the number of pixels currently used in the view on the imaginary axis.
*/
void setImaginaryAxisPixelCount(int imaginaryAxisPixelCount);
/**
* Calculates the minimum value that the current view has on the real axis.
* @return the minimum value that the current view has on the real axis.
*/
BigDecimal getRealAxisMinimum();
/**
* Calculates the minimum value that the current view has on the imaginary axis.
* @return the minimum value that the current view has on the imaginary axis.
*/
BigDecimal getImaginaryAxisMinimum();
/**
* Create a new MandelbrotView for a sub-rectangle of the current view. It will preserve the same pixel dimensions
* as the original and so will choose the scaling axis to ensure that the whole of the selected rectangle is
* visible within the new MandelbrotViewImpl instance.
* @param realAxisPosition The pixel position on the real axis within the current view of the top left corner of the
* rectangle.
* @param imaginaryAxisPosition The pixel position on the imaginary axis within the current view of the top left
* corner of the rectangle.
* @param realAxisPixels The pixel width on the real acis of the rectangle
* @param imaginaryAxisPixels The pixel width on the imaginary axis of the rectangle
* @return a new MandelbrotViewImpl representing the sub-rectangle of the current view.
*/
MandelbrotView selectSubRectangle(
int realAxisPosition, int imaginaryAxisPosition,
int realAxisPixels, int imaginaryAxisPixels);
/**
* Create a new MandelbrotView for a super-rectangle of the current view where the current view will be contained
* within the selected rectangle. It will preserve the same pixel dimensions as the original.
* @param realAxisPosition The pixel position on the real axis within the new view of the top left corner of the
* rectangle containing the current view.
* @param imaginaryAxisPosition The pixel position on the imaginary axis within the new view of the top left
* corner of the rectangle containing the current view.
* @param realAxisPixels The pixel width on the real axis of the rectangle
* @param imaginaryAxisPixels The pixel width on the imaginary axis of the rectangle
* @return a new MandelbrotViewImpl representing the super-rectangle of the current view.
*/
MandelbrotView selectSuperRectangle(
int realAxisPosition, int imaginaryAxisPosition,
int realAxisPixels, int imaginaryAxisPixels);
/**
* Get the preferred generation strategy.
* @return the preferred generation strategy.
*/
MandelbrotGenerationStrategy getMandelbrotGenerationStrategy();
/**
* The maximum number if iterations to use before considering that we have escaped the set.
* @return the maximum number of iterations to use.
*/
int getMaxIter();
/**
* Set the maximum number if iterations to use before considering that we have escaped the set.
* @param maxIter the maximum number of iterations to use.
*/
void setMaxIter(int maxIter);
}
While the view interface talks about the 'minimums' and the number of pixels which maps well to what we need to render, the underlying implementation defines the view in terms of the centre of the view and the pixel size. This means that when we resize the window containing the view the view remains centred on what we intend:
package co.uk.fvdl.mandelbrot;
import co.uk.fvdl.mathematics.BigDecimalArithmetic;
import java.math.BigDecimal;
import static co.uk.fvdl.util.Preconditions.checkNotNull;
/**
* This class represents a view onto the Mandelbrot set coordinate space. It is fundamentally represented in terms of
* the centre of the view on the set and in terms of the zoom level represented by the span of a pixel in that coordinate
* space. This representation of the view makes it easier to handle re-sizing of the containing window, treating it as a
* viewport on to the Mandelbrot set.
*/
public class MandelbrotViewImpl implements MandelbrotView {
private final BigDecimal realAxisCentre;
private final BigDecimal imaginaryAxisCentre;
private final BigDecimal realAxisPixelSize;
private final BigDecimal imaginaryAxisPixelSize;
private final BigDecimalArithmetic numberArithmetic;
private final MandelbrotGenerationStrategy mandelbrotGenerationStrategy;
private int realAxisPixelCount;
private int imaginaryAxisPixelCount;
private int maxIter;
/**
* Construct an instance of the Mandelbrot view for the following parameters.
* @param realAxisCentre the centre of the view on the Real Axis.
* @param imaginaryAxisCentre the centre of the view on the Imaginary Axis.
* @param realAxisPixelSize the size of pixels on the Real Axis.
* @param imaginaryAxisPixelSize the size of pixels on the Imaginary Axis.
* @param realAxisPixelCount the number of pixels to be used in the view on the real axis.
* @param imaginaryAxisPixelCount the number of pixels currently used in the view on the imaginary axis.
* @param mandelbrotGenerationStrategy the prefered strategy to use when generating the Mandelbrot set.
* @param maxIter The maximum iteration to use for generating the set.
*/
@SuppressWarnings("unchecked")
public MandelbrotViewImpl(BigDecimal realAxisCentre, BigDecimal imaginaryAxisCentre,
BigDecimal realAxisPixelSize, BigDecimal imaginaryAxisPixelSize,
int realAxisPixelCount, int imaginaryAxisPixelCount, MandelbrotGenerationStrategy mandelbrotGenerationStrategy, int maxIter) {
this.mandelbrotGenerationStrategy = mandelbrotGenerationStrategy;
this.maxIter = maxIter;
this.numberArithmetic = new BigDecimalArithmetic();
this.realAxisCentre = checkNotNull(realAxisCentre, "Real Axis centre is null.");
this.imaginaryAxisCentre = checkNotNull(imaginaryAxisCentre, "Imaginary Axis centre is null.");
this.realAxisPixelSize = checkNotNull(realAxisPixelSize, "Real Axis pixel size is null.");
this.imaginaryAxisPixelSize = checkNotNull(imaginaryAxisPixelSize, "Imaginary Axis pixel size is null.");
this.realAxisPixelCount = realAxisPixelCount;
this.imaginaryAxisPixelCount = imaginaryAxisPixelCount;
}
/**
* Get the centre of the view on the Real Axis.
* @return the centre of the view on the Real Axis.
*/
public BigDecimal getRealAxisCentre() {
return realAxisCentre;
}
/**
* Get the centre of the view on the Imaginary Axis.
* @return the centre of the view on the Imaginary Axis.
*/
public BigDecimal getImaginaryAxisCentre() {
return imaginaryAxisCentre;
}
@Override
public BigDecimal getRealAxisPixelSize() {
return realAxisPixelSize;
}
@Override
public BigDecimal getImaginaryAxisPixelSize() {
return imaginaryAxisPixelSize;
}
@Override
public int getRealAxisPixelCount() {
return realAxisPixelCount;
}
@Override
public int getImaginaryAxisPixelCount() {
return imaginaryAxisPixelCount;
}
@Override
public void setRealAxisPixelCount(int realAxisPixelCount) {
this.realAxisPixelCount = realAxisPixelCount;
}
@Override
public void setImaginaryAxisPixelCount(int imaginaryAxisPixelCount) {
this.imaginaryAxisPixelCount = imaginaryAxisPixelCount;
}
@Override
public BigDecimal getRealAxisMinimum() {
return numberArithmetic.subtract(
realAxisCentre,
numberArithmetic.divide(
numberArithmetic.multiply(
realAxisPixelSize,
numberArithmetic.fromInteger(realAxisPixelCount)
),
numberArithmetic.fromInteger(2)));
}
@Override
public BigDecimal getImaginaryAxisMinimum() {
return numberArithmetic.subtract(
imaginaryAxisCentre,
numberArithmetic.divide(
numberArithmetic.multiply(
imaginaryAxisPixelSize,
numberArithmetic.fromInteger(imaginaryAxisPixelCount)
),numberArithmetic.fromInteger(2)));
}
@Override
public MandelbrotViewImpl selectSubRectangle(
int realAxisPosition, int imaginaryAxisPosition,
int realAxisPixels, int imaginaryAxisPixels) {
BigDecimal scale;
if((realAxisPixelCount / imaginaryAxisPixelCount) > (realAxisPixels/imaginaryAxisPixels)){ //use the imaginary axis to provide the scaling
scale = numberArithmetic.divide(
numberArithmetic.fromInteger(imaginaryAxisPixels),
numberArithmetic.fromInteger(imaginaryAxisPixelCount)
);
} else { //use the real axis to provide scaling.
scale = numberArithmetic.divide(
numberArithmetic.fromInteger(realAxisPixels),
numberArithmetic.fromInteger(realAxisPixelCount)
);
}
float newRealPixelCentre = (float)realAxisPosition + (float)realAxisPixels / 2.0F;
float newImaginaryPixelCentre = (float)imaginaryAxisPosition + (float)imaginaryAxisPixels / 2.0F;
float currentRealPixelCentre = (float)realAxisPixelCount / 2.0F;
Float currentImaginaryPixelCentre = (float)imaginaryAxisPixelCount / 2.0F;
BigDecimal deltaRealPixels = numberArithmetic.fromFloat(newRealPixelCentre - currentRealPixelCentre);
BigDecimal deltaImaginaryPixels = numberArithmetic.fromFloat(newImaginaryPixelCentre - currentImaginaryPixelCentre);
BigDecimal newRealAxisCentre = numberArithmetic.add(
realAxisCentre,
numberArithmetic.multiply(
deltaRealPixels,
realAxisPixelSize));
BigDecimal newImaginaryAxisCentre = numberArithmetic.add(
imaginaryAxisCentre,
numberArithmetic.multiply(
deltaImaginaryPixels,
imaginaryAxisPixelSize));
BigDecimal newRealAxisPixelSize = numberArithmetic.multiply(realAxisPixelSize, scale);
BigDecimal newImaginaryAxisPixelSize = numberArithmetic.multiply(imaginaryAxisPixelSize, scale);
return new MandelbrotViewImpl(
newRealAxisCentre, newImaginaryAxisCentre,
newRealAxisPixelSize, newImaginaryAxisPixelSize,
realAxisPixelCount, imaginaryAxisPixelCount, mandelbrotGenerationStrategy, maxIter);
}
@Override
public MandelbrotViewImpl selectSuperRectangle(
int realAxisPosition, int imaginaryAxisPosition,
int realAxisPixels, int imaginaryAxisPixels) {
BigDecimal scale;
if((realAxisPixelCount / imaginaryAxisPixelCount) > (realAxisPixels/imaginaryAxisPixels)){ //use the imaginary axis to provide the scaling
scale = numberArithmetic.divide(
numberArithmetic.fromInteger(imaginaryAxisPixelCount),
numberArithmetic.fromInteger(imaginaryAxisPixels)
);
} else { //use the real axis to provide scaling.
scale = numberArithmetic.divide(
numberArithmetic.fromInteger(realAxisPixelCount),
numberArithmetic.fromInteger(realAxisPixels)
);
}
int newRealPixelCentre = realAxisPixelCount / 2;
int newImaginaryPixelCentre = imaginaryAxisPixelCount / 2;
int currentRealPixelCentre = realAxisPosition + realAxisPixels / 2;
int currentImaginaryPixelCentre = imaginaryAxisPosition + imaginaryAxisPixels / 2;
BigDecimal deltaRealPixels = numberArithmetic.fromInteger(newRealPixelCentre - currentRealPixelCentre);
BigDecimal deltaImaginaryPixels = numberArithmetic.fromInteger(newImaginaryPixelCentre - currentImaginaryPixelCentre);
BigDecimal newRealAxisPixelSize = numberArithmetic.multiply(realAxisPixelSize, scale);
BigDecimal newImaginaryAxisPixelSize = numberArithmetic.multiply(imaginaryAxisPixelSize, scale);
BigDecimal newRealAxisCentre = numberArithmetic.add(
realAxisCentre,
numberArithmetic.multiply(
deltaRealPixels,
newRealAxisPixelSize));
BigDecimal newImaginaryAxisCentre = numberArithmetic.add(
imaginaryAxisCentre,
numberArithmetic.multiply(
deltaImaginaryPixels,
newImaginaryAxisPixelSize));
return new MandelbrotViewImpl(
newRealAxisCentre, newImaginaryAxisCentre,
newRealAxisPixelSize, newImaginaryAxisPixelSize,
realAxisPixelCount, imaginaryAxisPixelCount, mandelbrotGenerationStrategy, maxIter);
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer();
sb.append("MandelbrotViewImpl");
sb.append("{maxIter=").append(maxIter);
sb.append(", realAxisCentre=").append(realAxisCentre);
sb.append(", imaginaryAxisCentre=").append(imaginaryAxisCentre);
sb.append(", realAxisPixelCount=").append(realAxisPixelCount);
sb.append(", imaginaryAxisPixelCount=").append(imaginaryAxisPixelCount);
sb.append(", realAxisPixelSize=").append(realAxisPixelSize);
sb.append(", imaginaryAxisPixelSize=").append(imaginaryAxisPixelSize);
sb.append(", mandelbrotGenerationStrategy=").append(mandelbrotGenerationStrategy);
sb.append('}');
return sb.toString();
}
@Override
public MandelbrotGenerationStrategy getMandelbrotGenerationStrategy() {
return mandelbrotGenerationStrategy;
}
@Override
public int getMaxIter() {
return maxIter;
}
@Override
public void setMaxIter(int maxIter) {
this.maxIter = maxIter;
}
}
package co.uk.fvdl.mandelbrot;
import java.math.BigDecimal;
import static co.uk.fvdl.util.Preconditions.checkNotNull;
/**
* This class provides a level of indirection that allows us to work with immutable implementations of the
* MandelbrotView interface without having to deal with a large amount of reference passing in UI components as the
* view is modified.
*/
public class MandelbrotViewHolder implements MandelbrotView {
private MandelbrotView wrappedInstance;
/**
* Construct and instance using the wrapped instance.
* @param wrappedInstance the instance of MandelbrotView to wrap.
*/
public MandelbrotViewHolder(MandelbrotView wrappedInstance) {
this.wrappedInstance = checkNotNull(wrappedInstance, "The MandelbrotView instance to be wrapped may not be null");
}
@Override
public BigDecimal getRealAxisPixelSize() {
return wrappedInstance.getRealAxisPixelSize();
}
@Override
public BigDecimal getImaginaryAxisPixelSize() {
return wrappedInstance.getImaginaryAxisPixelSize();
}
@Override
public int getRealAxisPixelCount() {
return wrappedInstance.getRealAxisPixelCount();
}
@Override
public int getImaginaryAxisPixelCount() {
return wrappedInstance.getImaginaryAxisPixelCount();
}
@Override
public void setRealAxisPixelCount(int realAxisPixelCount) {
wrappedInstance.setRealAxisPixelCount(realAxisPixelCount);
}
@Override
public void setImaginaryAxisPixelCount(int imaginaryAxisPixelCount) {
wrappedInstance.setImaginaryAxisPixelCount(imaginaryAxisPixelCount);
}
@Override
public BigDecimal getRealAxisMinimum() {
return wrappedInstance.getRealAxisMinimum();
}
@Override
public BigDecimal getImaginaryAxisMinimum() {
return wrappedInstance.getImaginaryAxisMinimum();
}
/**
* Calls the underlying wrapped instance to create a new view on the Mandelbrot set for the sub-rectangle, replacing
* it with the new one.
* @param realAxisPosition The pixel position on the real axis within the current view of the top left corner of the
* rectangle.
* @param imaginaryAxisPosition The pixel position on the imaginary axis within the current view of the top left
* corner of the rectangle.
* @param realAxisPixels The pixel width on the real acis of the rectangle
* @param imaginaryAxisPixels The pixel width on the imaginary axis of the rectangle
* @return this instance of the MandelbrotViewHolder which now wraps the new instance of the view.
*/
@Override
public MandelbrotView selectSubRectangle(int realAxisPosition, int imaginaryAxisPosition,
int realAxisPixels, int imaginaryAxisPixels) {
wrappedInstance = wrappedInstance.selectSubRectangle(
realAxisPosition, imaginaryAxisPosition,
realAxisPixels, imaginaryAxisPixels);
return this;
}
/**
* Calls the underlying wrapped instance to create a new view on the Mandelbrot set for the super-rectangle, replacing
* it with the new one.
* @param realAxisPosition The pixel position on the real axis within the new view of the top left corner of the
* rectangle containing the current view.
* @param imaginaryAxisPosition The pixel position on the imaginary axis within the new view of the top left
* corner of the rectangle containing the current view.
* @param realAxisPixels The pixel width on the real axis of the rectangle
* @param imaginaryAxisPixels The pixel width on the imaginary axis of the rectangle
* @return a new MandelbrotViewImpl representing the super-rectangle of the current view.
*/
@Override
public MandelbrotView selectSuperRectangle(int realAxisPosition, int imaginaryAxisPosition, int realAxisPixels, int imaginaryAxisPixels) {
wrappedInstance = wrappedInstance.selectSuperRectangle(
realAxisPosition, imaginaryAxisPosition,
realAxisPixels, imaginaryAxisPixels);
return this;
}
@Override
public MandelbrotGenerationStrategy getMandelbrotGenerationStrategy() {
return wrappedInstance.getMandelbrotGenerationStrategy();
}
@Override
public int getMaxIter() {
return wrappedInstance.getMaxIter();
}
@Override
public void setMaxIter(int maxIter) {
wrappedInstance.setMaxIter(maxIter);
}
}
Fixed Precision Mathematics
Using his work as a jumping off point I started seeing if I could integrate Fixed Precision calculations into my Mandelbrot Generator. The only problem I had was that I didn't fully understand the code that I was using and wanted to be able to extend it to greater precisions so I decided to write my own implementation from first principles. I did reuse a useful hack that he used replacing all the insignificant digit carry calculations with a '3'.
In the end I created a stripped down library to service my own Fixed Precision Mandelbrot generation.
It was interesting to compare his and my Fixed Precision libraries to see the similarities and differences - I deliberately didn't take some of the optimisation paths (e.g. dedicated squaring logic) - but I did end up with a mixture of similar and different methods. The largest difference was in the Positive / Positive multiplication where I decided to try to take advantage of the vector methods:
#define MAX_UINT4 (uint4)(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)
#define MOST_SIGNIFICANT_BIT_MASK 0x80000000U
uint4 addFP128(uint4 firstAddend, uint4 secondAddend)
{
uint4 naiveAddition = firstAddend + secondAddend;
uint4 propagatableVector = convert_uint4(naiveAddition == MAX_UINT4);
uint4 overflowCarries = (convert_uint4(naiveAddition < firstAddend) & (uint4)(0,1,1,1)).yzwx;
uint4 propagatedCarries = (uint4)(
( (overflowCarries.z & propagatableVector.z) | overflowCarries.y ) & propagatableVector.y,
overflowCarries.z & propagatableVector.z,
0,
0);
return naiveAddition + overflowCarries + propagatedCarries;
}
uint4 doubleFP128(uint4 operand)
{
uint4 propagated = (operand & (uint4)(0,MOST_SIGNIFICANT_BIT_MASK, MOST_SIGNIFICANT_BIT_MASK, MOST_SIGNIFICANT_BIT_MASK)).yzwx >>(uint4)(31U);
return (operand<<(uint4)(1U)) + propagated;
}
uint4 incrementFP128(uint4 operand)
{
uint4 propagatableVector = convert_uint4(operand == MAX_UINT4); // Determine if any of the operand elements are equivalent to 0xFFFFFFFF
uint4 carriedIncrement = (uint4)(
propagatableVector.y & propagatableVector.z & propagatableVector.w & 1, //Carry up to x
propagatableVector.z & propagatableVector.w & 1, //Carry up to y
propagatableVector.w&1, //Carry up to z
1); //increment to be added to w
return operand + carriedIncrement;
}
uint4 negateFP128(uint4 operand)
{
uint4 complement = operand ^ MAX_UINT4;
return incrementFP128(complement);
}
uint4 absoluteFP128(uint4 value) {
return ((value.x & MOST_SIGNIFICANT_BIT_MASK)?negateFP128(value):value);
}
uint4 multiplyFP128PositivePositive(uint4 firstMultiplicand, uint4 secondMultiplicand)
{
uint4 wXYZW = firstMultiplicand * (uint4)(secondMultiplicand.w);
uint4 wXYZWHigh = mul_hi(firstMultiplicand, (uint4)(secondMultiplicand.w));
uint4 zXYZW = firstMultiplicand * (uint4)(secondMultiplicand.z);
uint4 zXYZWHigh = mul_hi(firstMultiplicand, (uint4)(secondMultiplicand.z));
uint4 yXYZW = firstMultiplicand * (uint4)(secondMultiplicand.y);
uint4 yXYZWHigh = mul_hi(firstMultiplicand, (uint4)(secondMultiplicand.y));
uint4 xXYZW = firstMultiplicand * (uint4)(secondMultiplicand.x);
uint4 xXYZWHigh = mul_hi(firstMultiplicand, (uint4)(secondMultiplicand.x));
uint4 resultPart1 = addFP128((uint4)(yXYZWHigh.x, zXYZWHigh.x, wXYZWHigh.x, 3), (uint4)(xXYZWHigh.y, yXYZWHigh.y, zXYZWHigh.y, wXYZWHigh.y));
uint4 resultPart2 = addFP128((uint4)(0, xXYZWHigh.z, yXYZWHigh.z, zXYZWHigh.z), (uint4)(0, 0, xXYZWHigh.w, yXYZWHigh.w));
uint4 resultPart3 = addFP128((uint4)(0, 0, 0, wXYZW.x), (uint4)(0, 0, zXYZW.x, zXYZW.y));
uint4 resultPart4 = addFP128((uint4)(0, yXYZW.x, yXYZW.y, yXYZW.z), (uint4)(xXYZW.x, xXYZW.y, xXYZW.z, xXYZW.w));
return addFP128(addFP128(resultPart1, resultPart2), addFP128(resultPart3, resultPart4));
}
//TODO consider if a dedicated squaring function would be a good idea.
uint4 multiplyFP128AnyAny(uint4 firstMultiplicand, uint4 secondMultiplicand)
{
// Get the absolute product;
uint4 p = multiplyFP128PositivePositive(absoluteFP128(firstMultiplicand), absoluteFP128(secondMultiplicand));
//Return it with the appropriate sign
return ((firstMultiplicand.x & MOST_SIGNIFICANT_BIT_MASK)^(secondMultiplicand.x & MOST_SIGNIFICANT_BIT_MASK))?negateFP128(p):p;
}
__kernel void MandelbrotFixedPrecision128(
const uint4 realDelta,
const uint4 imaginaryDelta,
const uint4 realMinimum,
const uint4 imaginaryMinimum,
const unsigned int maxIter,
const unsigned int escapeNumber,
const unsigned int hRes,
__global int* outputi
)
{
int realId = get_global_id(0);
int imaginaryId = get_global_id(1);
uint4 realPosition = addFP128(realMinimum, multiplyFP128AnyAny(realDelta, realId));
uint4 squaredRealValue = multiplyFP128AnyAny(realPosition, realPosition);
uint4 realValue = realPosition;
uint4 imaginaryPosition = addFP128(imaginaryMinimum, multiplyFP128AnyAny(imaginaryDelta, imaginaryId));
uint4 squaredImaginaryValue = multiplyFP128AnyAny(imaginaryPosition, imaginaryPosition);
uint4 imaginaryValue = imaginaryPosition;
int iter = 0;
while ( (iter < maxIter) && (addFP128(squaredRealValue,squaredImaginaryValue).x < escapeNumber) )
{
imaginaryValue = addFP128(doubleFP128(multiplyFP128AnyAny(realValue, imaginaryValue)), imaginaryPosition);
realValue = addFP128(addFP128(squaredRealValue, negateFP128(squaredImaginaryValue)), realPosition);
squaredRealValue = multiplyFP128AnyAny(realValue, realValue);
squaredImaginaryValue = multiplyFP128AnyAny(imaginaryValue, imaginaryValue);
iter++;
}
if(iter >= maxIter)
iter = maxIter + 1;
outputi[imaginaryId * hRes + realId] = iter;
}
Workgroup size: TwoTuple{firstElement=16, secondElement=16}
10 runs of MandelbrotViewImpl{maxIter=512, realAxisCentre=-0.75, imaginaryAxisCentre=0, realAxisPixelCount=1280, imaginaryAxisPixelCount=1280, realAxisPixelSize=0.00234375, imaginaryAxisPixelSize=0.00234375, mandelbrotGenerationStrategy=FP128} took on average 157.7323556 milliseconds
10 runs of MandelbrotViewImpl{maxIter=512, realAxisCentre=-0.75, imaginaryAxisCentre=0, realAxisPixelCount=1280, imaginaryAxisPixelCount=1280, realAxisPixelSize=0.00234375, imaginaryAxisPixelSize=0.00234375, mandelbrotGenerationStrategy=DOUBLE} took on average 7.4757887 milliseconds
10 runs of MandelbrotViewImpl{maxIter=512, realAxisCentre=-0.75, imaginaryAxisCentre=0, realAxisPixelCount=1280, imaginaryAxisPixelCount=1280, realAxisPixelSize=0.00234375, imaginaryAxisPixelSize=0.00234375, mandelbrotGenerationStrategy=SINGLE} took on average 6.429966899999999 milliseconds
March 09, 2012
Selenium, HtmlUnit, JavaScript and JQuery - Writing Performant Tests
I finally got time a couple of weeks ago to investigate the problem and because I tend to avoid JavaScript like the plague, it took me a little while to garner the necessary skills and utilities to diagnose the problems.
I thought I would share the lessons I learnt in case anyone else comes across similar problems.
The first thing I did was to upgrade all the libraries to their latest versions this often fixes performance bugs and can highlight problems with the code base. In my case it only garnered a minor speed up but highlighted the fact that the old version of Selenium we were using allowed interaction with hidden and disabled HTML elements and a number of our tests were badly written.
The second thing I discovered is that the Selenium / HtmlUnit event model means that SELECT element change events were only fired when focus changed to another element in the page.
Seeing no major speed increase I instead reverted to profiling the Java processes. We don't have access to a commercial profiler but a remarkable amount can be achieved using VisualVM which is included as part of the Sun JDK since about version 6.012.
The first thing you should always do when installing this utility is to increase the memory available using the VisualVM options file in the JDK configuration folders.
I was very careful to limit what I profiled to only a certain subset of the classes involved as even with the tweaked memory settings VisualVM is a little flaky when profiling large, long-running processes.
I used the CPU profiling capability and focused in on the projects test classes, the Selenium classes and the HtmlUnit classes.
After a certain amount of faffing around I discovered that a large amount of time was spent in the JavaScript and particularly around event bubbling and DOM change listening.
Unfortunately there is a certain level of indirection involved in the JavaScript implementation and could not discover precisely which JavaScript elements were involved even when using Java debugging.
I instead reverted to using FireBug and FireQuery to profile and navigate through the JavaScript within Firefox. I quickly discovered some obvious JavaScript performance problems and fixed them. Equally rapidly I discovered that the performance characteristics of the Firefox JavaScript engine is very different to those of the HtmlUnit JavaScript engine - Mozilla Rhino.
FireBug and FireQuery were excellent for helping me to understand what was executing and would be perfect for profiling browser performance issues, I would have to look elsewhere for the root cause of the HtmlUnit performance problems.
I resorted to old-school - comment stuff out until it broke or performed faster.
The problem was rapidly resolved to being the use of JQuery live() methods. They are designed to handle events highly dynamic DOMs by listening to events that have bubbled all the way up to the root and then matching the source of the event to a selector before calling the method intended to handle the event.
I replaced them with JQuery bind() and delegate() methods and saw a massive performance increase: A UI test suite that took 26 minutes to run completed in 10.
I learnt that highly dynamic JQuery based scripting could cause severe test performance problems and learnt a lot about how it worked. Longer term I'm going to move to statically bound JavaScript as much as possible particularly when I need to be able to test it.
A framework like JQuery is a little like an iceberg, it may look like you're barely using any JavaScript but under the surface...
- Posted using BlogPress from my iPad
September 08, 2011
On the Insanity of Expecting Nothing to Happen...
On my current project we're building an asynchronous message based system with various outputs, in particular a file for consumption by a mainframe.
On a number of occasions tests have been written that assert that something has not happened.
When you're testing synchronous processes this is a perfectly reasonable assertion to make, when the process returns all the work will be done.
For asynchronous processes?When the process returns then you have no direct way of knowing if it has completed.
For many people the most obvious way of checking that something has not happened in an asynchronous process is the counterpart for checking that something has happened asynchronously. You put in a timeout.
The problem with this is the likelihood of a given outcome. When expecting something to happen asynchronously you only have to wait until it has happened which means that you only ever wait until the timeout when something has gone wrong. When expecting something not to happen, you end up waiting for the time out most of the time. This means that for every assertion that nothing will happen you end up adding a fixed timeout that causes your test phase to increase in duration.
When you use a time out to wait for something not to happen, you risk setting the timeout too short and the test becoming invalid because the thing you asserted wouldn't happen occurs after the test completes. This can lead to further increases in the timeouts to ensure that the tests remain valid.
There is a related problem with testing asynchronous processes where the test only checks an intervening step in the process before moving on the next test. This leaves you open to side effects from the processes started by the preceding tests causing hard to diagnose problems with your later tests. This is particularly likely with tests that assert that something doesn't happen as they have a greater chance of leaving the test before the process under test has completed.
How do we avoid these problems and still be able to make assertions that something hasn't happened?
The 'Sentinel' pattern is a good place to start.
Send a second request after the first that will produce a result after the first has completed, in this way you can assert that if you only see the result of the second request then you can state that the first request hasn't produced anything. Maybe you can ask the asynchronous process to do two things in one request and can then assert that only the effects of the additional work are visible.
Alternatively you can look at other ways of knowing that the process under test has completed. Check the logging for a statement that is only produced when the process has completed or maybe introduce an event or message that is produced when the process completes regardless of whether the other output is produced.
It is always a bad idea to expect 'nothing' to happen asynchronously, always expect something.
April 04, 2011
Debugging Neo4J Spatial - Update 3 on Neo4J Spatial and British Isles OSM Data import.
March 31, 2011
Update 2 on Neo4J Spatial and British Isles OSM Data import.
March 24, 2011
Update 1 on Neo4J Spatial and British Isles OSM Data import.
package com.presynt.neo4j;
import static org.junit.Assert.*;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import org.geotools.referencing.CRS;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.neo4j.gis.spatial.Layer;
import org.neo4j.gis.spatial.SpatialDatabaseService;
import org.neo4j.gis.spatial.SpatialTopologyUtils;
import org.neo4j.gis.spatial.osm.OSMImporter;
import org.neo4j.gis.spatial.osm.OSMLayer;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.kernel.EmbeddedGraphDatabase;
import org.neo4j.kernel.impl.batchinsert.BatchInserterImpl;
import javax.xml.stream.XMLStreamException;
import java.io.IOException;
public class TestNeo4JSpatial {
private static GraphDatabaseService graphDB;
private static SpatialDatabaseService spatialDB;
@Test
@Ignore
public void createSpatialDB() throws XMLStreamException, IOException {
OSMImporter importer = new OSMImporter("OSM-BUCKS");
BatchInserterImpl inserter = new BatchInserterImpl("/testDB");
importer.importFile(inserter ,"/OSMData/buckinghamshire.osm");//"british_isles.osm"); // "C:\\british_isles.osm");
inserter.getGraphDbService().shutdown();
graphDB = new EmbeddedGraphDatabase("/testDB");
importer.reIndex(graphDB, 100000);
graphDB.shutdown();
}
@Test
public void retrieveLayer(){
final Layer layer =
spatialDB.getLayer("OSM-BUCKS");
assertNotNull(layer);
assertEquals(OSMLayer.class, layer.getClass());
}
@Test
public void useLayer() {
final OSMLayer osmLayer = (OSMLayer)spatialDB.getLayer("OSM-BUCKS");
final GeometryFactory factory = osmLayer.getGeometryFactory();
System.out.println("Unit of measure: " + CRS.getEllipsoid(osmLayer.getCoordinateReferenceSystem()).getAxisUnit().toString());
final Point point = factory.createPoint(new Coordinate(51.808721,-0.689735));
// final Layer boundaryLayer = osmLayer.addSimpleDynamicLayer("boundary", "administrative");
// SearchContain searchContain = new SearchContain(point);
// boundaryLayer.getIndex().executeSearch(searchContain);
// for(SpatialDatabaseRecord record: searchContain.getResults()){
// System.out.println("Container:" + record);
// }
final Layer highwayLayer = osmLayer.addSimpleDynamicLayer("highway", null);
long startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 10.0);
System.out.println("10m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 10.0);
System.out.println("10m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 100.0);
System.out.println("100m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 100.0);
System.out.println("100m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 1000.0);
System.out.println("1000m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 1000.0);
System.out.println("1000m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 10000.0);
System.out.println("10000m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 10000.0);
System.out.println("10000m took:" + ((System.currentTimeMillis() - startTime) / 1000d) +"s");
startTime = System.currentTimeMillis();
// for(SpatialTopologyUtils.PointResult result : SpatialTopologyUtils.findClosestEdges(point, highwayLayer, 10000.0)){
// System.out.println(result);
// }
}
@BeforeClass
public static void initialiseDatabase() {
graphDB = new EmbeddedGraphDatabase("target/test-classes/spatialTestDB");
spatialDB = new SpatialDatabaseService(graphDB);
}
@AfterClass
public static void shutdownDatabase() {
graphDB.shutdown();
spatialDB = null;
graphDB = null;
}
}
March 21, 2011
Neo4J Spatial and Importing the British Isles Open Street Map Data
- org.neo4j:neo4j:1.3.M04
- org.neo4j:neo4j-spatial:0.5-SNAPSHOT
- org.geotools:gt-referencing:2.6.5
- org.geotools:gt-main:2.6.5
- org.geotools:gt-cql:2.6.5
- org.geotools:gt-epsg-hsql:2.6.5
- com.vividsolutions:jts:1.11
October 12, 2010
My Web Comics...
- Butternutquash - Telling the tales of a group of friends that hang around a comic strip. It is far more cynical than you would expect but always funny. I love the Narcoleptic Dog.
- Ctrl+Alt+Del - A delightfully odd ball comic strip following the personal and imaginary life of a dedicated gamer.
- Diesel Sweeties - The life and loves of an assorted group of people, including a fading porn actress her sometime boyfriend - a robot, a chronic hipster, a Canadian, a very disturbing furry and the list goes on. This comic strip has resulted in some of my favourite T-Shirts.
- Girl Genius - A gloriously drawn story of an orphaned. young woman who discovers that she has inherited the 'Spark' from her parents; the ability to build amazing steam-punk devices. This is a complex and involving tale set in a richly realised world.
- Gunnerkrigg Court - I only discovered this recently - A comic that becomes more beautiful as the artist develops his skill. telling the story of a conflict between science and magic through the eyes of a young girl thrown into the middle.
- Kukuburi - By the artist of Butternutsquash. Again a fabulously beautiful comic strip set in a seemingly dream world. Now sadly on hiatus but well worth reading.
- Orneryboy - A richly humorous gothic story about a couple who live with Brian the Zombie and three cats, one of whom is a ghost.
- Penny Arcade - A pure geek gamers comic strip but highly entertaining if you get the jokes. I'm a particular fan of Catsby and Twisp who crop up from time to time.
- PvP - The Godfather of web comics. Many web cartoonists cite Scott Kurtz as their influence both artistically and professionally. He is credited with pioneering making web cartooning a viable career. The comic itself follows the adventures of a bunch of journalists working at a games magazine.
- Scary-Go-Round - A very British comic strip and all the better for it. Over the years it has mutated but always centres around ordinary people living very un-ordinary lives.
- Sluggy Freelance - For a comic strip that started as a little story about a group of house mates this has developed into one of the richest and most perpetually rewarding comic strips that I read. Dimension hopping, big guns and humour make this a firm favourite. One character in particular should always be mentioned: Bun-Bun a psychotic dwarf lop rabbit with a flick-knife.
- The Abominable Charles Christopher - Tough to describe as it follows the perambulations of a perpetually innocent hairy beast that is largely man-shaped. He says nothing but still converses freely with the animals of the forest. Just before the summer break he has found himself face to face with a human town. I love it for the art and the crazy animal characters.
- User Friendly - One of the oldest web comics. I was introduced to this many years ago by my friend Neil. Gloriously anarchic tales told of an Internet Service Provider's support team with occasional visits by characters of the Cthulhu pantheon.
September 28, 2010
Further Forays Into the HTML5 Stack - Animation
September 20, 2010
Forays into the HTML5 stack.
- It is significantly more mature as a specification than the HTML 5 Canvas element.
- The HTML 5 canvas element is procedural in its rendering so with a command it renders pixels directly to screen and so does not have a screen graph; the SVG xml elements are the scene graph and are embedded directly in the DOM and so are manipulable using all the tricks that we are familiar with. This also means that a lot of the heavy lifting for re-drawing is done for us.
- Text is more of a first class citizen in SVG and so is easier to make available to Accessible browsers.
- SVG graphics elements are style-able using CSS.
- HTML can also be embedded back into SVG (and manipulated via the DOM) using the foreignObject element
- The default SVG animation framework is built around the SMIL standard which is pretty low level and is not always the easiest to work with though it is very powerful. I'm intending to see how well SVG interacts with the CSS3 animation effects which are much nicer to work with.
- SVG support is present in the latest iteration of all the browsers but is patchy to say the least. The best of the moment is the WebKit family, Firefox is the next best though it has big issues with the text rendering. IE9 claims to be a huge step forwards over IE8 but I have yet to try out the beta and there are question marks over how well it will support SVG animation.
- While its text support is good there is one glaring omission - word wrap. The neatest work-around is to embed an appropriate HTML element in the SVG using foreignObject.
September 15, 2010
My life with BDD...
- Given: The remainder of the sentence defines pre-conditions for the next steps of the scenario.
- When: Defines activity that the system under test is carrying out as part of the test.
- Then: The assertions that the test should be checking.
September 12, 2010
Some New Social Networking Infrastructure
August 29, 2010
Lyrical Ambiguity in the Kinks' Lola
One thing that always makes me stop and wonder is how many people believe firmly that they know the gender of Lola.
To me it seems that they have missed the brilliance of the lyrical ambiguity. The lines that really seem to be misunderstood are: "But I know what I am and I'm glad I'm a man and so is Lola.". Most people I talk to who have yet to get it believe firmly that the lines indicate that Lola is a man.
This interpretation arises from the belief that "so is Lola" refers to the fragment "I'm a man". This is a perfectly valid interpretation of the lyric but ignores another equally valid interpretation.
There is another fragment that could be referred to by "so is Lola"; "I'm glad I'm a man". If this is the reference then the interpretation would be that Lola is also glad that the protagonist is a man thus leaving Lola's gender entirely unstated.
In many ways this is the aural equivalent of the face/vase illusion where you can see either the faces or the vase but not both.
Whatever else it is, it is a brilliant song with brilliant lyrics.
- Posted using BlogPress from my iPad
July 05, 2010
iPhone 4 has arrived...
Faced a rather unique problem... I got two iPhones in my package rather than the one I expected. The nice young customer service rep nearly fell off her seat when I called and told her about it. The first time they had heard of that happening.
I think that I slightly restored her faith in humanity as she thought that no one would have called up and would have just stayed quiet about it. She asked me to drop off the extra to a store and gave me a nice in-store credit.
I'm now on my way to London waiting for the new SIM to activate and just using the iPhone as an iPod. I'm hoping that the rest of the experience is as painless as the iPad was.
- Posted using BlogPress from my iPad
June 30, 2010
My First Track day...

On Monday I took my life into my hands and went on a track day at Bedford Autodrome with EasyTrack.co.uk.
I had a blast! The track was ideal for a novice and my car (an Audi TT RS) proved more than capable. I had asked for a session with an instructor and I got a great guy named Steve. In the end I took a second session in the afternoon. As a result I went from being slowest on the track to not being the slowest by a decent margin. There were some fabulous drivers on the track that day, the two sticking in my mind included one in a GT3 RS and an utter genius in a bog standard MX5.
I've included a photo that was taken by TrackPhoto.co.uk.
- Posted using BlogPress from my iPadJune 27, 2010
The Height of Geekery
Well, here I am at my favourite cafe for breakfast with my iPad. I'm trying the whole blogging from my iPad thing with an app called BlogPress.
It's really interesting to see how modern integrated technology is making it feasible to do things that would have been awkward if not impossible a few years ago. The opportunities in this space are endless.
The excitement about the potential for location based services is well known. Unfortunately the excitement can obscure the real win. Location-less Services. The fact that I can start doing this kind of thing truly independently of my location is what's exciting. There have been some early mis-fires with the excitement of being able to take a laptop with you on holiday. Lugging a laptop though is really pretty hard work. We're on the cusp of being able to access and interact with the "datasphere" anywhere in the world.
This is where reading science fiction is a great advantage. Writers have been exploring how to use this for years. I feel that I am ready to make use of this and build on their thinking to go places that people who have not been reading will never think of.
How about you?
- Posted using BlogPress from my iPad
Location:Cliffe High St,Lewes,United Kingdom
June 17, 2010
Constructor Versus Setter Based Dependency Injection
There seems to be two extreme camps here and no middle ground.
I, as usual, agree with neither extreme.
The simple fact is that my preferred approach is to design classes that meet the original intent of object orientation. That the class is fully usable after construction.
That is not to say that I believe that Constructor injection is the one, true way. Rather, I believe that all mandatory dependencies should be configured in the constructor and optional ones should be satisfied by setter methods. This makes the class much easier to understand. It also means that the constructor does not end up completely overloaded with parameters but neither are you left looking at a a sea of setter methods wondering which ones have to be used.
By using this convention the code becomes more self-documenting.
March 19, 2010
The problem with modern 3D films
February 10, 2010
What do I think about Business Analysis?
- Working with the business to understand the business domain.
- Capturing and documenting business requirements.
- Analyse the business domain to highlight areas of inconsistency and identify areas of uncertainty.
- Distill and communicate the business requirements to the implementation team.
