Author Topic: All variables should be Global  (Read 18065 times)

0 Members and 1 Guest are viewing this topic.

Offline @rtTopic starter

  • Super Contributor
  • ***
  • Posts: 1059
All variables should be Global
« on: February 27, 2017, 02:45:35 pm »
Hi Guys,
That, I believe, is what’s known in the biz as clickbait :)

It occurs to me that in microcontroller land, and in C,  that all functions should be void functions,
and have no arguments passed to them, or returned from them by anything other than global variables accessed directly within the function.

Why is anything other than the above, anything other than a waste of time using a stack for nothing?
.. other than some guy with a suit & tie said: “Variables should be private unless global in their nature”.
I know the obvious reason of the Human programmer making a mess, but is there a real reason?

 

Offline capt bullshot

  • Super Contributor
  • ***
  • Posts: 3033
  • Country: de
    • Mostly useless stuff, but nice to have: wunderkis.de
Re: All variables should be Global
« Reply #1 on: February 27, 2017, 02:49:36 pm »
There are many reasons to do so:

limited ressources (RAM), limited ressources (Flash memory), and limited ressources (processing speed)
Coding OOP style on a 8K Flash / 256B RAM micro is considered bloat ;-)
Safety devices hinder evolution
 

Offline @rtTopic starter

  • Super Contributor
  • ***
  • Posts: 1059
Re: All variables should be Global
« Reply #2 on: February 27, 2017, 03:04:33 pm »
Yes sure your variables can be reused, but if you can get away with it...
you could also do faster, and sometimes save memory.
I can’t see any way to get past the copy to some other memory or stack where the arguments are passed,
unless that’s where an optimiser comes in.

Code: [Select]
x = 0; y = 0; // passed as arguments
while (x < 100) {setpixel(x,y); x++;}

x = 0; y = 0; // global
while (x < 100) {setpixel(); x++;}
 

Offline cyr

  • Frequent Contributor
  • **
  • Posts: 252
  • Country: se
Re: All variables should be Global
« Reply #3 on: February 27, 2017, 03:19:26 pm »
Using global variables may stop the compiler from doing optimizations like sticking the values in registers...

Having code that is readable, maintainable and reusable are "real" reasons BTW.

 
The following users thanked this post: janoc, moz

Offline donotdespisethesnake

  • Super Contributor
  • ***
  • Posts: 1093
  • Country: gb
  • Embedded stuff
Re: All variables should be Global
« Reply #4 on: February 27, 2017, 03:25:25 pm »
I know the obvious reason of the Human programmer making a mess, but is there a real reason?

That is the real reason. Programmers are human.

BTW, you failed the interview, sorry :)
Bob
"All you said is just a bunch of opinions."
 

Offline @rtTopic starter

  • Super Contributor
  • ***
  • Posts: 1059
Re: All variables should be Global
« Reply #5 on: February 27, 2017, 03:40:52 pm »
Quote
That is the real reason. Programmers are human.

BTW, you failed the interview, sorry :)

You already know that I already know not to show that to an interviewer, but yeah, I know :D



 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1637
  • Country: nl
Re: All variables should be Global
« Reply #6 on: February 27, 2017, 03:47:44 pm »
I think these styles can be architecture specific. For example, on 8-bit PIC global variables is actually employed in the background, because that's basically the only efficient memory model it supports to run C.

But it is done for you, and the compiler can make choices on reusing certain bits of memory depending on the call nesting of your program and which local variables exist at what time. I would argue to not try to beat the compiler.


Yes sure your variables can be reused, but if you can get away with it...
you could also do faster, and sometimes save memory.
I can’t see any way to get past the copy to some other memory or stack where the arguments are passed,
unless that’s where an optimiser comes in.

Code: [Select]
x = 0; y = 0; // passed as arguments
while (x < 100) {setpixel(x,y); x++;}

x = 0; y = 0; // global
while (x < 100) {setpixel(); x++;}

I think you'll find neither is significantly quicker or memory efficient on something like ARM. The first has to push/pop variables onto the stack (which it does anyway as per the calling convention), and the other needs to store variables to fixed addresses (which is cumbersome on ARM) and load them too. I suppose both will have very similar levels of overhead.
But in all cases I would favour the first one. It is more readable, more maintainable, less of a mess , and most importantly probably a lot better inlineable by the compiler and thus quicker.
And useful for some: unit testing is a lot easier when you don't have tons of global variables that need housekeeping.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #7 on: February 27, 2017, 04:34:19 pm »
It occurs to me that in microcontroller land, and in C,  that all functions should be void functions,
and have no arguments passed to them, or returned from them by anything other than global variables accessed directly within the function.

If you set arguments as globals and return values as globals, you are just doing the compiler's job for it, and it can probably do a better job than you at passing and returning values to/from function.

When you have a modern compiler, on many architectures, arguments and return values aren't actually passed using the stack. Registers are used for the first few arguments instead. Return values are often not copied at all (return-value optimization). Even if the stack has to be used, many architectures have dedicated instructions for using the stack, that will be faster than doing indirect load/stores to globals. On ARM for example, there are instructions to push and pop multiple registers in a single instruction. On higher end microcontrollers with cache (like some Cortex-M7-based ones), stack also has very good temporal AND spatial locality.

Function call mechanism has been optimized to death already by both hardware designers and compiler writers. It's unlikely that you can beat it.
« Last Edit: February 27, 2017, 04:37:44 pm by cyberfish »
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21658
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: All variables should be Global
« Reply #8 on: February 27, 2017, 05:21:15 pm »
One word: interrupts.

Reentrant and recursive programming requires stack (or if not hardware stack, then a software emulation of it).

That's still no hard requirement -- interrupts can usually be serviced quickly, and usually one at a time.  But it's a lot easier to deal with things if they can pile up a modest amount.

You can design a system so that interrupts are used more like events, where multiple concurrent events (even from the same source) can be executing.  An example might be a global timer interrupt, which fires off housekeeping activities periodically (reading inputs, swapping tasks in an RToS, etc.).  Each of those events is stored on the stack, on top of the interrupt that started it.  That interrupt might fire again before the first one has finished, in which case, a non-reentrant version gets its variables trashed!

Not to say functions should be exclusively reentrant, either: it can be very useful to keep a global state, to say for example: if an optional function is still waiting on the stack, skip over it this time.  This might be implemented by setting a static (global) "busy" flag within the function (and clearing it at the end of the function), and checking that flag before jumping into the function again.

A universally applicable case of this: checking stack size/position/usage.  If you have an arbitrarily recursive program, with no provable limit on stack requirements, and you outright ignore how much stack is being used, then some day, your program is going to steamroll through other memory.  Madness ensues.  >:D

Which relates to the theoretical concept: a Turing-complete machine has unlimited memory, and can recurse [computably] infinitely.  An ideal Turing machine does not need to check for stack size, because it won't run out.  All our so-called "Turing-complete" machines are semantically complete, but finite in storage, so they cannot truly compute any computable function.  (Example: no one has calculated what Ackermann(999, 999) is, but it's proven computable.  Oh, also... we only have finite time to compute something.  So, say, while it's absolutely possible to find collisions in any hash function, a SHA-1 collision has been realized only recently.)

So, as programmers of not-quite-Turing-complete machines, we must be aware of their limitations, and dirty our theoretically ideal functions with necessary precautions.

Tim
« Last Edit: February 27, 2017, 05:24:11 pm by T3sl4co1l »
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 
The following users thanked this post: janoc, 3db

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1668
  • Country: us
Re: All variables should be Global
« Reply #9 on: February 27, 2017, 05:25:15 pm »
Complexity is the number-one enemy of high-quality code.
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13742
  • Country: gb
    • Mike's Electric Stuff
Re: All variables should be Global
« Reply #10 on: February 27, 2017, 05:32:58 pm »
Unless you're doing high volumes, dev &debug time costs more than silicon, so using a slightly bigger part to make coding quicker or more reliable is a no-brainer.
By making things unnecessarily global you are depriving the compiler of info that could be used for optimisation.
One thing I feel is missing from C is the concept of local procedures, like Pascal, so you have a mid-way point between local and global vars.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 
The following users thanked this post: Frost

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9889
  • Country: us
Re: All variables should be Global
« Reply #11 on: February 27, 2017, 05:35:43 pm »
Sounds a lot like Fortran COMMON to me!

For very large programs, including those with overlays, COMMON blocks were used to statically allocate variables.  Whether or not that is a good idea is something I'll leave to the computer scientists.

The first computer I ever programmed was an IBM 1130 with 8k words of memory.  It wasn't unusual to have user subroutines loaded on call.  Even system routines could be loaded on call, the Fortran compiler had 27 overlays (called phases).  This resulted in a lot of disk activity but memory was expensive.  Mainline programs often had a massive COMMON statement that carried through to all the subroutines.  The only good news is that it was possible to duplicate the cards and simply add them to the deck.

Data hiding and protection seems like a much better approach.  At least keep variables file static.  The whole world doesn't need to know.
 

Offline tszaboo

  • Super Contributor
  • ***
  • Posts: 7369
  • Country: nl
  • Current job: ATEX product design
Re: All variables should be Global
« Reply #12 on: February 27, 2017, 05:42:29 pm »
One word: interrupts.
The keyword "volatile" should be used by all data, which is acessed by interrupts, and then magically it will work.
To OP: Avoid "new" and "malloc" (or use malloc once and never free it) define everything compile time, dont use pointer list and silly things like that. Otherwise you can return an int for a function. What you must avoid is memory fragmentation, and running out of memory. Dont be so strict, it is unnecessary, unless some automotive silliness requires it. So yes. Use local variables, it is fine. Just give the stack some space.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: All variables should be Global
« Reply #13 on: February 27, 2017, 06:04:02 pm »
Hi Guys,
That, I believe, is what’s known in the biz as clickbait :)

It occurs to me that in microcontroller land, and in C,  that all functions should be void functions,
and have no arguments passed to them, or returned from them by anything other than global variables accessed directly within the function.

Why is anything other than the above, anything other than a waste of time using a stack for nothing?
.. other than some guy with a suit & tie said: “Variables should be private unless global in their nature”.
I know the obvious reason of the Human programmer making a mess, but is there a real reason?

Well, that's just wrong.

In AVR land wth a C compiler, the first 18 bytes of function arguments are in registers. The first 8 bytes of those you just go ahead and use, the next 10 you have to make sure you save and restore before you yourself return.

8 bytes is usually plenty, if your arguments are pointers or 8/16 bit integers. Why on earth would you want to use extra code and time to copy those to global variables, and then time and code to read them from the global variables in the called function? Decent modern compilers can usually have those values generated into the right place for a subsequent function call, so you don't even have to use code and time to move them there.

On ARM, the first 16 bytes (up to four arguments if 32 bits or smaller) are passed in registers. Again, usually enough.

On RISC-V (which I have in my "HiFive1" Arduino clone, and may well be found in increasing numbers in embedded work in future) the first 32 bytes of arguments (up to 8 arguments) are passed in registers. And the called function had all those eight registers to play with, plus another seven "temporary" registers, before it even has to think about touching memory.

