Refactoring code into fewer lines does not necessarily reduce its complexity

May 20, 2009

Developers are almost universally aware that they should try and write code that is simple to read and maintain.  Complicated code can be a stumbling block for another developer (or even yourself at a later date) when he/she needs to fix bugs or modify functionality.

So refactoring existing code in an effort to reduce its complexity is definitely a good thing.

But refactoring code into fewer lines does not necessarily mean that its complexity has been reduced.  In fact, the opposite can occur.

Take this example posted by an Intel blogger.  While I fully agree that lambdas are awesome, and I eagerly await them in C++0x, I don’t agree with his assertion that the lambda version of his example is simpler.  Regarding his first non-lamda version, he concludes, “The code is indeed complex. It contains too many lines and three methods.”

Certainly the first example has more lines than the lambda equivalent.  But on its own I wouldn’t call the first example complex at all.  It has a function event handler for the control, a function for the thread, and a function for the UI callback.

The lambda version just puts the thread function and callback function into anonymous functions inside the control’s event handler.

Syntax-wise, I’d argue the lambda version is more complex than the first.


STL Erase Findings

November 27, 2008

What’s the best way to erase one or more elements from an STL container?  By ‘best’ let’s go with performance as the first priority, followed by code reuse and C++ standard-compliance/portability.

Also, like all good developers we’ve profiled our code and have determined that yes, it’s worthwhile spending time trying to optimise this erasure.

So I did some tests and am happy to report that the STL algorithms kick ass.

If there’s only 1 element to be erased, then the best (as defined above) method is erase-find:

vector<int> v;
v.erase( find(v.begin(), v.end(), 5) );

(Or substitute another find algorithm, such as find_if.)

If there is 1 or more elements to be erased then go with erase-remove:

vector<int> v;
v.erase( remove(v.begin(), v.end(), 5) );

(Again, other remove algorithms can be used.)

The STL algorithms should be preferred over a custom loop and condition, as the following graph illustrates:

(Click to view at 100%.)

Note that the graph shouldn’t be used to compare the performance between containers or elements.  Rather, the graph only illustrates the performance of erase methods for a variety of containers.

Erase-remove performs about the same, and in some cases better, than a custom loop and condition (even when it breaks out of the loop once the element is found), which is quite impressive really.  But for a single element nothing can beat erase-find.


D: Modules and Class Private Members

November 25, 2008

One of D’s major differences from C++ is to do with classes and their private members.

In D, any code in the same module as a class can access that class’ private members.  An example is probably useful:

    module example.Dogsarama;
    
    class Dog
    {
    public:
    
        this(int age)
        {
            m_age = age;
        }
    
        int Age()
        {
            return m_age;
        }
    
        void SetAge(int age)
        {
            m_age = age;
        }
    
    private:
    
        int m_age;
    }
    
    void SetDogAge(Dog d, int age)
    {
        // I can access this dog's privates!!!
        d.m_age = age;
    }

Because SetDogAge() is part of the same module as the Dog class, it is entitled to access and manipulate Dog’s private members.

So in another module we can do this:

    module example.Main;
    
    import std.stdio;
    import example.Dogsarama;
    
    int main(char[][] args)
    {
    	auto fido = new Dog(15);
    
    	SetDogAge(fido, 17);
    
    	writeln(fido.age());
    
    	return 0;
    }

Though I had read the main parts of the D spec I only discovered this by experimenting.  I later found the appropriate documentation which is unfortunately a bit obscure, IMO.


I got a D for programming

October 24, 2008

D

Occasionally when I get a free moment I’ve been checking out D, a programming language from Digital Mars, and decided it was time to write down my thoughts.

