Saturday, December 24, 2016

Ten programming hints. For Christmas (part3/3)





My last three programming hints for today. For this year.

8. Multi-Threading, be Multi-Careful


As mentioned earlier, only enter this domain if you know what you’re doing, and don’t enter it if you don’t need to. If you want to learn a bit more about Multi-Threading, read this, and a lot of other practical strategies, because you'll need them.            



I consider MT as a necessary evil. In Tower22, that uses a roaming world (thus not level1,2,3, but just one big world, streamed around the player), it would be unacceptable if load-operations would stall the game all the time. So they are performed in background threads mostly. But boy, they can produce ugly code. Be extremely careful when variables are shared between two or more threads, because they can give you serious killer-bugs. For instance, I should not try to draw parts of the world that are still somewhere halfway in their loading (or unloading) process.

Instead, try to think in terms of tasks. Determine what kind of tasks lend themselves to be worked out isolated, in parallel. A Server makes a nice example. Maybe we have 1 client, maybe 10. Or maybe 10-thousand. A client would typically ask stuff. What is my position? Can you insert this new password? Please return me www.brazilianfartporn.com/index.html (don't click), and so on. Now does your town-hall have a single ticket-window, queuing up all those people? Or do you open a new window for every person that walks in? In reality, option B is not feasibly unfortunately, but in a Virtual World we can.

A single ticket-window sucks. Everybody is biting his lips when the little Korean woman keeps saying yes-yes-yes, but doesn’t understand the airport lady telling she can’t bring in boiled dog embryos. The drunk Brittish chap has enough of it, and starts throwing excrement. And you realize you left your ID in the fucking car, so, back-in-line. Same with computer stuff. We don’t want clients waiting on each other, especially not if they are making havoc, or are timing-out. It makes perfect sense to treat each client-connection as a separate thread, a separate pipe. And if it breaks, the others stay intact.
I wasn't allowed to use the Puppy photo for MultiThreading, so; a bunch of fine gentlemen that understood parallel working. Except the lady in the foreground.
 


Network clients lend themselves for multithreading because A: we have to, and B: tasks have little in common – usually. I don’t care what my neighbour does on the internet. His login, logout, his website queries or whatsoever, can be done completely isolated from my connection. Unless, we’re playing a game together. Now some server has to share data. If the neighbour and I are playing online Chess, it would be pretty useful to see his latest move for instance. 

Well kids, the key to successful MultiThreading is too complicated to trash within the next few lines, but at least some here hints. And other than that, read & learn from books or the internet.

·         Use semaphores or mutexes whenever shared variables need to be read or written
o   Or use “Atomic” datatypes / instructions – with care.
o   Bools are often atomic types, so they can be used as flags (“flag_IsReading”, “flag_Done”, …)
·         And better, minimize the amount of shared variables
o   And vice-versa, minimize the amount of calls that can change states / (global) variables.
o   That also counts for single-threaded programs!
o   So if data gets screwed up, you know where it comes from.
·         Threadsafe LockedLists are useful
o   Use them to put tasks in a queue. Typically the main thread would feed this list.
o   Another thread could lock the list, pick a task from it, unlock the list, and perform that task.
o   And eventually report its results back via another locked list.
·         Extra danger when working with variables that might be destroyed in the meanwhile
o   For example, my A.I. bot scans the world, but at the same time the world is unloaded as the player leaves it.
o   Make sure you don’t clean up garbage if threads are still potentially working on it.
·         It can be useful to make a copy of your variables to work with in a background thread, and then synchronize back when the thread is done with them
·         If your code starts stinking because of confusing flags, patch on top of patch, and tricks to workaround… Better redo it. Trust me.

If your MultiThreading implementation seems to be overly confusing, chances are you didn’t design it very well. Like performance, you can’t just rip out a random chunk of code, and move it to another thread. It needs to be in the design at forehand. And as other hints keep saying here, keep it easy. If the reasons & hows behind your MultiThreaded tasks are simple to explain, you probably have a better time implementing them as well. Read about it.

9. Don’t blame your computer. Even if the bastard did it


Yes, like anybody else I have fail-days and moments that I wonder why I just didn’t become a garbage man, instead of debugging impossible bugs. The 19 inch monitor sure suffered some domestic violence when I was still a teenager.

But being a few million bugs further, I learned that I had to blame myself in 99,9% of the cases. Rather than smashing your machine, calling C++ an asshole, cursing Bill Gates and the writers of libraryX, telling it’s all the fault of those retarded n00b end-users, and swearing that the bug just cannot be true, calm down. Cup of coffee, cigarette, calm down. And learn how to fix bugs, or even better, how to avoid them.


Just like you can’t avoid stinky flies in your house, you can’t avoid software-bugs completely either. But at least you can reduce them by not throwing poop or leaving rotting meat on the kitchen counter:
·         As mentioned with Multi-Threading or making unnecessary libraries back in topic #7, avoid advanced techniques if you don’t need them.
·         ALWAYS work neat. Do not litter your code with hacks and “quickly try this for fun”
o   And if you do override with a TEST, make it very clear so you can’t forget to remove it later on
·         Do not write the same variable or state via a dozen different callers / functions / directions.
·         If you have memory profilers or leak-detectors, use them once in a while.
·         Do not keep ignoring the same bugs for too long while you keep on adding other code
·         Try Catch & useful error messages/reports/dumps.
o   Do not assume user-input or your file-loader will always work, because some douchebag eventually will feed your program a rotten carcas.
·         Same thing for anything communication related. Expect noise, expect disconnects, expect chaos.
·         Robust code. Proper naming, small to-the-point functions and using “field-tested” methods.
o   As handy and flexible as it may seem at first, don’t write 100 different ways to do the same thing

And when things do go wrong, start with Readable code. Because it’s easier to debug when things do crash. Next, develop a sixth sense for errors. Don’t give up too easily. Use all the artillery you got. Your best friend is the Debugger, standard for most IDE’s, and in most cases, very useful to pinpoint and zoom-in on the target:
·         Call – Stack / Trace
o   As the program halts or crashes, it shows the functions called earlier that brought you to this place. Maybe the function itself is not faulty, but the parameters given by its caller are?
·         Use Breakpoints to halt the program when reaching a tagged line.
o   Do we even get there at all? If not… decision gone wrong / bug earlier?
·         Step through your code, line by line
o   See on what exact line things go BOOM
o   Keep a close eye on all your variables.
o   Weird numbers? Wrong array indexing? Pointers Zeroed or never initialized at all?
·         You can use “Watches” to monitor (global) variables over a longer time
o   Sometimes global variable X may lead to a crash, but was actually mutilated by some other process earlier on.

Also don’t forget you can dump text lines or variables into a console in most cases. With this, you can tackle most errors. There are a few son-of-a-bitch exceptions though. So, first of all, do not suffer from police-investigation-tunnel-vision. Stay open for trouble-causers in unexpected corners… Which is why I warned you for Multi-Threading and using external libraries, as they can hide certain errors.


Worst thing are invalid memory reads/writes. This usually happens when forgetting to initialize pointers, or when they refer to something already destroyed. Also streaming larger data arrays and getting out of boundaries can affect innocent surrounding variables. Such errors are mostly detected right away, but in some cases your program keeps running and triggers a chain reaction of weirdness, Twilight Zone errors. The type of error that seems untraceable and illogical. But don’t forget, there is always a cause. Check your pointers. Or more drastically, quarantine code sections by leaving them out (if possible). Also memory profilers can be valuable here.

Oh, and one more thing, don’t suffer from tunnelvision either (debuggers and the tips above don’t show everything!!). If the police investigation doesn’t reveal anything, try to approach the crime-scene from a whole different approach. For example, sometimes bugs get fixed by catching another, seemingly unrelated, bug.


If all fails, have a plan-B. And no, crying is not a plan-B. Replace the faulty part with other code, or if your whole procedure was wonky anyway, redo (see lesson1). As much as it sucks, it’s part of the job.
 



10. Be consistent


One more hint, randomly grabbed from my stinky programming hat. Be consistent. In everything you do. As you think, as you talk, as you walk, as you make your sandwiches, as you program. As mentioned somewhere earlier, it’s good to view your code from a third perspective. You probably think your code rocks, but would another programmer think the same? Probably not, because programmers are stubborn and can always do better than you. But… at least it helps if your code A: just works, and B: is comprehensible.

As explained in #5, Size matters. What also matters, is consistency. If you look carefully at other professional libraries, you will notice there are certain naming conventions, as well as a certain approach. OpenGL for instance is a working on a State Machine principle. In general you would:
·         Bind the resources you want to work on or with (textures, vertex buffers, shaders, …)
·         Toggle options on/off (enable depth-testing, disable anti-alias, …)
·         Eventually issue a drawing-command
·         Bind to some other resource

You may argue if that’s easy when comparing to other API’s such as DirectX, but the point is that they use this approach everywhere. So you know what to expect. Things would be very confusing if a mixture of techniques was used. If textures would be OOP classes while vertex-buffers aren’t, it makes it harder to learn the patterns of your API. If you have to toggle off State Machine options after using X, but not after doing Y, it generates false expectations and errors.

Providing good documents is Silver, providing self-explanatory code that doesn’t need a manual, is Gold. Certainly some things are just too complicated, but at least try to.


Styles changes though. In the eighties we had big-fat mullets, in 2016 kids grow beards to look more manly, but spend more hours behind the mirror than their girls, combing and twisting that beard. Next year I expect pipe-smoking, sabre duels, and Nazi moustaches to be the trend again. Your coding style will change too. And that makes it harder to work consistently, especially when working on a large project spread over months/years, or done with multiple persons. You can write down coding conventions, though I’m the type of guy that would never read such a document again. In that case, keep looking back at other previously done code segments to take an example, instead of just writing away blindly. And even if you’re new style is much cooler than those old snippets, think again before suddenly doing things differently in the same code-base.

Be neat. If not for another programmer, then at least for yourself, in case that bitch from the office calls again with a weird problem, four years later. Do yourself a favour. It doesn’t cost too much, and that neater style you develop throughout the years will reflect in every bit of code.

 Work neat I said. Merry Christmas. Or not, if you're not celebrating it.

 

No comments:

Post a Comment