As for global variables vs local variables in the main function ... it makes no difference. Most systems are allocating global variables starting from some low address (just above the code, if code isn't in another address space). First the initialized variables (copied from flash or wherever) and then the zero-inited variables. Then you have the "program break" and space after that is allocated by sbrk() (or malloc() calling sbrk()). The stack starts from the top of memory and works its way down towards the program break. If they ever meet .. bad news. It makes basically zero difference whether something is a global, allocated by malloc() at program startup (and never released), or or in the stack frame of main(). You've got the same memory use either way, with the only difference being exactly where the stack and program break will meet (if they do).

Systems don't have to be organized that way, but these days most that use C have converged on the same layout.

For functions other than main() ... if several functions share some data, and there's only every one copy of it .. sure, make it a global rather than passing around a pointer. Things such as counting how many times something happened in a function, spread over many function calls, should be globals.

Temporary data, used only by one function (or by functions it calls) should be on the stack. The stack gives you automatic overlays of the memory areas used by different functions that are called at different times (and don't call each other). The *only* valid reason not to use data on the stack is if your CPU has poor support for a stack. That's the case for some older CPUs such as 6502 (and PIC?), but it's most definitely not true for AVR or ARM or even anything derived from 8080/Z80.
 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1637
  • Country: nl
Re: All variables should be Global
« Reply #14 on: February 27, 2017, 09:20:18 pm »
The 8-bit PICs only have a HW stack for program counter only if I recall. All data must be passed through RAM, with perhaps the exception of 1 argument and return which can be passed through the processor register.

That's why I said in my previous comment that this style of coding could be architecture dependent, if indeed a programmer tries to target such a platform. But a modern compiler like XC8 will have stack emulation techniques built in (take a moment to browse in the compiler settings). I recall the default one tries to achieve the most compact variable layout with most amount of sharing depending on the call tree, but there were 2 others one as well.
On PICs it's not uncommon to have functions compiled twice, in particular when it needs to be reentrant from ISR and main code. Some algorithms may also have some kind of recursiveness (maybe only 1-2 calls deep) which will make trouble using globals. Having the compiler handle this for you, instead of manually coding this using globals, is a big relief.

Trying to beat that level of compiler ingenuity is not worth it IMO, but I'm not surprised it's being done. It depends on what you need I suppose, maybe automotive that demands simple CPUs use these chips and also wants to eliminate any "intelligence" not in control of a human between build cycles. But then I would say: write assembler. If C is truly a portable assembler, I wouldn't want to worry that much about the memory model of my target architecture.

I think it's also common censuses on this forum that we rather code things well than code things for mass production (qty: >100k-1M) devices.
 

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 414
  • Country: us
Re: All variables should be Global
« Reply #15 on: February 28, 2017, 04:25:07 pm »
Execution efficiency should matter.  Trust me it will one day.  Remember extra cycles used in the name of clean code use extra energy.

How much energy is wasted by CPU's having to clock faster to process suboptimal code. 

It's my pet peeve because i write code for micros running on batteries so I like to be stingy with the cpu cycles for battery life.

Would somebody care to explain how globals can be less efficient in terms of execution time.

Oh and please let me have it regarding my uninformed and silly ideas.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #16 on: February 28, 2017, 04:27:33 pm »
Execution efficiency should matter.  Trust me it will one day.  Remember extra cycles used in the name of clean code use extra energy.

How much energy is wasted by CPU's having to clock faster to process suboptimal code. 

It's my pet peeve because i write code for micros running on batteries so I like to be stingy with the cpu cycles for battery life.

Would somebody care to explain how globals can be less efficient in terms of execution time.

Oh and please let me have it regarding my uninformed and silly ideas.

A few people (including myself) have already. Read replies above.

Using function arguments is faster, uses less RAM, and also cleaner. Also more flexible (allows recursion, and calling from interrupt handlers). Main reason is that most arguments are passed using registers anyways, which is faster than globals.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: All variables should be Global
« Reply #17 on: February 28, 2017, 04:45:24 pm »
Functions are also a complete waste of time. You should put everything in assembly!

Now serious. C and C++ are all about keeping data where is belongs.
If you put everything global you will have a lot of data where it does not belong. This will impede other coders, and eventually also you, in understanding what is going on.

C has a few scopes where data is relevant.
1. Global, referenced from everywhere. No protection from mistakes whatsoever.
2. File, referenced only from file.c, use the static keyword to enforce this.
3. Static function scope, this variable has the static keyword used on it in a function. It can only be referenced from function scope. Between the {}. However, it is allocated permantently. Meaning it's like a global, but private.
4. Function scope, these variables are only allocated for function duration. Such as the arguments. You'd be making a mess with "tempvar" if you insist in making these global.
5. Block scope. Wherever in the code you can nest a block {}. You can do this for the editor the collapse the block. But in strict ansi c you can use it to create another level of temporary veriables only allocated during the block. (Since you can only do this at the start a of block) Useful hints for you and the compiler. An for with {} has such block. There are often iterators inside.

Ignoring the implementation of ancient or crap compilers, it would be a waste not using the tools on hand in C to create more structured and segmented code.
You can put everything in 1 file, or even in main. Or all assembly. But it wastes the most expensive commodity on hand. developer time.
« Last Edit: February 28, 2017, 04:51:11 pm by Jeroen3 »
 

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 414
  • Country: us
Re: All variables should be Global
« Reply #18 on: February 28, 2017, 04:51:25 pm »
Well what I read there concerns using the registers which are faster.  But doesn't this need to assume that the values are already in the registers when the function is called.  That certainly would be the case many times but not always.  Some CPU operations can act directly on memory without loading to a register so this would seem to be a plus for globals.  But I guess the main idea is that variables passed through the function interface are not necessarily copied to the stack if they are small enough for registers.  I get it.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: All variables should be Global
« Reply #19 on: February 28, 2017, 05:05:21 pm »
Well what I read there concerns using the registers which are faster.  But doesn't this need to assume that the values are already in the registers when the function is called.  That certainly would be the case many times but not always.  Some CPU operations can act directly on memory without loading to a register so this would seem to be a plus for globals.  But I guess the main idea is that variables passed through the function interface are not necessarily copied to the stack if they are small enough for registers.  I get it.

No CPU acts on memory without copying it to a register. It's just that the temporary register used doesn't have a name and you can't see it.

Accessing memory takes probably ten times the energy of accessing a register that already has the value.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #20 on: February 28, 2017, 05:09:11 pm »
Well what I read there concerns using the registers which are faster.  But doesn't this need to assume that the values are already in the registers when the function is called.  That certainly would be the case many times but not always.  Some CPU operations can act directly on memory without loading to a register so this would seem to be a plus for globals.  But I guess the main idea is that variables passed through the function interface are not necessarily copied to the stack if they are small enough for registers.  I get it.

If the values are in memory and not in registers, using globals would require copying those values from memory to memory first. m-2-m is slower than m-2-r on most architectures, and maybe equal on some.

The only time it can be faster is if the argument is already in the global you need. This is almost never going to be the case, unless you have a chain of functions you know you'll be calling one by one. In this case using arguments and return values still beat it because it allows the optimizer to do return-value optimization which eliminates all those copies.

Using globals also stop the compiler from doing optimizations like function inlining, which even eliminates the function call overhead, and allows more optimizations to be done following the inlining.

Modern optimizers are very smart. A lot of times the best thing you can do is to write your code in the simplest and cleanest way, to make the optimizer's job easier. Unless you know exactly what you are doing, and you know the architecture better than the compiler, being clever will often just confuse the optimizer and you get sub-optimal code.

Don't make your code ugly for no reason. In this case it's even slower.
 

Offline grumpydoc

  • Super Contributor
  • ***
  • Posts: 2905
  • Country: gb
Re: All variables should be Global
« Reply #21 on: February 28, 2017, 05:30:59 pm »
Unless you're doing high volumes, dev &debug time costs more than silicon, so using a slightly bigger part to make coding quicker or more reliable is a no-brainer.
By making things unnecessarily global you are depriving the compiler of info that could be used for optimisation.
One thing I feel is missing from C is the concept of local procedures, like Pascal, so you have a mid-way point between local and global vars.
Nested functions are available in GCC as a extension.

AFAIK they are not yet in ANSI standard C.

As to the original question - why do (some) embedded programmers seem to feel that they can abandon all good coding practices as suits their whim?

If everything is a global your code will not be re-entrant, and your compiler might miss optimisations but more importantly anything which is longer than a single A4 page will simply become unreadable and unmaintainable. You will also invariably create bugs by using the wrong variable accidentally. Don't do it.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #22 on: February 28, 2017, 05:35:21 pm »
Nested functions are available in GCC as a extension.

It has been available in C++ since C++11 though!

Code: [Select]
int main() {
  auto increment = [](int x) -> int {
    return x + 1;
  }

  int a = increment(2);
}
 

Offline slicendice

  • Frequent Contributor
  • **
  • Posts: 365
  • Country: fi
Re: All variables should be Global
« Reply #23 on: February 28, 2017, 05:43:56 pm »
Functions are also a complete waste of time. You should put everything in assembly!

LOL, writing Assembly the correct way (the most memory conservant and efficient) would be the equivalent of a C-function with input parameters and a return value (if you need them, depends of application complexity), and local variables for just enough values to get the function job done. The simpler the function, the better. You want to stick to the registers only, as far as possible, and if the code must jump, it should do a short jump (calling the stack) and try to avoid long jumps (getting new instructions from memory) as much as possible.

A variable is just a memory address, the fewer addresses you need at one point in time, the better. Defining everything global would require huge amounts of memory, or you would have a hard time making sure variables are not overwritten.

Using ANY modern C compiler today, should produce really good Assembly code for the most basic functions. There should not be any big difference between writing Assembly or C. I am not sure how the ARM compilers behave, as I have not tested it, but all C compilers has produced exactly the same Assembly code as I have written manually in Assembly. There are however a lot of exceptions to be aware of, and this is where writing pure Assembly comes in play. The difference in size can be many many bytes for just one single instruction. Sometimes there are multiple solutions(in Assembly) for the same simple problem, but the size of your code can differ a lot between them.

 

Offline grumpydoc

  • Super Contributor
  • ***
  • Posts: 2905
  • Country: gb
Re: All variables should be Global
« Reply #24 on: February 28, 2017, 05:44:44 pm »
Nested functions are available in GCC as a extension.

It has been available in C++ since C++11 though!

As if C++ was not a deep enough quagmire of features ready to drown yourself in!
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #25 on: February 28, 2017, 05:47:52 pm »
Nested functions are available in GCC as a extension.

It has been available in C++ since C++11 though!

As if C++ was not a deep enough quagmire of features ready to drown yourself in!

It's a difficult language to learn, but once you are proficient with it it's amazing!

You get all the performance benefits of C (sometimes even faster than C from things like template meta-programming), with high level constructs that save a lot of time/code.
 

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1668
  • Country: us
Re: All variables should be Global
« Reply #26 on: February 28, 2017, 06:10:06 pm »
It has been available in C++ since C++11 though!

With incredibly painful syntax that only a demented language designer could love.  :palm:
Complexity is the number-one enemy of high-quality code.
 

Offline Cerebus

  • Super Contributor
  • ***
  • Posts: 10576
  • Country: gb
Re: All variables should be Global
« Reply #27 on: February 28, 2017, 06:38:24 pm »
With incredibly painful syntax that only a demented language designer could love.  :palm:

I don't think any actual design was involved.

If you think that syntax is painful to use or remember, try writing a parser for it - the actual grammar is about as irregular and inconsistent as you can get without actually deliberately setting out to make it that way.
Anybody got a syringe I can use to squeeze the magic smoke back into this?
 

Offline grumpydoc

  • Super Contributor
  • ***
  • Posts: 2905
  • Country: gb
Re: All variables should be Global
« Reply #28 on: February 28, 2017, 06:56:30 pm »
Nested functions are available in GCC as a extension.

It has been available in C++ since C++11 though!

As if C++ was not a deep enough quagmire of features ready to drown yourself in!

It's a difficult language to learn, but once you are proficient with it it's amazing!

As a professional software engineer I have written a lot of C++ although I am a bit out of touch with some of the newer language features including lambda expressions. EDIT: I left full-time IT in 2003. I just tinker these days.

As a software developer I agree that it is a nice language to work with, it is expressive and nuanced and you can write elegant code if you wish.

With a more managerial hat on, however, I grew more and more disillusioned with the language - the more large projects are tempted to use the "neat" features of the language (especially templates and now, I suppose, lambdas) the more they become difficult to maintain and the bar to new staff joining a project becomes ever higher. Large, complex C++ programs can be impossible to comprehend because they simply defeat static analysis.

I think part of the problem is that the C++ committee likes to add "powerful building blocks" to the language but forget that these features need a very responsible hand when in use. The nested functions thing is a case in point - rather than add nested functions they chose to add lambda bindings sufficiently powerful to allow them to be used to implement nested functions. Templates is another example - did you realise that the template language is actually Turing complete and there are examples of code which causes the compiler to emit (eg) Fibonacci sequences or lists of prime numbers as a side effect of compilation. I mean, did we really need that  |O

These days I actually prefer well crafted C
« Last Edit: February 28, 2017, 07:40:33 pm by grumpydoc »
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #29 on: February 28, 2017, 07:08:26 pm »
It has been available in C++ since C++11 though!

With incredibly painful syntax that only a demented language designer could love.  :palm:

Yeah. That's because lambda expressions are much more useful than just for nested functions.

It also allows you to pass those functions around as objects, and bind local values and references to them. That makes async code much cleaner.

When used as nested functions, the syntax does seem over-engineered.

Quote
I think part of the problem is that the C++ committee likes to add "powerful building blocks" to the language but forget that these features need a very responsible hand when in use.
This I totally agree with. It's one of the main differences in design philosophy between Java and C++. Java = don't include features that can be abused. C++ = include features as long as there are uses for them.

This is why we don't have operator overloading in Java for example (very useful if used correctly, but easy to abuse), and we still have goto in C++ (can be used to break out of nested loops, which would otherwise require a bunch of flags, or abusing exceptions). I would much rather work in C++ than Java, but I would much rather work with an incompetent Java developer than an incompetent C++ one.

Luckily everyone I work with are highly competent, so I don't worry about that too much.
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #30 on: February 28, 2017, 07:38:40 pm »
Nested functions are available in GCC as a extension.

It has been available in C++ since C++11 though!

As if C++ was not a deep enough quagmire of features ready to drown yourself in!

It's a difficult language to learn, but once you are proficient with it it's amazing!

As a professional software engineer I have written a lot of C++ although I am a bit out of touch with some of the newer language features including lambda expressions.

As a software developer I agree that it is a nice language to work with, it is expressive and nuanced and you can write elegant code if you wish.

With a more managerial hat on, however, I grew more and more disillusioned with the language - the more large projects are tempted to use the "neat" features of the language (especially templates and now, I suppose, lambdas) the more they become difficult to maintain and the bar to new staff joining a project becomes ever higher. Large, complex C++ programs can be impossible to comprehend because they simply defeat static analysis.

I think part of the problem is that the C++ committee likes to add "powerful building blocks" to the language but forget that these features need a very responsible hand when in use. The nested functions thing is a case in point - rather than add nested functions they chose to add lambda bindings sufficiently powerful to allow them to be used to implement nested functions. Templates is another example - did you realise that the template language is actually Turing complete and there are examples of code which causes the compiler to emit (eg) Fibonacci sequences or lists of prime numbers as a side effect of compilation. I mean, did we really need that  |O

These days I actually prefer well crafted C

All very very true, unfortunately :(

In the early 90s I decided that "if C++ is the answer, then you ought to change the question". Unfortunately nothing has caused me to change that somewhat aesthetic decision. Back then the two key points for me were
  • the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered
  • the endless discussions as to whether it "casting away constness" should be mandatory or should be forbidden

Much later, the amusing/frightening C++ FQA emerged. Anybody contemplating using C++ should understand its ramifications inside out before using the language.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1637
  • Country: nl
Re: All variables should be Global
« Reply #31 on: February 28, 2017, 08:13:34 pm »
I thought it would be nice to benchmark said claims on 2 architectures: PIC24 and PIC32.
I chose these because they have simulators in MPLAB X. And both chips have GCC-based compilers.

Also interesting is the contrast between both architectures. The PIC24 has it's CPU registers mapped onto the address bus, and some instructions can access RAM (even like INC [W14], [W14]). However most ALU instructions need to go through the register file.
The PIC32, like ARM CPUs, needs more explicit instructions to access RAM. Mainly via pointers only.

Now, let's run this code:
Code: [Select]
#include <stdint.h>
#include <xc.h>

uint16_t gx = 0;
uint16_t gy = 0;

uint16_t result = 0;
uint32_t s = 0;

inline void doStuffWithGlobals(void)
{
    result = (gx%9) * (gy%3);
}
inline uint32_t doStuffWithParameters(uint16_t x, uint16_t y)
{
    return (x%9) * (y%3);
}

uint32_t benchmarkParams(void)
{
    uint16_t x,y;
    uint32_t r = 0;
    for (y = 0; y < 0xFF; y++)
    {
        for (x = 0; x < 0xFF; x++)
        {
            r += doStuffWithParameters(x,y);
        }
    }
    return r;
}

void benchmarkGlobals(void)
{
    for (gy = 0; gy < 0xFF; gy++)
    {
        for (gx = 0; gx < 0xFF; gx++)
        {
            doStuffWithGlobals();
            s += result;
        }
    }
}

void stopwatchStart(void)
{
    T2CON = 0;
    T2CONbits.T32 = 1;
    TMR2 = 0;
    TMR3 = 0;
    T2CONbits.TON = 1;
}
uint32_t stopwatchStop(void)
{
    T2CONbits.TON = 0;
    return ((uint32_t)TMR2) | ((uint32_t)TMR3 << 16);
}

int main(void)
{
    volatile uint32_t timeParams  = 0;
    volatile uint32_t timeGlobals = 0;
   
    stopwatchStart();
   
    volatile uint32_t s1 = benchmarkParams();
   
    timeParams = stopwatchStop();
   
    stopwatchStart();
    benchmarkGlobals();
   
    volatile uint32_t s2 = s;
   
    timeGlobals = stopwatchStop();
   
    // s1 should be s2
    while(1);
}

In all cases the computed result is correct. The algorithm I try to benchmark is just some bogus thing, but at least the compiler isn't clever enough to optimize away the complete algorithm straight away.

For PIC24 I used XC16 1.30 GCC -O1 + "do not override inline".
With inline: Time of parameters: 1,762,339 ticks Time of globals: 1,761,832 ticks
Without inline: Parameters: 3,772,759 Globals: 4,097,871

If I change the algorithm to simply x*y I get (with inline):
Parameters: 456,727 Globals: 456,476
Without inline: Parameters: 1,889,837 Globals: 1,889,578

Actually quite surprising results. The code actually marginally faster. Now that's cool I suppose, but it's not faster in all cases. Especially on non-optimized code like without inline.
The reason I did not want to focus on x*y though, is because the compiler sees that sequence of operations and optimizes multiplication out of the code. It's not far off from assigning a fixed value to the result.

On PIC32 the results are drastically different:
(XC32 1.30 GCC -O1 -finline-functions)
Inline: Parameters: 848,916 Globals: 978,200
No inline: Parameters: 848,916 Globals: 2,342,448
Multiplication (inline): Parameters: 327,171 Globals: 391,447

Implicit vs explicit compiler optimizations become very clear at this level (the compiler tried to inline the parameterized function anyway). It also shows that RAM access with huge address spaces is a lot more expensive.

That's why they have register files and caches to speed programs up.
 
The following users thanked this post: thm_w

Offline moz

  • Regular Contributor
  • *
  • Posts: 89
  • Country: au
Re: All variables should be Global
« Reply #32 on: February 28, 2017, 09:37:30 pm »
The rule of thumb I use is that good code is more than 90% maintenance. Only bad code is less... it means no-one is using it, or no-one is willing to risk modifying it.

Much later, the amusing/frightening C++ FQA emerged. Anybody contemplating using C++ should understand its ramifications inside out before using the language.

This. I started a project with some specific feature requirements that at the time could really only be met by C++ (talking to embedded hardware via internet-exposed encrypted IP). Sadly Scala, Rust and Go were all 90% there. C++ is awesome, whenever there's a question of features they settle for "all of them". You can do just about anything with the language. Which makes it a bit too much like Perl for my liking - it's very easy to produce write-only code. And the lack of a first class unit test framework makes me cringe.

I have ended up writing "C++ lite" most of the time. No Boost, very few templates, but I use selected features like lambdas a lot because coming down from a modern language it's very hard not to think in lambdas.
 

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 414
  • Country: us
Re: All variables should be Global
« Reply #33 on: February 28, 2017, 09:46:45 pm »

 
On PIC32 the results are drastically different:
(XC32 1.30 GCC -O1 -finline-functions)
Inline: Parameters: 848,916 Globals: 978,200
No inline: Parameters: 848,916 Globals: 2,342,448
Multiplication (inline): Parameters: 327,171 Globals: 391,447

Implicit vs explicit compiler optimizations become very clear at this level (the compiler tried to inline the parameterized function anyway). It also shows that RAM access with huge address spaces is a lot more expensive.

That's why they have register files and caches to speed programs up.


Any speculation as to why globals took so much more time without inlining?
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #34 on: February 28, 2017, 10:05:33 pm »
The rule of thumb I use is that good code is more than 90% maintenance. Only bad code is less... it means no-one is using it, or no-one is willing to risk modifying it.

Much later, the amusing/frightening C++ FQA emerged. Anybody contemplating using C++ should understand its ramifications inside out before using the language.

This. I started a project with some specific feature requirements that at the time could really only be met by C++ (talking to embedded hardware via internet-exposed encrypted IP). Sadly Scala, Rust and Go were all 90% there. C++ is awesome, whenever there's a question of features they settle for "all of them". You can do just about anything with the language. Which makes it a bit too much like Perl for my liking - it's very easy to produce write-only code. And the lack of a first class unit test framework makes me cringe.

I have ended up writing "C++ lite" most of the time. No Boost, very few templates, but I use selected features like lambdas a lot because coming down from a modern language it's very hard not to think in lambdas.

That's all valid.

A problem can be that each project and/or developer and/library implicitly chooses a different subset, and they don't play well with each other.

Of course some subsets of some languages have significant tooling and momentum, e.g. MISRA C and SPARK ADA.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline TNorthover

  • Contributor
  • Posts: 42
  • Country: us
Re: All variables should be Global
« Reply #35 on: February 28, 2017, 10:12:14 pm »
Any speculation as to why globals took so much more time without inlining?

The test looks simple enough that (with MIPS's 32ish registers) virtually nothing would have to be stored to perform the calculation. Parameters automatically live in registers with or without inlining, so there's no barrier there.

But for globals you need to calculate their address and be store/load them at each function boundary. With inlining, the compiler can remove all the paired load/store operations because it sees them in the same function. Without, no such luck.
 

Offline hans

  • Super Contributor
  • ***
  • Posts: 1637
  • Country: nl
Re: All variables should be Global
« Reply #36 on: February 28, 2017, 10:55:20 pm »
I took a look at the disassembly. GCC dares to inline the multiplication routine even at level O1 and no explicit inline statements. It only does  that with globals at level O2. At O2, you get the same results as with explicit inline. Eventually the compiler does get to a similar level of performance.
However, by nature of a global, the compiler is more careful with optimizing at lower aggressive levels. Perhaps there also situations where a piece of code written using globals doesn't get the 'special treatment'.

This is indeed a really simple algorithm. Not a lot of data is being passed around. A lot of time is crunched in the inner loop. I suppose that the globals wins out because y only changes once every 256 inner loop ticks. For a global call it doesn't have to move the y value explicitly. This could save some time, but in respect to inline it's almost neglible. But that's only 1 case, maybe some others are different. But it certainly is not a general notion to take note of.

To be honest I'm surprised that the compiler isn't able to optimize my little bogus routine to an integer value.

These days in C++ you can generate complete random mazes at compile runtime, perform pathfinding algorithms on them, format the grid, and display it. And it all compiles back to down a const string.
https://youtu.be/3SXML1-Ty5U
 

Offline moz

  • Regular Contributor
  • *
  • Posts: 89
  • Country: au
Re: All variables should be Global
« Reply #37 on: February 28, 2017, 11:19:57 pm »
A problem can be that each project and/or developer and/library implicitly chooses a different subset, and they don't play well with each other.

That's another reason I chose C++, my coworkers at least use C on the embedded side so my code is fairly readable for them. It also means that the data structures look quite similar on both ends of the comms channel ("again with the debugging. Always with you the debugging" in my best Jewish mother-in-law voice).
 

Offline moz

  • Regular Contributor
  • *
  • Posts: 89
  • Country: au
Re: All variables should be Global
« Reply #38 on: February 28, 2017, 11:33:22 pm »
To be honest I'm surprised that the compiler isn't able to optimize my little bogus routine to an integer value.

Do you have access to a library common across architectures? I'm thinking of something like crypto or FFT. That way you can pick one function, hack a version to use mostly globals, and compare.

I have a wee crypto library (TinyAES) that I use for testing because it's all in C rather than the hand-optimised OpenSSL code that is much faster but makes debugging tools really unhappy (valgrind says it uses un-initialised values, OpenSSL say it only looks that way. Etc). But it's complex enough that I can compare real-ish performance just by saying "AES128 this" and get an idea of CPU performance in 10 lines of (my) code.

It also depends on which C complier you're using, and which C standard. C99 at least lets you have local static variables, which are better than globals for reasoning about the code (whether it's you or the compiler doing the reasoning). C11 adds thread-local variables and static asserts, again making life easier. But also making the "everything global" claim even less credible.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #39 on: March 01, 2017, 12:41:11 am »
In the early 90s I decided that "if C++ is the answer, then you ought to change the question". Unfortunately nothing has caused me to change that somewhat aesthetic decision. Back then the two key points for me were
  • the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered
  • the endless discussions as to whether it "casting away constness" should be mandatory or should be forbidden

Much later, the amusing/frightening C++ FQA emerged. Anybody contemplating using C++ should understand its ramifications inside out before using the language.

"If C++ is the answer, the problem is too complicated for someone who only writes C."

I hope you are aware that the first C++ standard was published in 1998 (so what you looked at wasn't even the first revision), and there has been 3 more standards since, and C++ today is nothing like C++ back then? It's very different even from C++03 (the latest standard before 2011).

For example, (almost) no one does manual memory management anymore. I work as a professional C++ developer and I haven't written any "new" or "delete" in many months. There's also automatic type inference now, saving people a lot of typing.

I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.

Templates have their problems, but we don't know any better way to achieve the same, and it's so useful that even Java copied it (and then bastardized it, but that's another discussion).
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #40 on: March 01, 2017, 01:05:59 am »
In the early 90s I decided that "if C++ is the answer, then you ought to change the question". Unfortunately nothing has caused me to change that somewhat aesthetic decision. Back then the two key points for me were
  • the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered
  • the endless discussions as to whether it "casting away constness" should be mandatory or should be forbidden

Much later, the amusing/frightening C++ FQA emerged. Anybody contemplating using C++ should understand its ramifications inside out before using the language.
I hope you are aware that the first C++ standard was published in 1998 (so what you looked at wasn't even the first revision), and there has been 3 more standards since, and C++ today is nothing like C++ back then? It's very different even from C++03 (the latest standard before 2011).

I am aware of that.
I started using C in 1982, when there were precisely two books on it.
I started using C++ in 1988, and having been liberated by grokking Smalltalk, I preferred Objective-C.
In the early 90s I kept using C, kept a watching eye on C++'s glacial progress, and was unpleasantly surprised by the standardisation process. As I said, I prefer my tools to be designed, not discovered. ("Oh look, my multimeter also solders wires")

I also like my tools to solve my problem, not be an added problem. "Language lawyers" are far too much part of the C++ ecosystem; they shouldn't be necessary.

Quote
For example, (almost) no one does manual memory management anymore. I work as a professional C++ developer and I haven't written any "new" or "delete" in many months. There's also automatic type inference now, saving people a lot of typing.

Not to anything like the extent possible with a strongly typed modern language.

Remind me, is it possible to "cast away constness" now?

Quote
I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.

I don't see how that is relevant to anything I wrote.

Quote
Templates have their problems, but we don't know any better way to achieve the same, and it's so useful that even Java copied it (and then bastardized it, but that's another discussion).

C++ wasn't the first to employ templates. (Old observation: academic papers about C++ refer to other academic papers about C++, whereas those for language X refer to languages X, Y, Z. That's a reflection of the C++ community being too self-absorbed and inward-looking for its own good)

Java templates are a pigs ear due to the requirement for "type erasure" to ensure backward compatibility. Even so, compilers produce comprehensible error messages, and debuggers produce understandable, correct output.

I suspect C# templates are better, but I have no interest in that language/environment.
« Last Edit: March 01, 2017, 01:07:44 am by tggzzz »
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #41 on: March 01, 2017, 01:26:50 am »
Remind me, is it possible to "cast away constness" now?
Yes, and it has been the case since the very first standard, so I'm not sure why you added the word "now".

Quote
Quote
I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.

I don't see how that is relevant to anything I wrote.

Quote
the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered

Unless you literally meant someone wrote a C++ compiler, didn't write code to support templates, and it turned out to magically support templates? (this would be equivalent to your multimeter and soldering example)
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: us
Re: All variables should be Global
« Reply #42 on: March 01, 2017, 01:54:47 am »
The idea that variables on a stack are less efficient to access if they're on a stack than if they're global is just ... wrong.
Consider "i += 1; // increment variable."

on AVR:
Code: [Select]
  ;; globals:
   lds r16, i    ; load from global: 4 bytes, 2 cycles
   subi r16, -1 ; increment:  2 bytes, 1 cycle
   sts r16, i   ; store to global: 4 bytes, 2 cycles
   
  ;; locals
   ldd r16, Y+i   ; load from stack: 2 bytes, 2 cycles
   subi r16, -1
   std r16, Y+i   ;store to stack: 2 bytes, 2 cycles.


67% bigger to access i as a global.  Z80, 68xx, 6502 all have some form of indexed addressing and should end up using similar code.

It's 100% bigger on an ARM, because there's no LDS equivalent:
Code: [Select]
;; Global
   ldr     r3, [pc, #8]    ; get address of i (6 bytes!)
   ldrb    r2, [r3, #0]    ;  get i
   adds    r2, #6          ; add
   strb    r2, [r3, #0]    ; store

;; local
   ldr     r3, [sp, #4]
   adds    r3, #6
   str     r3, [sp, #4] 

PIC16f might be able to do the global in 2 instructions, but the compiler I tried implemented the "local variable" version as a global anyway.  PIC18 and "enhanced PIC16f15xx" implement some form of indexed addressing, so they get better at handling a stack, even though there is no "hardware stack"  (we remember that stacks are just index registers with some autoincrement/decrement modes, and "stack frames" don't actually need those...)  MSP430 might be able to add immediate values direct to memory, but the instructions start to get long and slow...

And that's without worrying that you need a separate "i" for each subroutine, if you're using globals.

Argument-wise, if your arguments aren't passed in registers (which is common with modern processor) or on the stack, you eventually end up with the (old) BASIC-like syntax where you end up moving your arguments into the correct globals:
Code: [Select]
   sub1_param1=thisval
   sub1_param2=anotherval
   sub1();

(assuming you re-use your subroutines more than once, which ... is part of the idea, you know.)  This is obviously no better than moving the values to the stack (and might be worse, as per the above instructions.)

So, in the absence of an actual example where using globals is more efficient, I claim your entire premise is incorrect!
« Last Edit: March 01, 2017, 02:03:52 am by westfw »
 

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 414
  • Country: us
Re: All variables should be Global
« Reply #43 on: March 01, 2017, 02:24:33 am »
I have read on this issue that if there a many variables needed in a routine then define a struct containing the variables and pass a pointer to it to the function.  Is this a good way to handle many variables.

As an example suppose there is a "motor" datatype created with parameters such as current, speed, voltage, acceleration, temperature...etc.

functions get the pointer to the struct so they can read and modify the values. 

How about if the struct variable is declared with global scope??
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #44 on: March 01, 2017, 02:51:31 am »
I have read on this issue that if there a many variables needed in a routine then define a struct containing the variables and pass a pointer to it to the function.  Is this a good way to handle many variables.

As an example suppose there is a "motor" datatype created with parameters such as current, speed, voltage, acceleration, temperature...etc.

functions get the pointer to the struct so they can read and modify the values. 

How about if the struct variable is declared with global scope??

There's no performance difference in this case. Structs (of sizes larger than the largest int type usually) are always passed by pointers anyways (copies are made by caller if necessary).

This is also the sort of basic optimization that any half decent compiler will be able to do, so it's best to just keep the code as clean and intuitive as possible.
 

Offline TNorthover

  • Contributor
  • Posts: 42
  • Country: us
Re: All variables should be Global
« Reply #45 on: March 01, 2017, 03:08:52 am »
There's no performance difference in this case. Structs (of sizes larger than the largest int type usually) are always passed by pointers anyways (copies are made by caller if necessary).

This is roughly true for parameters passed by value (it's very ABI-dependant). But if the routine needs to modify a lot of variables (and so passes disparate values by pointer/reference) I could see putting them together in a struct would be beneficial. You'd possibly save a double-indirection, and the data would be more cache-local if that mattered to you.

Regardless, I'd always recommend making these decisions based on maintainability rather than pure efficiency. At least until there's no other option.

Quote
This is also the sort of basic optimization that any half decent compiler will be able to do, so it's best to just keep the code as clean and intuitive as possible.

Totally agree about the second part, but compilers aren't very free to meddle with parameter passing because functions can usually be called from other files. You need link-time optimization to have a hope in hell, and even then this kind of optimization is most charitably described as an emerging field.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #46 on: March 01, 2017, 03:37:59 am »
Totally agree about the second part, but compilers aren't very free to meddle with parameter passing because functions can usually be called from other files. You need link-time optimization to have a hope in hell, and even then this kind of optimization is most charitably described as an emerging field.
That is true. I'd hope everyone is using LTO by now!

I don't usually pay too much attention to this, because if a function is small enough to be inlined, calling convention doesn't matter anymore. If it isn't, it's probably big enough that call overhead is negligible anyways. Though I guess this is not true if you are highly flash-constrained, and have to optimize for size instead of speed.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: All variables should be Global
« Reply #47 on: March 01, 2017, 03:55:39 am »
Any speculation as to why globals took so much more time without inlining?

The test looks simple enough that (with MIPS's 32ish registers) virtually nothing would have to be stored to perform the calculation. Parameters automatically live in registers with or without inlining, so there's no barrier there.

But for globals you need to calculate their address and be store/load them at each function boundary. With inlining, the compiler can remove all the paired load/store operations because it sees them in the same function. Without, no such luck.

Hi Tim, what brings you here from llvm land?

I was a bit confused when you started talking about MIPS when the OP was talking about PIC32. I looked at 8 bit PIC once and ran screaming back to AVR. Didn't realize they'd picked up a decent architecture later. Does it implement the compressed instruction set?
 

Offline TNorthover

  • Contributor
  • Posts: 42
  • Country: us
Re: All variables should be Global
« Reply #48 on: March 01, 2017, 04:12:48 am »
Hi Tim, what brings you here from llvm land?

I decided there'd be even more fun toys to play with if I made them myself. And this specific subforum, just looking to stick my oar in. Good to see you here too!

Quote
Does it implement the compressed instruction set?

I'm afraid I don't know that for certain (my MIPS is strictly theoretical), but Google suggests at least some of them do (https://www.microchip.com/forums/m296546.aspx in particular).
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21658
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: All variables should be Global
« Reply #49 on: March 01, 2017, 08:41:22 am »
More examples:

8086:

Code: [Select]
INC BYTE PTR [BP+02]
Instruction: 3 bytes.
Time: 15 + 9 (EA) = 24 cycles.

Code: [Select]
INC BYTE PTR [0002]
Instruction: 4 bytes.
Time: 15 + 6 (EA) = 21 cycles.

Not a big difference, but fairly slow overall.  This is vastly improved in later models (like 386), so the difference is small.  x86 is a very powerful instruction set, but in even later models (highly pipelined, microinstruction, superscalar), the power of that instruction set became a huge burden (i.e., instruction decode takes up a lot of die area, time and power).

Z80:

Code: [Select]
INC (IX+02)
Instruction: 3 bytes.
Time: 6 M-cycles (23 clocks).

Code: [Select]
LD A, (0002)
INC A
LD (0002), A
Instructions: 3+1+3 = 7 bytes.
Time: 9 M-cycles (30 clocks).

There is no INC (nn) instruction (read-modify-write).  With more side-effects, we could also do:

Code: [Select]
LD HL, 0002
INC (HL)
Instructions: 3+1 = 4 bytes.
Time: 6 M-cycles (21 clocks).

Huh, that's a typo.  Z80 CPU User's Manual UM008005-0205, page 102, M cycles is written as 2, but it's actually 3.

So the x86 is fairly indifferent, but this shouldn't be surprising for a quirky CISC like that; and Z80 is more comfortable with pointer arithmetic than (load-store at immediate address).

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #50 on: March 01, 2017, 09:16:39 am »
Remind me, is it possible to "cast away constness" now?
Yes, and it has been the case since the very first standard, so I'm not sure why you added the word "now".

Because the committee discussed/argued about that for at least a year.

Now, if you can cast away constness, how can you guarantee that code designed+compiled+optimised assuming constness will continue to function correctly alongside code compiled optimised assuming it can ignore that specification.

Quote
Quote
Quote
I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.

I don't see how that is relevant to anything I wrote.

Quote
the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered

Unless you literally meant someone wrote a C++ compiler, didn't write code to support templates, and it turned out to magically support templates? (this would be equivalent to your multimeter and soldering example)

While templates were being "designed" the language designers didn't realise their construction had become so complex that it had become Turing complete, and even refused to believe people that tried to point it out. It was only when someone took that compiler (multimeter), wrote a small valid program (measured voltage), and demonstrated that the compilation process emitted the sequence of primes (soldered wires), that the language designers woke up and smelled the coffee. I remember this emerging; see https://en.wikibooks.org/wiki/C%2B%2B_Programming/Templates/Template_Meta-Programming#History_of_TMP and http://www.erwin-unruh.de/primorig.html

If the language designers didn't understand their creation, what chance to expert programmers have (let alone normal programmers). We don't live in a "Lake Wobegone world" where all children are above average! The need for language lawyers in ordinary programming situations is a damning indictment.

IMNSHO it is a very bad starting point to use a language where it is provably impossible to determine whether a valid program will even complete compilation!
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline tatus1969

  • Super Contributor
  • ***
  • Posts: 1273
  • Country: de
  • Resistance is futile - We Are The Watt.
    • keenlab
Re: All variables should be Global
« Reply #51 on: March 01, 2017, 09:41:30 am »
browsed through the comments and did not see one hard reason why the scope of each and every variable should be as short as possible:

When you make everything global, then the compiler has to allocate enough memory to hold all of them simultaneously. But if you restrict yourself to making only those global that need it (need to be static, or need to be accessed from multiple modules), and make all others function ("dynamic") scope, then you will have less total memory consumption in most cases.

I am assuming that all dynamic variables are allocated on the stack and freed again after use (valid for most architectures). I also assume that the application has more than one call nesting "branch". Imagine that you call one branch from main(). The machine executes it and eventually reaches the deepest nesting level. At this time, all dynamic variables for this branch are present on the stack. The machine continues to work through that code and eventually returns back to main(). It then has deallocated again all these dynamic variables from the stack. Imagine that it then jumps into and down another branch, again allocating dynamic variables on the stack. This is the important moment: all the new variables share the same memory as the others from the first branch.

Generally spoken, the maximum stack space required is determined by the one of all the independent branches, that has the largest set of dynamic variables. Plus overhead from parameter / return value passing and return address storage. Modern compilers can calculate this value, which gives you the ability to set the stack size right.
« Last Edit: March 01, 2017, 09:43:30 am by tatus1969 »
We Are The Watt - Resistance Is Futile!
 

Offline Kjelt

  • Super Contributor
  • ***
  • Posts: 6460
  • Country: nl
Re: All variables should be Global
« Reply #52 on: March 01, 2017, 09:51:11 am »
A lot of good arguments already given, going to add two:

- Some processor architectures like the STM8 have a "zero page" RAM of 128 bytes or so that are accessible in a single cycle, all other RAM locations are two or more cycles.
  So let the compiler use this fast RAM for where it is best used: local variables in for or while loops.

- Security: Global variables for all possible variables are a hackers heaven.
 

Offline mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13742
  • Country: gb
    • Mike's Electric Stuff
Re: All variables should be Global
« Reply #53 on: March 01, 2017, 09:51:21 am »
browsed through the comments and did not see one hard reason why the scope of each and every variable should be as short as possible:

When you make everything global, then the compiler has to allocate enough memory to hold all of them simultaneously. But if you restrict yourself to making only those global that need it (need to be static, or need to be accessed from multiple modules), and make all others function ("dynamic") scope, then you will have less total memory consumption in most cases.

I am assuming that all dynamic variables are allocated on the stack and freed again after use (valid for most architectures). I also assume that the application has more than one call nesting "branch". Imagine that you call one branch from main(). The machine executes it and eventually reaches the deepest nesting level. At this time, all dynamic variables for this branch are present on the stack. The machine continues to work through that code and eventually returns back to main(). It then has deallocated again all these dynamic variables from the stack. Imagine that it then jumps into and down another branch, again allocating dynamic variables on the stack. This is the important moment: all the new variables share the same memory as the others from the first branch.

Generally spoken, the maximum stack space required is determined by the one of all the independent branches, that has the largest set of dynamic variables. Plus overhead from parameter / return value passing and return address storage. Modern compilers can calculate this value, which gives you the ability to set the stack size right.
I think that in some cases, e.g. PIC8, locals are statically allocated in a pool, the size being determined by the call tree, so the memory usage will only be that needed by the deepest call path. The compiler can figure out how to safely re-use memory that can't be used by multiple procedures at the same time.
This is probably going to be more efficient than using a stack, especially on small systems, as all the addresses are fixed at compile time, and avoids the uncertainty of knowing whether the stack allocation is big enough.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Offline forrestc

  • Supporter
  • ****
  • Posts: 653
  • Country: us
Re: All variables should be Global
« Reply #54 on: March 01, 2017, 10:27:01 am »
I think that in some cases, e.g. PIC8, locals are statically allocated in a pool, the size being determined by the call tree, so the memory usage will only be that needed by the deepest call path. The compiler can figure out how to safely re-use memory that can't be used by multiple procedures at the same time.
This is probably going to be more efficient than using a stack, especially on small systems, as all the addresses are fixed at compile time, and avoids the uncertainty of knowing whether the stack allocation is big enough.

This is exactly what I was going to say, more or less.

By declaring locals, the compiler is able to better understand the scope of the variable usage, and re-use the storage space accordingly.   In a call tree with no recursion, variables can be statically allocated in memory and the space can be reused across functions which are never active at the same time (i.e. are in different parts of the call tree) - hope that was clear.

With recursion, the compiler can switch to a stack-based memory model for those functions which are in the recursive part of the call tree.

One other related point is optimizations for speed vs size vs storage space.   These should be easier with local variables, as the compiler is free to use registers where appropriate, and memory where appropriate.



 

Offline madires

  • Super Contributor
  • ***
  • Posts: 7756
  • Country: de
  • A qualified hobbyist ;)
Re: All variables should be Global
« Reply #55 on: March 01, 2017, 10:42:42 am »
Another thing to consider when using global variables is the way you organize and access the variables. The best method depends on the addressing commands of the MCU.
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #56 on: March 01, 2017, 11:24:39 am »
Remind me, is it possible to "cast away constness" now?
Yes, and it has been the case since the very first standard, so I'm not sure why you added the word "now".

Because the committee discussed/argued about that for at least a year.

Now, if you can cast away constness, how can you guarantee that code designed+compiled+optimised assuming constness will continue to function correctly alongside code compiled optimised assuming it can ignore that specification.


I'm glad they spent a lot of time debating a feature that is obviously controversial.

const_cast is one of those things that shouldn't be used unless you know exactly what you are doing - most reference materials make that clear. It's for casting away const-ness in a const reference type when you know the object it refers to wasn't const to begin with. There are a few situations where it's useful. Casting const-ness away when the object was actually const results in undefined behaviour, as you would expect.

Quote
Quote
Quote
Quote
I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.

I don't see how that is relevant to anything I wrote.

Quote
the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered

Unless you literally meant someone wrote a C++ compiler, didn't write code to support templates, and it turned out to magically support templates? (this would be equivalent to your multimeter and soldering example)

While templates were being "designed" the language designers didn't realise their construction had become so complex that it had become Turing complete, and even refused to believe people that tried to point it out. It was only when someone took that compiler (multimeter), wrote a small valid program (measured voltage), and demonstrated that the compilation process emitted the sequence of primes (soldered wires), that the language designers woke up and smelled the coffee. I remember this emerging; see https://en.wikibooks.org/wiki/C%2B%2B_Programming/Templates/Template_Meta-Programming#History_of_TMP and http://www.erwin-unruh.de/primorig.html

If the language designers didn't understand their creation, what chance to expert programmers have (let alone normal programmers). We don't live in a "Lake Wobegone world" where all children are above average! The need for language lawyers in ordinary programming situations is a damning indictment.

IMNSHO it is a very bad starting point to use a language where it is provably impossible to determine whether a valid program will even complete compilation!

That's really not the same at all. They didn't "discover" a new feature. They "discovered" a new use for a feature they designed. It would be equivalent to figuring out that you can use multimeters' sample and hold capacitors to display CPU utilization of a microcontroller by having the microcontroller turn a GPIO on in the active part of an event loop, and off when sleeping. It's cool, but of little practical significance. People discover new ways to use existing things all the time, and I wouldn't say that's a sign of bad design. I would say it's a sign of good design when mechanisms turn out to be more capable than the designers imagined.
 

Offline DJohn

  • Regular Contributor
  • *
  • Posts: 103
  • Country: gb
Re: All variables should be Global
« Reply #57 on: March 01, 2017, 12:44:12 pm »
There's no performance difference in this case. Structs (of sizes larger than the largest int type usually) are always passed by pointers anyways (copies are made by caller if necessary).

This is roughly true for parameters passed by value (it's very ABI-dependant).

Indeed.  And sometimes it's not even the ABI.  I've worked on a platform on which small structs passed by value would be passed in registers, despite the ABI saying they must go on the stack.  The compiler knew that the callee would not be placed in a library (which would have to use the official ABI), and could use its own calling convention.

In the case of the non-inlined pass-by-global example on PIC32, it's not necessarily just the extra store and load instructions that are making it so much slower.  That code will be storing data to memory, jumping to the function, then immediately loading the data back.  What actually happens is that the store happens, then while the data is making its way to the cache (or to main memory if you're unlucky), the jump and load instructions will start. The load will recognise the data dependency and stall until the load has finished.  It will then start loading (hopefully from cache this time).  But then the next instruction tries to use the data, and stalls again.

Caches are fast, but their latency can still be surprisingly high.  That's the dirty secret of CPU design: high latency is often the price you pay for high throughput.

On 8 bit processors with no cache and few registers, things will of course be completely different.  If you care about performance, there is no substitute for knowing your processor and knowing your compiler.  Read the disassembly.  And trust only the profiler.
 

Offline tatus1969

  • Super Contributor
  • ***
  • Posts: 1273
  • Country: de
  • Resistance is futile - We Are The Watt.
    • keenlab
Re: All variables should be Global
« Reply #58 on: March 01, 2017, 12:49:06 pm »
I think that in some cases, e.g. PIC8, locals are statically allocated in a pool, the size being determined by the call tree, so the memory usage will only be that needed by the deepest call path. The compiler can figure out how to safely re-use memory that can't be used by multiple procedures at the same time.
This is probably going to be more efficient than using a stack, especially on small systems, as all the addresses are fixed at compile time, and avoids the uncertainty of knowing whether the stack allocation is big enough.
agree, the 8051 is another candidate from which I know this. These architectures do not provide efficient [indirect + offset] addressing schemes, so the compiler designers had to go that route. I like that strategy, but it adds complexity because only the linker can do that optimization, and it needs to know all dependencies. And that can become a sever limitation of it. As soon as you start using function pointers that are calculated at runtime, it really depends on the compiler/linker cleverness to "see" that dependency. I went into this problem long ago and it really tooke me a lot of time to detect it.
We Are The Watt - Resistance Is Futile!
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #59 on: March 01, 2017, 01:37:14 pm »
Remind me, is it possible to "cast away constness" now?
Yes, and it has been the case since the very first standard, so I'm not sure why you added the word "now".

Because the committee discussed/argued about that for at least a year.

Now, if you can cast away constness, how can you guarantee that code designed+compiled+optimised assuming constness will continue to function correctly alongside code compiled optimised assuming it can ignore that specification.

I'm glad they spent a lot of time debating a feature that is obviously controversial.

const_cast is one of those things that shouldn't be used unless you know exactly what you are doing - most reference materials make that clear. It's for casting away const-ness in a const reference type when you know the object it refers to wasn't const to begin with. There are a few situations where it's useful. Casting const-ness away when the object was actually const results in undefined behaviour, as you would expect.

Just so. The problem is that any observed effect can be subtle and is not necessarily visible in the source code, since it is a property of the source code, the compiler version, the compiler flags, the specific processor, and also the data. While one might accept such "random failures" in a generic application, they are often not acceptable in embedded systems, especially anything related to safety.

It is insufficient to simply say "you have to use the tool correctly" when in practice it is very difficult to ensure/check that it is used "correctly", and there are alternative tools where no such problem exists.

Quote
Quote
Quote
Quote
Quote
I'm not sure what the source of a feature has to do with anything. I evaluate language features by their merit. If a good feature was proposed by a random ganster on the streets, I would want it to be added, too.

I don't see how that is relevant to anything I wrote.

Quote
the template language "features" you mention, because they were discovered and the design committee did not believe it until someone rubbed their noses in it. I prefer my tools to be designed, not discovered

Unless you literally meant someone wrote a C++ compiler, didn't write code to support templates, and it turned out to magically support templates? (this would be equivalent to your multimeter and soldering example)

While templates were being "designed" the language designers didn't realise their construction had become so complex that it had become Turing complete, and even refused to believe people that tried to point it out. It was only when someone took that compiler (multimeter), wrote a small valid program (measured voltage), and demonstrated that the compilation process emitted the sequence of primes (soldered wires), that the language designers woke up and smelled the coffee. I remember this emerging; see https://en.wikibooks.org/wiki/C%2B%2B_Programming/Templates/Template_Meta-Programming#History_of_TMP and http://www.erwin-unruh.de/primorig.html

If the language designers didn't understand their creation, what chance to expert programmers have (let alone normal programmers). We don't live in a "Lake Wobegone world" where all children are above average! The need for language lawyers in ordinary programming situations is a damning indictment.

IMNSHO it is a very bad starting point to use a language where it is provably impossible to determine whether a valid program will even complete compilation!

That's really not the same at all. They didn't "discover" a new feature. They "discovered" a new use for a feature they designed.

It is more than that. Initially the language designers refused to accept that it was Turing complete until someone rubbed their noses in it and they could no longer wish/pretend the "feature" didn't exist.

Quote
It would be equivalent to figuring out that you can use multimeters' sample and hold capacitors to display CPU utilization of a microcontroller by having the microcontroller turn a GPIO on in the active part of an event loop, and off when sleeping. It's cool, but of little practical significance. People discover new ways to use existing things all the time, and I wouldn't say that's a sign of bad design. I would say it's a sign of good design when mechanisms turn out to be more capable than the designers imagined.

That's different. It is entirely acceptable and desirable that tool designers don't anticipate all the uses for their tool. It is unacceptable that the designers don't understand their own tool.

Do you think it is beneficial that valid programs can fail to complete compilation?
Do you think it is beneficial that you cannot tell whether valid programs can be compiled?
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline bookaboo

  • Frequent Contributor
  • **
  • Posts: 727
  • Country: ie
Re: All variables should be Global
« Reply #60 on: March 01, 2017, 01:49:20 pm »
Scrolled through most of this and didn't see one of the major reasons listed yet and that's portability. If you write a function with a few arguments and returning a value it's usually a cut/paste job to port it to another project. Whereas if you use globals you need to make sure there's no clash in variable names between new and ported code.
It's also more readable and easier to debug.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: All variables should be Global
« Reply #61 on: March 01, 2017, 02:16:56 pm »
While templates were being "designed" the language designers didn't realise their construction had become so complex that it had become Turing complete, and even refused to believe people that tried to point it out.

C++ templates are far from being the only thing that has accidentally turned out to be Turing complete.

Some other examples:

- Java generics. Opps! They even knew about C++ templates in advance!

- x86 MMU fault handling

- Magic, The Gathering

- HTML5+CSS

http://beza1e1.tuxen.de/articles/accidentally_turing_complete.html

Also: vi key macros https://github.com/divVerent/vi-turing

Also: x86, even without using any registers (not even the condition code register and branches) http://mainisusuallyafunction.blogspot.ru/2014/02/x86-is-turing-complete-with-no-registers.html
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #62 on: March 01, 2017, 03:08:51 pm »
While templates were being "designed" the language designers didn't realise their construction had become so complex that it had become Turing complete, and even refused to believe people that tried to point it out.
C++ templates are far from being the only thing that has accidentally turned out to be Turing complete.
Some other examples:
- Java generics. Opps! They even knew about C++ templates in advance!

I'm not a fan of Java's generics, but I haven't seen any solid evidence they are accidentally Turing complete, let alone that the designers (initially) denied that. Mind you, I haven't looked.

Quote
- x86 MMU fault handling

Surprising, but not something that a developer could invoke using the tools they use as part of their day-to-day job.

Quote
- Magic, The Gathering
- HTML5+CSS

I don't think Magic's objective is to develop programs!

As for HTML5+CSS, it doesn't surprise me since that whole area is a mutated mess! Given the net and graphic designers are involved, I'm not sure anyone would notice grossly slow and abnormal behaviour :)

Quote
http://beza1e1.tuxen.de/articles/accidentally_turing_complete.html

Asserts rather than proves accidental Turing Completeness.

Quote
Also: vi key macros https://github.com/divVerent/vi-turing

Also: x86, even without using any registers (not even the condition code register and branches) http://mainisusuallyafunction.blogspot.ru/2014/02/x86-is-turing-complete-with-no-registers.html

Sure; unsurprising.

But it remains that the baroque complexity of C++ baffled even the language designers. That's indicative of something that is out of control.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline @rtTopic starter

  • Super Contributor
  • ***
  • Posts: 1059
Re: All variables should be Global
« Reply #63 on: March 01, 2017, 03:20:00 pm »
Where a function setpixel() is used most in a graphics library, no matter whether drawing lines or circles,
and no matter their arguments, setpixel is going to be called the most by those functions, so there should be a difference
that could be measured by only changing that function to pass arguments setpixel(int x, int y);

Then if you drew a whole bunch of lines and circles, and measured the time with hardware timer,
is just that one change to code a fair measurement of anything?
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #64 on: March 01, 2017, 03:26:47 pm »
Just so. The problem is that any observed effect can be subtle and is not necessarily visible in the source code, since it is a property of the source code, the compiler version, the compiler flags, the specific processor, and also the data. While one might accept such "random failures" in a generic application, they are often not acceptable in embedded systems, especially anything related to safety.

It is insufficient to simply say "you have to use the tool correctly" when in practice it is very difficult to ensure/check that it is used "correctly", and there are alternative tools where no such problem exists.

It does not depend on the compiler, hardware, or data at all. Correctly written code will work with any correctly written compiler.

If you cannot guarantee that the object is not const, you cannot use const_cast. Practically, that means const_cast cannot be used except in very specific cases. The only such specific case I have seen is to provide an overloaded accessor function that returns a const reference if "this" pointer is const, and a non-const reference if it isn't. It saves code duplication in this case, and the behaviour is completely defined, and will work with any standard-compliant compiler. This is the only use of const_cast I have seen in high quality code.

Code: [Select]
class A {
public:
  const int& f() const {
    /* do a bunch of stuff */
    return x;
  }

  int& f() {
    const &A const_this = *this;
    return const_cast<const A*>(const_this->GetX());
  }

private:
  int x;
}

Yes, it's convoluted, and shouldn't be used unless you know exactly what you are doing.

This problem doesn't exist in alternative tools, because alternative tools don't allow you to do this at all. You have to write two different functions if you want a const and a non-const version. You can do that in C++ too if you want (and people who don't know how const_cast works should).

Java solves it by not supporting const (final) references. You can disallow assigning another object to the reference, but not make the object itself final. If you don't use const references in C++ (which makes things safer, without adding any overhead), you won't have this problem either.

Quote
That's different. It is entirely acceptable and desirable that tool designers don't anticipate all the uses for their tool. It is unacceptable that the designers don't understand their own tool.
Even if that's true, it's something that happened decades ago. Again, I evaluate language features by their merit, and not by where/how it came about.

Quote
Do you think it is beneficial that valid programs can fail to complete compilation?
Do you think it is beneficial that you cannot tell whether valid programs can be compiled?
No, but I think it is beneficial to have a powerful meta-programming system that allows people to do things like compile-time optimization of expressions in a linear algebra library that is not possible in languages without a meta-programming system (http://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html).

If you have a powerful meta-programming system, those properties are unavoidable side effects.

And I think the fact that you can abuse it in a way to write a valid program that fails to compile is an acceptable downside to that, because I am not going to do that.
 

Offline TNorthover

  • Contributor
  • Posts: 42
  • Country: us
Re: All variables should be Global
« Reply #65 on: March 01, 2017, 03:54:57 pm »
Allowing casting constness away is also a necessary for C compatibility. And the template horrors are gradually being reduced by constexpr in newer standards.
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #66 on: March 01, 2017, 04:18:52 pm »
Just so. The problem is that any observed effect can be subtle and is not necessarily visible in the source code, since it is a property of the source code, the compiler version, the compiler flags, the specific processor, and also the data. While one might accept such "random failures" in a generic application, they are often not acceptable in embedded systems, especially anything related to safety.

It is insufficient to simply say "you have to use the tool correctly" when in practice it is very difficult to ensure/check that it is used "correctly", and there are alternative tools where no such problem exists.

It does not depend on the compiler, hardware, or data at all. Correctly written code will work with any correctly written compiler.

Well, doh. That's a trivially true statement since it depends on the definitions of "correct". From listening to echoes of what happened in and after some of the standardisation attempts, that was non-trivial since the committee members disagreed about what some of the wording meant. Without such agreement, any definition of "correct" is moot. And then similar things happened when the commercial  compiler writers asked what sections of the standard meant, because there were, apparently and remarkably, conflicting sections.

I also remember somewhere around 2005 somebody triumphantly announcing the first complete compiler. Now I forget whether it was for C or C++, but since it was 6 years after the standard was fixed, it is plausible that it was for C99. Sorry I can't be more specific.

However, looking at https://en.wikipedia.org/wiki/C99#Implementations it appears that even GCC doesn't fully implement C99 after 15 years!

Of course mere users also have to try and figure out which compilers do/don't "correctly implement" the language - which is an impossible task.

Quote
If you cannot guarantee that the object is not const, you cannot use const_cast. Practically, that means const_cast cannot be used except in very specific cases. The only such specific case I have seen is to provide an overloaded accessor function that returns a const reference if "this" pointer is const, and a non-const reference if it isn't. It saves code duplication in this case, and the behaviour is completely defined, and will work with any standard-compliant compiler. This is the only use of const_cast I have seen in high quality code.

Again, you are saying "in correctly written code you get correct results", carefully avoiding the issue of whether it is practical to assume that all the code in your system is "correct".

Quote
This problem doesn't exist in alternative tools, because alternative tools don't allow you to do this at all. You have to write two different functions if you want a const and a non-const version. You can do that in C++ too if you want (and people who don't know how const_cast works should).

Yes, alternative tools don't allow such things, principally because they have a simpler better foundation where such things aren't necessary.

Quote
Java solves it by not supporting const (final) references. You can disallow assigning another object to the reference, but not make the object itself final. If you don't use const references in C++ (which makes things safer, without adding any overhead), you won't have this problem either.

Comparing with Java isn't helpful in this case, for the above reason.

Quote
Quote
That's different. It is entirely acceptable and desirable that tool designers don't anticipate all the uses for their tool. It is unacceptable that the designers don't understand their own tool.
Even if that's true, it's something that happened decades ago. Again, I evaluate language features by their merit, and not by where/how it came about.

Understanding history is a useful way of determining where skeletons are still concealed, or alternatively of determining that the skeletons have been properly removed.

Quote
Quote
Do you think it is beneficial that valid programs can fail to complete compilation?
Do you think it is beneficial that you cannot tell whether valid programs can be compiled?
No, but I think it is beneficial to have a powerful meta-programming system that allows people to do things like compile-time optimization of expressions in a linear algebra library that is not possible in languages without a meta-programming system (http://eigen.tuxfamily.org/dox/TopicLazyEvaluation.html).

I agree.

But those benefits (and many other) should be embodied either in a library, or in a well-defined special-purpose language - possibly one that is converted to plain-vanilla C/C++ for compilation and execution.

Accidentally including them in a general purpose language is a specification and design smell.

Quote
If you have a powerful meta-programming system, those properties are unavoidable side effects.

And I think the fact that you can abuse it in a way to write a valid program that fails to compile is an acceptable downside to that, because I am not going to do that.

I'm sure you and your compiler always produce correct code. Unfortunately most people don't even realise that they are subtly screwing up - and C/C++ baroque complexity is part of that problem.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 19470
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: All variables should be Global
« Reply #67 on: March 01, 2017, 04:21:49 pm »
Allowing casting constness away is also a necessary for C compatibility. And the template horrors are gradually being reduced by constexpr in newer standards.

Just so. Of course that amelioration presumes that compiler writers and developers will correctly implement and use such features.

Unfortunately there is a mass of important code in the wild that will never be rewritten, so the current problems will remain for the foreseeable future.
There are lies, damned lies, statistics - and ADC/DAC specs.
Glider pilot's aphorism: "there is no substitute for span". Retort: "There is a substitute: skill+imagination. But you can buy span".
Having fun doing more, with less
 

Offline cyberfish

  • Regular Contributor
  • *
  • Posts: 240
  • Country: gb
Re: All variables should be Global
« Reply #68 on: March 02, 2017, 04:33:03 pm »
Well, doh. That's a trivially true statement since it depends on the definitions of "correct". From listening to echoes of what happened in and after some of the standardisation attempts, that was non-trivial since the committee members disagreed about what some of the wording meant. Without such agreement, any definition of "correct" is moot. And then similar things happened when the commercial  compiler writers asked what sections of the standard meant, because there were, apparently and remarkably, conflicting sections.
Yes, standards as big as the C++ standards will inevitably have ambiguity in places. They are to be fixed in future standards. Many things were cleared up in C++11 for example. The C++ standard is not perfect, and that's why it's still being revised.

Quote
I also remember somewhere around 2005 somebody triumphantly announcing the first complete compiler. Now I forget whether it was for C or C++, but since it was 6 years after the standard was fixed, it is plausible that it was for C99. Sorry I can't be more specific.

However, looking at https://en.wikipedia.org/wiki/C99#Implementations it appears that even GCC doesn't fully implement C99 after 15 years!

And then you have languages like Objective-C or Python that don't even have a standard, only a reference implementation. Obviously in this case you'll have a perfect implementation and a perfectly-defined language, by definition! In theory that means all other implementation must also reproduce bugs in the reference implementation.

Quote
Of course mere users also have to try and figure out which compilers do/don't "correctly implement" the language - which is an impossible task.
All compilers for all languages have bugs. Are you claiming otherwise?

Quote
Again, you are saying "in correctly written code you get correct results", carefully avoiding the issue of whether it is practical to assume that all the code in your system is "correct".
Can you assume that all the code in your system involved in Obj-C compilation are correct? If you can, so can I. All C++ standard libraries included in major compilers have been extensively tested, and finding a bug in the field is extremely rare.

Quote
Yes, alternative tools don't allow such things, principally because they have a simpler better foundation where such things aren't necessary.

Like in Objective-C, by basically making const useless?

What, concretely, are you referring to?

Quote
Comparing with Java isn't helpful in this case, for the above reason.
Java can definitely be made safer by having a good const ref implementation.

You have an ArrayList of large objects, and you want to let the caller access one element, read-only, without making any copies. How do you go about doing that?

You can't. You have to either trust the caller to not modify the reference you return, or make a copy.

Quote
Understanding history is a useful way of determining where skeletons are still concealed, or alternatively of determining that the skeletons have been properly removed.
The language is fully defined by the current standard. All the skeletons are listed, no matter where or how they came about.

Quote
I agree.

But those benefits (and many other) should be embodied either in a library, or in a well-defined special-purpose language - possibly one that is converted to plain-vanilla C/C++ for compilation and execution.

Accidentally including them in a general purpose language is a specification and design smell.
Template metaprogramming IS mostly done in libraries (like Eigen). You will rarely see that in application code.

Well, yes, the special-purpose language is the template language. The compiler does convert it to plain-vanilla C++ for compilation.

Would it be better if it was just called a different name and described in a separate document?

Quote
I'm sure you and your compiler always produce correct code. Unfortunately most people don't even realise that they are subtly screwing up - and C/C++ baroque complexity is part of that problem.
Yes, C++ is difficult to learn. I don't think anyone is arguing otherwise.

It's a powerful language that can be used to write very elegant (and at the same time, high performance) code by proficient programmers, but it does require more training than most other languages.

Still, there are at least a million C++ developers in the world better than I am, and I can already use C++ effectively and write more elegant code than I can in any other language. So learning C++ to a very practically useful level is far from impossible.
 

Offline @rtTopic starter

  • Super Contributor
  • ***
  • Posts: 1059
Re: All variables should be Global
« Reply #69 on: March 03, 2017, 06:42:20 am »
This will draw two identical patterns in the same y position.
It would appear the top one will save significant program memory.
For the extra memory, will the second one be faster?

I’m aware you could loop through these values in an array of indexes,
and there are a number of other ways to save memory on it.

Code: [Select]
x = 13; y = 39; setpixel();
x = 114; setpixel(); // global y is already 39.
x = 12; y = 40; setpixel();
x = 115; setpixel();
x = 11; y = 41; setpixel();
x = 116; setpixel();
x = 11; y = 42; setpixel();
x = 116; setpixel();
x = 11; y = 43; setpixel();
x = 116; setpixel();
x = 11; y = 44; setpixel();
x = 116; setpixel();
x = 11; y = 45; setpixel();
x = 116; setpixel();
x = 12; y = 46; setpixel();
x = 115; setpixel();
x = 13; y = 47; setpixel();
x = 114; setpixel();


Code: [Select]
setpixel(13,39);
setpixel(114,39);
setpixel(12,40);
setpixel(115,40);
setpixel(11,41);
setpixel(116,41);
setpixel(11,42);
setpixel(116,42);
setpixel(11,43);
setpixel(116,43);
setpixel(11,44);
setpixel(116,44);
setpixel(11,45);
setpixel(116,45);
setpixel(12,46);
setpixel(115,46);
setpixel(13,47);
setpixel(114,47);
« Last Edit: March 03, 2017, 06:46:43 am by @rt »
 

Offline janekm

  • Supporter
  • ****
  • Posts: 515
  • Country: gb
Re: All variables should be Global
« Reply #70 on: March 03, 2017, 07:10:33 am »
This will draw two identical patterns in the same y position.
It would appear the top one will save significant program memory.
For the extra memory, will the second one be faster?

I’m aware you could loop through these values in an array of indexes,
and there are a number of other ways to save memory on it.

Code: [Select]
x = 13; y = 39; setpixel();
x = 114; setpixel(); // global y is already 39.
x = 12; y = 40; setpixel();
x = 115; setpixel();
x = 11; y = 41; setpixel();
x = 116; setpixel();
x = 11; y = 42; setpixel();
x = 116; setpixel();
x = 11; y = 43; setpixel();
x = 116; setpixel();
x = 11; y = 44; setpixel();
x = 116; setpixel();
x = 11; y = 45; setpixel();
x = 116; setpixel();
x = 12; y = 46; setpixel();
x = 115; setpixel();
x = 13; y = 47; setpixel();
x = 114; setpixel();


Code: [Select]
setpixel(13,39);
setpixel(114,39);
setpixel(12,40);
setpixel(115,40);
setpixel(11,41);
setpixel(116,41);
setpixel(11,42);
setpixel(116,42);
setpixel(11,43);
setpixel(116,43);
setpixel(11,44);
setpixel(116,44);
setpixel(11,45);
setpixel(116,45);
setpixel(12,46);
setpixel(115,46);
setpixel(13,47);
setpixel(114,47);

You seem to be forgetting that optimising compilers exist.
Your first example compiles to the following with ARM gcc 5.4.1 with -Os https://godbolt.org/g/c8O0nw (-O3 is better in this case, but still worse than the version with parameters):
Code: [Select]
setpixel():
        ldr     r2, .L2
        ldr     r0, [r2]
        ldr     r1, [r2, #4]
        mov     r2, #320
        ldr     r3, .L2+4
        ldr     r3, [r3]
        mla     r3, r2, r0, r3
        mvn     r2, #0
        strb    r2, [r3, r1]
        bx      lr
.L2:
        .word   .LANCHOR1
        .word   .LANCHOR0
main:
        stmfd   sp!, {r4, r5, r6, r7, r8, r9, r10, lr}
        mov     r3, #39
        mov     r8, #13
        mov     r7, #114
        ldr     r4, .L6
        mov     r10, #12
        stmia   r4, {r3, r8}
        mov     r9, #115
        bl      setpixel()
        str     r7, [r4, #4]
        bl      setpixel()
        mov     r3, #40
        mov     r6, #11
        mov     r5, #116
        stmia   r4, {r3, r10}
        bl      setpixel()
        str     r9, [r4, #4]
        bl      setpixel()
        mov     r3, #41
        stmia   r4, {r3, r6}
        bl      setpixel()
        str     r5, [r4, #4]
        bl      setpixel()
        mov     r3, #42
        str     r6, [r4, #4]
        str     r3, [r4]
        bl      setpixel()
        str     r5, [r4, #4]
        bl      setpixel()
        mov     r3, #43
        str     r6, [r4, #4]
        str     r3, [r4]
        bl      setpixel()
        str     r5, [r4, #4]
        bl      setpixel()
        mov     r3, #44
        str     r6, [r4, #4]
        str     r3, [r4]
        bl      setpixel()
        str     r5, [r4, #4]
        bl      setpixel()
        mov     r3, #45
        str     r6, [r4, #4]
        str     r3, [r4]
        bl      setpixel()
        str     r5, [r4, #4]
        bl      setpixel()
        mov     r3, #46
        str     r10, [r4, #4]
        str     r3, [r4]
        bl      setpixel()
        str     r9, [r4, #4]
        bl      setpixel()
        mov     r3, #47
        str     r8, [r4, #4]
        str     r3, [r4]
        bl      setpixel()
        str     r7, [r4, #4]
        bl      setpixel()
        mov     r0, #0
        ldmfd   sp!, {r4, r5, r6, r7, r8, r9, r10, lr}
        bx      lr
.L6:
        .word   .LANCHOR1
mem:
        .word   1073745920
y:
x:

Your second example becomes this with the same options https://godbolt.org/g/O4qx6Z :
Code: [Select]
setpixel(int, int):
        ldr     r3, .L2
        ldr     r2, [r3]
        mov     r3, #320
        mla     r1, r3, r1, r2
        mvn     r3, #0
        strb    r3, [r1, r0]
        bx      lr
.L2:
        .word   .LANCHOR0
main:
        mvn     r2, #0
        ldr     r3, .L5
        ldr     r3, [r3]
        add     r3, r3, #12288
        strb    r2, [r3, #205]
        strb    r2, [r3, #306]
        strb    r2, [r3, #524]
        strb    r2, [r3, #627]
        strb    r2, [r3, #843]
        strb    r2, [r3, #948]
        strb    r2, [r3, #1163]
        strb    r2, [r3, #1268]
        strb    r2, [r3, #1483]
        strb    r2, [r3, #1588]
        strb    r2, [r3, #1803]
        strb    r2, [r3, #1908]
        strb    r2, [r3, #2123]
        strb    r2, [r3, #2228]
        strb    r2, [r3, #2444]
        strb    r2, [r3, #2547]
        mov     r0, #0
        bx      lr
.L5:
        .word   .LANCHOR0
mem:
        .word   1073745920

 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: All variables should be Global
« Reply #71 on: March 03, 2017, 08:27:43 am »
Brilliant!!
 

Offline Bruce Abbott

  • Frequent Contributor
  • **
  • Posts: 627
  • Country: nz
    • Bruce Abbott's R/C Models and Electronics
Re: All variables should be Global
« Reply #72 on: March 03, 2017, 09:34:23 pm »
some guy with a suit & tie said: “Variables should be private unless global in their nature”.
He was right. The purpose of using a HLL is so the programmer can concentrate on program structure and let the compiler worry about implementation.

Quote
Code: [Select]
x = 13; y = 39; setpixel();
x = 114; setpixel(); // global y is already 39
...
In this trivial example it may be more efficient to do setpixel(x,y) but it could be a different story if the coordinates are variable.

Write code that reflects what you are trying to do, not contortions that you think might help the compiler to do a better job. If you want to display a fixed array of pixels then write appropriate code to do that. Hard-coding an image into a sequence of instructions is just awful programming style, whatever language you use.

If at the end you find that the compiled code is too inefficient, then consider what to do about it. But don't create a hack unless you are willing to accept the consequences (obtuse and unmaintainable code, lack of portability, compiler-specific results etc.). Most modern CPUs are optimized for C, and modern compilers are pretty good at optimizing away the 'fluff'. If you need to squeeze the absolute maximum out of a low-end chip and the compiler isn't up to it then program in assembler - then you will have complete control!   
 
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21658
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: All variables should be Global
« Reply #73 on: March 04, 2017, 01:43:31 am »
Here's an optimization you probably won't get from the compiler: read it as an array.

In both above examples, the values are used in sequence, and not assigned to any memory (beyond a single pair of local variables, or registers).  That incurs a lot of MOV reg,imm instructions, which usually aren't very efficient: you have to encode the instruction itself, plus the data.

If instead we put the values in an array (usually a const array), then they can be "streamed" out with an auto-increment pointer (if such operations are available on the platform, that is).  So it turns into:
Code: [Select]
MOV ptr,&points_array
cycle:
MOV reg1,[ptr+]
MOV reg2,[ptr+]
CALL PlotPoint
LOOP cycle

Rolling things up into a loop saves code space, and the array can be inlined with code (Von Neumann architecture) or stuffed away in the data segment.  It probably executes slower, though -- loops being what they are.

On Harvard architectures, this saves space nicely.  AVR for example.  Loading registers is one thing, but having the compiler do it is doubly worse: C only knows how to read and write from SRAM.  You have to go out of your way to implement a const array in program memory, and you can't just pass it around, you have to use stub functions to read it into SRAM.  Whereas you can write something like,
Code: [Select]
movw Z,offset(points_array) << 1
cycle:
lpm r16, z+
lpm r17, z+
rcall PlotPoint
brne cycle

Takes up 6 words of PROGMEM, and only one byte (half word) per uint8_t sized parameter (two byte minimum).

Of course making the optimization of in-register parameter passing, for both cases.  Pushing and popping things only adds overhead, but it's a constant adder for both examples.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline Feynman

  • Regular Contributor
  • *
  • Posts: 192
  • Country: ch
Re: All variables should be Global
« Reply #74 on: March 04, 2017, 08:43:08 am »
I know the obvious reason of the Human programmer making a mess, but is there a real reason?
Why shouldn't "not making a mess" be a real reason? That's probably the most siginificant reason as a professional.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: All variables should be Global
« Reply #75 on: March 04, 2017, 01:01:17 pm »
Here's an optimization you probably won't get from the compiler: read it as an array.
Compilers use pointer arithmetic all the time. They're not made as dumb as you think.
They can even re-order non volatile statements to fit the arithmetic.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4028
  • Country: nz
Re: All variables should be Global
« Reply #76 on: March 04, 2017, 01:12:21 pm »
Here's an optimization you probably won't get from the compiler: read it as an array.
Compilers use pointer arithmetic all the time. They're not made as dumb as you think.
They can even re-order non volatile statements to fit the arithmetic.

I've never seen a compiler take a series of hand-written statements, identical except for the literals, and extract the literals into an array and use a loop. And I write compiler optimizations for a living :-)

You *could* do it. It's just the inverse of unrolling, which compilers do all the time. But it would be quite expensive to find, and would only very seldom find code on which it could do it. And then you just made the program slower, so it's only something you'd do when optimising for size, which is also not the common case.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 4078
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: All variables should be Global
« Reply #77 on: March 04, 2017, 02:09:28 pm »
I was looking in some 8051 listing. Code fetches a pointer to a global.
Next 5 different globals are accessed by adding to the pointer.

It doesn't put them in a loop, but it skips the pointer fetch.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21658
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: All variables should be Global
« Reply #78 on: March 04, 2017, 03:25:37 pm »
Here's an optimization you probably won't get from the compiler: read it as an array.
Compilers use pointer arithmetic all the time. They're not made as dumb as you think.
They can even re-order non volatile statements to fit the arithmetic.

It's quite standard to use pointer + offset to refer to a stack frame, for example.  That's easy to set up, though: the variables are allocated in specific locations, they can be used independently -- and the offset can be called out independently, thanks to instructions like MOV reg,[reg+imm].

It's not obvious that a compiler would know how to resolve that into an array access: one where the pointer itself is moving.  That would complicate other stack accesses, at the very least: if you use one MOV reg,[reg++] instruction, now all subsequent offsets have to be one less, and so on.  If it's inside a loop, the offsets vary from time to time, and you can't make that assumption at all.  Then you need to use a new pointer register (which is available on many platforms, at least).

I don't know that compilers will ever create a loop to optimize for size; speed is the most common optimization after all, and it's nice that unrolling a loop is a much more well-defined problem than rolling one up is!

Bruce seems to have confirmed my suspicion, so at least I'm not completely wrong for once. :)

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf