Thursday, 3 May 2018

Is low level programming still relevant these days?

The levels of abstraction have made the application programming much easier and faster. But everything comes at a price.

This is a new type of article here, I hope I will have some more such articles describing programming problems and the practices to solve the former.


Contents

Top


Unlimited Resources Myth


The overhead of various frameworks and libraries sometimes (or should I say always) makes memory usage and CPU utilization less efficient. Fast hardware and loads of RAM lead to sloppy coding practices too, luring the developers into the mirage of endless resources.

Sure thing, most developers would place a check now and than (e.g. making sure malloc does not return NULL, although most of the time this will not happen). But what happens when memory is fragmented? Large data structures will make fragmentation even worse and allocating more memory will become slower and slower.

And yes, eventually the available RAM will shrink.

Top


Basic Data Structure


Consider this data structure:

typedef struct{
  int id; //MAX id < 10000
  int array1[10000]; //MAX element value <= 4
  int array2[10000]; //MAX element value <= 4
} my_struct_type;

struct my_struct_type my_array[10000];

On a 64bit machine the structure will take 8 (id) + 8 x 10000 x 2 (array1,2) = 160008 bytes. Creating an array of 10000 such structures will occupy about 1.6GB. This is a not-so-high level language. Just imagine what size the equivalent structure will be in Java or C#. 
 
Top


Improved Data Structure


Let's take a closer look at the struct. It is noticeable that the arrays operate on very small values, so it would be natural to change the structure to use smallest possible datatype for arrays elements:


typedef struct{
  int id; //MAX id <= 10000
  uint8_t array1[10000]; //MAX element value <= 4
  uint8_t array2[10000]; //MAX element value <= 4
} my_struct_type;

Now the size of the structure is 8 (id) + 1 x 10000 x 2 (array1,2) = 20008 bytes. And the total size is 200MB. That is much more manageable.

But wait! What will happen if we need more structures  -e.g. 100K? This will lead to 2GB - oops, too much again (to be honest, 200MB is too much anyway).

Most developers (I hope) know that a byte consists of 8 bits. From the data structure comments we can assume that an array element will not occupy more than 2 bytes. So why don't we use bitfields instead of full uint8_t? Well, it's better not to - bitfields are compiler dependent and are not that efficient (more on that later).

Theoretically we can reduce the data struct footprint to a quarter size. And it is possible using bitmasks and bitwise operators. Lets do it step by step.


Top


Optimized Data Structure


Start with the notion the arrays1,2 are of the same size. So we can put element of array2 into unused part of array1:

typedef struct{
  int id; //MAX id <= 10000
  uint8_t array12[10000]; //two arrays combined
} my_struct_type;

So now the array element in its binary form will look like array12[0] = 0b0000BBAA, where AA is the array1 element  and BB is array2 element. So the size is halved. But there is still enough room to insert more data. Why don't we store one more of each elements of array1 and array2 in the same element of array 12, like this: array12[0] = 0bB1A1B0A0.

this way our struct becomes

typedef struct{
  int id; //MAX id <= 10000
  uint8_t array12[5000]; //four values in one element
} my_struct_type;

Now the memory footprint will be roughly 50MB. And this is not the only benefit of the current data structure. But before discussing it lets consider the possible overhead of bit manipulation.

Data Packing Algorithm


To store the value val in the array's element el we need to perform the following operations:

1. Put value in correct binary position:  

val = val << val_pos;

2. Depending on the index value, shift the value to the left half:

val = val << (index % 2 ? 4 : 0);

Modulus can be replaced with binary index & 1  and the value can be shifted left 2 bits to get rid of conditional jump:  

val = val << ((index & 1 ) << 2);

3. Insert the value into the element:

el |= val;

There are 5 bitwise operators. Bitwise operators are the most simple and "inexpencive" ones for the CPU. This will take about the same time (or only slightly more) than storing (copying) a value into array.

Faster Memory Allocation


As I mentioned earlier the reduced memory footprint is not the only good thing about the new data structure. For the computer's memory management engine it is much easier to allocate lots of small memory chunks than lots of large memory chunks. By reducing the structures memory size we increase the processing seed (possible compensating for additional CPU cycles taken by bitwise operators).

Sporadic nature of new data insertion


Another thing worth noting that addition of new elements does not happen all at once, but occurs more or less sporadically. The new element creation will be unnoticeable from CPU point of view overshadowed by more CPU intense operations. As for the occupied memory, there is not way around it and at some stage the program will have to struggle with subsequent memory allocation due to RAM fragmentation (if using dynamic memory allocation of course).

Top


Example program


A working example can be found here: https://github.com/droukin-jobs/packer - a proof of concept that bitwise data placement is not much slower than traditional approach and much more memory efficient.

Top


Conclusion


While having "unlimited" memory and CPU resources it is still important to keep track of your resource usage. How many times you had to sit and wait until some program loads a new screen or processes a new request (a good example here is SolidWorks - the behemoth is so slow even on the fastest systems despite being a well known and respected brand). When proramming it is good to create robust and easy to understand code, but after all testing is done, why not optimize some obvious parts - if you know how?
Top

Tuesday, 1 May 2018

There is no master in modern team

 Of course this is sarcasm, don't take it seriously!

Economics of efficiency

Meet the team

How many Software engineers do you need to screw in a light bulb? One? You are wrong! Let's calculate risks and how we can mitigate those.