D is syntactically in the C family (C, C++, C#, Java) but isn’t an evolution from one of those languages.  Instead, it’s more like C++ redesigned with modern features from Java and C# (and C++0x).

From a C++ perspective, some of D’s features that interest me are garbage collection, modules, anonymous & nested functions, contract programming, and auto type inference.

D compiles to machine code so doesn’t have the overhead of a runtime framework or virtual machine.  The language keeps a lot of C++ features, including multi-paradigm programming, though isn’t fully backwards compatible with C/C++ code.  Although a D compiler can’t compile C/C++ code, a D program can call C and C++ functions, though with restrictions for C++.

So far I’ve been quite impressed with D.  It’s still a very young language, though, and doesn’t have a lot of commercial support or backing.  Only Digital Mars and GNU have compilers for it.  It seems quite popular in open source circles, but I think D really needs an IBM/Microsoft/Borland/Apple to get behind it.

I shall report back sometime in the future after I’ve had a chance to play more with it.


VC++ Headers

October 16, 2008

It annoys me that changing/adding a comment in a C++ header file means that the next build has to recompile all source files that include that header, even though no code has changed.

This can have a massive impact on build times, especially when you use a documentation generator (ala Doxygen) and have to keep the comments up-to-date in the class header files.

It can’t be that hard for VS to track edits to real code as opposed to comments, can it?


The Return of Software Rendering

September 17, 2008

Courtesy of Slashdot, the following is an interesting interview with Tim Sweeney (game software developer, co-founder of Epic Games, who produced the Unreal engines) where he discusses the decline of the dedicated, fixed-function GPU and the future of software-based rendering:

Twilight of the GPU

Excerpt:

I expect that in the next generation we’ll write 100 percent of our rendering code in a real programming language—not DirectX, not OpenGL, but a language like C++ or CUDA. A real programming language unconstrained by weird API restrictions. Whether that runs on NVIDIA hardware, Intel hardware or ATI hardware is really an independent question. You could potentially run it on any hardware that’s capable of running general-purpose code efficiently.


VC++ CRT Versions

September 8, 2008

The following table lists the versions of the Visual C++ 9 CRT DLLs.  This is particularly useful to know when you want to manually deploy the CRT DLLs, and hence need to ensure your application’s EXE and DLL manifests all use the same version of the CRT.

Description CRT Version
VC++ 9.0 (default Visual Studio 2008 installation) 9.0.21022.8
VC++ 9.0 with Feature Pack 9.0.30411.0
VC++ 9.0 with SP1 9.0.30729.1

Lack of Aussie C++ communities

June 23, 2008

There is a disappointing lack of C++ conferences, user groups, meet-ups and other such events in Australia.

Microsoft Australia’s Tech.Ed is one example where the developer sessions are virtually 100% focused on managed code and framework development.  I can appreciate the reasons for this, with .NET/Java accounting for most business and enterprise application development.

Nonetheless, native C/C++ skills remain critical for games, multimedia, drivers, embedded systems, and other applications (desktop or web) that demand the utmost performance and efficiency.

What to do?  Meditate on this, I will.


Scott Meyers on C++0x

June 20, 2008

Check out the podcast video from InformIT, which is Part 1 of an interview with Scott Meyers.  Not sure when they’ll be posting Part 2.


Fixed array profiling

June 17, 2008

C++ has a number of solutions for collections/arrays, including arrays inherited from C, STL collections such as vector, and non-standard yet widely adopted solutions such as Boost’s scoped_array and shared_array.

STL’s vector is the preferred option for collections that need to dynamically resize. But what about fixed size arrays?

If you only need a fixed size array of, say, integers, and performance is critical then which solution is best?

This was the question I was faced with earlier, so I did some profiling tests. The following graph illustrates the results:

Graph

Tests were performed with MSVC 9 (Visual Studio 2008, with VC Feature Pack) and Boost version 1.34.1 on an Intel Q6600 CPU with Windows Vista SP1. See below for test details.

Interpretation of Results

Boost’s scoped_array and shared_array perform just as fast as a C-array solution. The tiny variance in results is likely to be memory/operating system factors rather than the solution itself.

Boost’s solutions should be preferred over C arrays since they minimise potential memory leaks.

As expected, vector suffered from it’s power; it’s designed for a wide variety of use cases, including the ability to dynamically resize itself, and in this scenario that extra power added some overhead. Note, however, that the vector test case did not do any dynamic resizing; the vector was manually resized/reserved to the required size before populating it with values.

Test Details

The following tests were performed on a C array, scoped_array, shared_array and vector:

  1. Create 3 arrays of integers (named source1, source2, dest) with a size of 720 x 576 x 4. This size represents a typical RGBA video image.
  2. Populate source1 and source2 arrays with random values.
  3. Perform the following tests:
    for (size_t i = 0; i < arraySize; ++i)
    {
        dest[i] = source1[i] + source2[i];
    }

and:

    for (size_t i = 0; i < arraySize; ++i)
    {
        int rndSource1 = rand() % arraySize;
        int rndSource2 = rand() % arraySize;

        dest[i] = source1[rndSource1] + source2[rndSource2];
    }

Then finally, if necessary, destroy the arrays.

As can be observed, the test cases are quite basic and focus on accessing the array elements. The first loop accesses the elements in sequential order (which will take advantage of CPU cache lines), whereas the second loop accesses the elements somewhat randomly.

These test cases fulfilled the requirements that I wanted to profile, but performance results may differ with other test cases.

The test cases were repeated 10 times for each array solution, with timing done around the entire operation by using the QueryPerformanceFrequency and QueryPerformanceCounter Windows APIs.

The order of the array tests was changed as well, in case subsequent arrays benefited from previous execution.

First run:

  1. scoped_array
  2. shared_array
  3. C array
  4. vector

Second run:

  1. vector
  2. C array
  3. shared_array
  4. scoped_array

Third run:

  1. vector
  2. scoped_array
  3. C array
  4. shared_array