What if the Engineer instead of screwing in will start unscrewing the light bulb? Someone will need to ensure that. An extra pair of eyes will definitely help to catch the wrong direction of turning at the early stage, thus saving a lot of time in the long run. This is called Peer review.

But we need to test the light bulb - what if it 's not working? Let's get the Engineers perform the set of tests on the light bulb. Since they are busy testing, we will have to hire two more Engineers to keep up with the work.

What if the lamp is unscrewable, e.g. it has different size or shape of connector? We need someone to tell Engineer what kind of light bulb to use - the Architect will help with that.

What if the light bulb got changed in the wrong room? Or the customer wants a different light bulb? Product manager will help with this.

How about the history of successful bulb replacements - that should definitely help with future installations! Let's hire an Agile coach to teach the team to reflect on their mistakes and re-use successful solutions.



So far we have 4 engineers + System Architect + Product Manager + Agile Coach = 7 people. We now have a TEAM !


Quality calculations

After the project is done we can safely assume the quality of the final solution is going to be very high. But still let's calculate the approximate quality and time spent on an average light bulb project.

Suppose the average Engineer's time to screw in the light bulb is 1 hour and the quality rate is about 70% - i.e. about 70% of work is acceptable. Peer review (in theory) will increase this to 100% - 30% x 70 % = 80%. The time spent will increase by 1 hour.

Meeting with Architect to discuss what kind of screwing method to use will take 0.5 hours of time. If Engineers are less competent with this method, this will reduce their accuracy say by 10%.

Lets see what the customer want, consult with Product manager for 0.5 hours.

And don't forget to listen what other team members are doing, and also reflect on previous work in a short standup meeting for 0.25 hours.

The overall quality is now 80% (70% if different method of screwing is used), and the time spent is 3.25 hours.


Imagine the unthinkable


What if in the beginning we hire an Engineer with 80% quality rating? And get him to do a bit of product management, allowing him to decide on screwing methods?

It's still takes 1 hour to screw in the light bulb. Testing might take 0.25 hours, and in 1 time out of 5 will result in re-fixing the light bulb - so add 0.25 hours. Customer interaction will take 1 hour.

So the quality is still 80%, the time spent 2.5 hours and - quelle horreur! - it's all done by one person!

And if we don't pay this person equivalent of at least 3 people from the team above, he will eventually quit. And the project will halt. That's why it is so important to have a team!


New technologies vs Old logic

The levels of abstraction

A master often invents some kind of system to minimize time spent on trivial tasks. Other people may employ this system to their benefit. Some even improve it. And some - a majority - just blindly copy it without understanding the root causes and original problems the system supposed to solve.

Then there are people who wrap the system nicely and start to sell it to others. In order to increase the value these people add some beautiful decorations and supply shiny manuals on how to use the system.

And eventually some other people start to train others on how to sell the system.

At the end the reason the system was created has long been forgotten, the final solution is bloated and complex and no one quite understands why it should be used - apart from the obvious "it saves time".

What is missing?

Not what, but who - the master is left out of the whole thing. Remember, it was he who invented the system to solve _his_ problems. Your problems may or may not be the same.

So who is missing then?

A person with old fashioned logic. What is logic? What, you did not study this? And I am not talking boolean logic - this is only good for binary devices. Humans are not binary, even the simplest ones. But most humans are not complex either. They make fast-fetched conclusions based on inadequate information and this leads them to mistakes.

A person with old sense logic normally tries to understand the deep layers of the problem. Seeing wider picture (i.e. having broad experience well outside the main skill set) helps enormously during decision making. And all this provides more confidence and less room for error.

So what?

A thorough logical thinking allows to determine the snake oil salesmen early. With enough experience sometimes a few seconds is enough to recognize hype and stop wasting time trying to employ it. Remembering there is nothing new under the Sun will help critically assess all the "new" technologies. Some of these are indeed new and useful, but the majority are just a variety of haphazardly sewn pieces of useless dirty laundry.

It is easy for an average person to be restricted by the set of rules - and it definitely helps a lot during the 'infancy' in the profession. But then experience must take over saying that different rules are for different contexts. Even within each context there are times when it is much easier to bend the rule in order to achieve the result. But only master can do it. And only master can then create a system that will take care of trivial tasks.



Modern problem

There are not many masters around. More over the Software engineering philosophy is based on anti-master principles: there is no I in the TEAM, we follow the rules, make sure others understand your code. This is good for a junior developer, but this will not help him to grow into master, unless he will logically comprehend various levels of abstraction and why those were created in the first place.

Old solution

It's hard to break the circle of never ending "improvement techniques" and "efficiency programs". Most people lack common sense and prefer to use ready made solutions. It works for some, but the overhead is usually hard to calculate and it is not necessarily the best solution. And definitely not the simplest one. However it would be unwise to do nothing about it. I am not talking of destroying the whole software development model, but rethinking the approach to implementing some parts of it. And for every one the final solution will be different - the master should decide what to leave in place what to discard.

Parkinson (The Parkinson's Laws) mentioned that work expands so as to fill the time available for its completion. Similar with the team size - it does not really matter how many members in your team, it will take approximately the same time for small team and for big team to finish the same task. To choose the right time and right people for work again requires master.

The solution is simple. There is only one problem - where to find the master?

Is low level programming still relevant these days?

The levels of abstraction have made the application programming much easier and faster. But everything comes at a price. This is a new ...