Author Topic: C++ for embedded: how to learn it in 2021?  (Read 22792 times)

0 Members and 5 Guests are viewing this topic.

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: C++ for embedded: how to learn it in 2021?
« Reply #50 on: December 16, 2021, 10:48:38 am »
Complete nonsense. Many languages have that capability; the first of many was designed in the 1950s!
Fine, the only low level one.
Lisp or whatever other brainfsck don't count :P

How about Python? Anything containing a REPL or reflection can do it, e.g. Smalltalk, Java, Forth, Perl, Ruby, many special purpose languages, and apparently C#.

Quote
People are moving away from C++ for reasons of security, amongst things. Even the Linux kernel will soon have Rust in it for those and other reasons.
If you can move off of C++ you probably didn't need it in the first place.

Regarding it being a mess, no disagreement.

Indeed, which is why there is little value to learning C++ if you already know C. Better to choose a clean/modern 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 tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: C++ for embedded: how to learn it in 2021?
« Reply #51 on: December 16, 2021, 10:59:46 am »
So, are you suggesting learning Rust instead of C++?
It's tggzzz, not me. And has indeed already done exactly that yesterday.

I'm not familiar with Rust but what I can say is that I haven't seen them produce a decent migration guide for C/C++ developers and trying to learn the language from their "tutorials" is pain because they are written with little explanation of how the various constructs are implemented and how they relate to established languages.

There isn't any need for a C++->Rust migration guide. Nobody would migrate C++ to Rust!

I don't recognise your point about the tutorials. I don't understand "how the constructs are implemented", and their relationship to other languages is either obvious or is irrelevant.

Having said that, Gosling's original Java Whitepaper was wonderful: it explained why each capability had been chosen, its pedigree, and why they all fitted together harmoniously. (The last would be impossible for C++ because its objective was to be all things to all people.)

The Rust tutorials aren't as good, but they are good enough for any programmer whose mind hasn't been crippled by only learning C++ (with a nod to Dijkstra and COBOL!).
« Last Edit: December 16, 2021, 11:01:25 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 magic

  • Super Contributor
  • ***
  • Posts: 8062
  • Country: pl
Re: C++ for embedded: how to learn it in 2021?
« Reply #52 on: December 16, 2021, 11:26:35 am »
Python is not low level and doesn't generate machine code ;)

VM languages with reflection are a more interesting example because they provide something that cannot be replicated in C without a 3rd party JIT library.
But using reflection to generate and compile a few functions ahead of time wouldn't be as convenient as templates.

There isn't any need for a C++->Rust migration guide. Nobody would migrate C++ to Rust!
I mean migrating C and C++ developers, not code. That's something they just can't stop talking about.

You ask about relevance, so let's look at something related to this thread, generics and traits.
https://doc.rust-lang.org/book/ch10-00-generics.html

Simple question: are they static dispatch like C++ templates or dynamic dispatch like Java interfaces? How much code is generated when I define trait Comparable, implement it for a few types and write a generic btree_insert working with that?
Answer: the word dispatch doesn't even appear in this chapter, but the word "tweet" does and we also get to learn how to find the largest number in a vector.

That's what I'm talking about.

And the true answer is: I found some blog post somewhere explaining that Rust generics use static dispatch but there are ways to get dynamic dispatch with traits as well.
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: C++ for embedded: how to learn it in 2021?
« Reply #53 on: December 16, 2021, 01:14:37 pm »
Python is not low level and doesn't generate machine code ;)

Neither does C/C++! In superscalar processors the output of the compilers is internally converted into many instructions. Yes, in effect "add ax, bx" is interpreted by the hardware.

The processor manufacturers keep that internal instruction set a closely guarded secret, and it changes over time.


Quote
VM languages with reflection are a more interesting example because they provide something that cannot be replicated in C without a 3rd party JIT library.
But using reflection to generate and compile a few functions ahead of time wouldn't be as convenient as templates.

Reflection is a dangerous tool, and too many people will use it where there are simpler and better alternatives.

One example of the danger is that in Java it is possible to cause the rest of the VM to "think" that Integer(2) has the same numerical value as int 3, so Integer(2)+Integer(10)=Integer(13). Let's see if the unit tests detect that, ho ho ho :)

Quote
There isn't any need for a C++->Rust migration guide. Nobody would migrate C++ to Rust!
I mean migrating C and C++ developers, not code. That's something they just can't stop talking about.

That seems more sensible :)

Quote
You ask about relevance, so let's look at something related to this thread, generics and traits.
https://doc.rust-lang.org/book/ch10-00-generics.html

Simple question: are they static dispatch like C++ templates or dynamic dispatch like Java interfaces? How much code is generated when I define trait Comparable, implement it for a few types and write a generic btree_insert working with that?
Answer: the word dispatch doesn't even appear in this chapter, but the word "tweet" does and we also get to learn how to find the largest number in a vector.

That's what I'm talking about.

And the true answer is: I found some blog post somewhere explaining that Rust generics use static dispatch but there are ways to get dynamic dispatch with traits as well.

Any programmer that isn't familiar with multiple languages is not an engineer, by definition! I would expect that it would be relatively simple for a C++ programmer with basic competence to migrate themselves to Rust - certainly easier than the reverse.

I wouldn't expect a language tutorial to go into that level of depth because it could well be implementation dependent.

Given that it is probable that Rust will be in the linux kernel (probably drivers initially), I would presume there are no major inefficiencies compared with C. Indeed, the clarifications/simplifications inherent in Rust can be expected to enable the compiler to avoid having to make pessimistic assumptions.

Short term the biggest issue appears to be that Rust compiler is based on LLVM, not gcc. But that's true of many new languages, for reasons that bore me :)
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 DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 5098
  • Country: gb
Re: C++ for embedded: how to learn it in 2021?
« Reply #54 on: December 16, 2021, 04:32:18 pm »
This is the previous cpu-board of the sewing machine.
The new one is based on PowerPC e500.


The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17798
  • Country: fr
Re: C++ for embedded: how to learn it in 2021?
« Reply #55 on: December 16, 2021, 05:42:49 pm »
In C, the typical approach would be to use pointers to "keys". The keys could be objects defined as structures holding either directly the information needed for the key, or a pointer to it, and then one or more functions acting on the key.

This might look like a pretty "manual" way of doing OO, but in practice, when done properly, it's not that much different nor even that many more lines of code. I see practically no difference in the number of lines of code needed for either approach.

I did it in C for production and it works very well, but its backstage was a pretty masochistic experience, I am somehow proud of my code because it also looks shiny and nice, but not so happy because it took me 3 months from the draft to the official commit.

Sure, but again, what makes you think it would have taken less time using C++? Actually, since you do not master C++ as far as I've understood, between learning it and using it efficiently to implement this, I bet it would have taken you more time.

But do not take my word for it. Since you seem to be willing to give it a try, just do it! Try reimplementing this in C++ and see what you get.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17798
  • Country: fr
Re: C++ for embedded: how to learn it in 2021?
« Reply #56 on: December 16, 2021, 05:44:58 pm »
Oh uh, I usually agree with tggzzz on programming topics. Except about Rust. But hey, let's not pollute this thread! :-DD
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: C++ for embedded: how to learn it in 2021?
« Reply #57 on: December 16, 2021, 05:54:26 pm »
Oh uh, I usually agree with tggzzz on programming topics. Except about Rust. But hey, let's not pollute this thread! :-DD

FWIW I'm not a Rust fanbois; I haven't yet seriously "kicked its tyres".

Competent people that have "kicked its tyres" seem to believe that it has sufficient characteristics to be a useful improvement on the existing languages/tools. That makes it worthy of attention.

The same can be said for a few other languages for different application domains.
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 ve7xen

  • Super Contributor
  • ***
  • Posts: 1221
  • Country: ca
    • VE7XEN Blog
Re: C++ for embedded: how to learn it in 2021?
« Reply #58 on: December 16, 2021, 07:30:39 pm »
C++ doesn't make compile time computation as easy to express as runtime computation.

I would argue that with the advent of constexpr, it actually does, at least since C++14's improvements.
73 de VE7XEN
He/Him
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17798
  • Country: fr
Re: C++ for embedded: how to learn it in 2021?
« Reply #59 on: December 16, 2021, 07:37:02 pm »
Oh uh, I usually agree with tggzzz on programming topics. Except about Rust. But hey, let's not pollute this thread! :-DD

FWIW I'm not a Rust fanbois; I haven't yet seriously "kicked its tyres".

Oh, I didn't imply you were. But yes, if you're interested, you should definitely try using it and see what you like and what you don't like in practice.

Competent people that have "kicked its tyres" seem to believe that it has sufficient characteristics to be a useful improvement on the existing languages/tools. That makes it worthy of attention.

I agree it is. But after taking a closer look at it for a while, I've also seen a number of points that were IMHO not so good. That would certainly warrant a completely separate thread though.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 8062
  • Country: pl
Re: C++ for embedded: how to learn it in 2021?
« Reply #60 on: December 16, 2021, 08:35:35 pm »
Neither does C/C++! In superscalar processors the output of the compilers is internally converted into many instructions. Yes, in effect "add ax, bx" is interpreted by the hardware.

The processor manufacturers keep that internal instruction set a closely guarded secret, and it changes over time.
Which makes the whole remark irrelevant - writing or generating assembly is as close to metal as it gets. C takes me there, Python doesn't come close.

Besides, I don't know about "add ax, bx", but "add eax, ebx" surely translates to a single µOP. The do release enough to know that much. And it has nothing to do with superscalar - i8086 was microcoded and single issue and you could make some trivial RISC like MIPS run superscalar on bare metal.

I wouldn't expect a language tutorial to go into that level of depth because it could well be implementation dependent.
If I suspected something like that about a language I would be more cautious recommending it to embedded developers.

BTW, I will have to dig how they handle multiple (conflicting or not) instantiations of the same generic in different modules and linking this clusterfuck together. C++ loves such things.

Given that it is probable that Rust will be in the linux kernel (probably drivers initially), I would presume there are no major inefficiencies compared with C. Indeed, the clarifications/simplifications inherent in Rust can be expected to enable the compiler to avoid having to make pessimistic assumptions.
Well, this cuts both ways. They fell for hype and rewrote the whole thing in C++ at one point and Linus curses that language to this day :-DD
« Last Edit: December 16, 2021, 08:38:58 pm by magic »
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: C++ for embedded: how to learn it in 2021?
« Reply #61 on: December 16, 2021, 10:04:19 pm »
Python is not low level and doesn't generate machine code ;)

Neither does C/C++! In superscalar processors the output of the compilers is internally converted into many instructions. Yes, in effect "add ax, bx" is interpreted by the hardware.

That's nothing to do with superscalar (running multiple machine language instructions in parallel). You're talking about CISC, such as x86.

CPUs running true RISC instruction sets such as RISC-V or MIPS or Alpha don't have secret internal instructions. The instruction you see is the instruction that goes down the pipeline.

ARM is somewhere in the middle. Most instructions are like any other RISC. The load/store multiple are expanded internally into the obvious series of single-register transfers. Complex addressing modes and on some implementations also ALU operations that combine shift/rotate with something else might be broken down into a couple of uops.

Even on x86, an instruction like "add ax, bx" will be ONE uop. Multiple uops is for instructions that use memory, especially with complex addressing modes, or operations that involve reading and writing memory in the same instruction e.g. adding a register to a memory location.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: C++ for embedded: how to learn it in 2021?
« Reply #62 on: December 16, 2021, 10:06:41 pm »
C++ doesn't make compile time computation as easy to express as runtime computation.

I would argue that with the advent of constexpr, it actually does, at least since C++14's improvements.

That does help a lot, yes.

I'm not sure it's fast. I guess I could do an experiment...
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: C++ for embedded: how to learn it in 2021?
« Reply #63 on: December 16, 2021, 10:38:02 pm »
Neither does C/C++! In superscalar processors the output of the compilers is internally converted into many instructions. Yes, in effect "add ax, bx" is interpreted by the hardware.

The processor manufacturers keep that internal instruction set a closely guarded secret, and it changes over time.
Which makes the whole remark irrelevant - writing or generating assembly is as close to metal as it gets. C takes me there, Python doesn't come close.

Clearly incorrect; you can't access lower levels, but other people can and do. They even change the internal instructions in processors operating in customers' premises - that's how all the recent slew of security breaches are mitigated.

Quote
Besides, I don't know about "add ax, bx", but "add eax, ebx" surely translates to a single µOP. The do release enough to know that much. And it has nothing to do with superscalar - i8086 was microcoded and single issue and you could make some trivial RISC like MIPS run superscalar on bare metal.

You are almost certainly correct about the specific (pseudo!) instruction I used. But if different instructions and/or addressing modes that touch memory are used, then the instructions output by  the C compiler will be interpreted.

Quote
I wouldn't expect a language tutorial to go into that level of depth because it could well be implementation dependent.
If I suspected something like that about a language I would be more cautious recommending it to embedded developers.

Then you shouldn't be recommending C for that purpose!

The original C tutorials (i.e. K&R and The C Puzzle Book) certainly didn't discuss anything like that. For good reasons they don't even mention stacks, even though all implementations will be based around stacks.

Having said that, C has finally got around to specifying a memory model - quarter of a century after other mainstream languages did it, and a decade after Boehm had to forcibly remind people that threads couldn't be implemented as a library in C.

Quote
BTW, I will have to dig how they handle multiple (conflicting or not) instantiations of the same generic in different modules and linking this clusterfuck together. C++ loves such things.

Given that it is probable that Rust will be in the linux kernel (probably drivers initially), I would presume there are no major inefficiencies compared with C. Indeed, the clarifications/simplifications inherent in Rust can be expected to enable the compiler to avoid having to make pessimistic assumptions.
Well, this cuts both ways. They fell for hype and rewrote the whole thing in C++ at one point and Linus curses that language to this day :-DD

That cuts both ways: given Torvald's "sub-optimal" experiences that you mention, his not objecting to Rust in the kernel is indicative.
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 magic

  • Super Contributor
  • ***
  • Posts: 8062
  • Country: pl
Re: C++ for embedded: how to learn it in 2021?
« Reply #64 on: December 16, 2021, 11:37:58 pm »
But if different instructions and/or addressing modes that touch memory are used, then the instructions output by  the C compiler will be interpreted.
It changes nothing about the fact that C prepares those architectural instructions in advance and some languages don't.
And "interpretation" is a rather melodramatic way to describe what happens to most instructions.

Then you shouldn't be recommending C for that purpose!

The original C tutorials (i.e. K&R and The C Puzzle Book) certainly didn't discuss anything like that. For good reasons they don't even mention stacks, even though all implementations will be based around stacks.
Because C has no generics.

But if it had generics, and if it were implementation defined whether they blow up into many variants during compilation, and if the reference implementation were silent about what it can promise in this regard, then yes, I would think twice before using C for anything memory constrained.

BTW, I found that the book does actually explain the implementation of generic function. In the subchapter on generic data types ::)
 

Offline tggzzz

  • Super Contributor
  • ***
  • Posts: 23122
  • Country: gb
  • Numbers, not adjectives
    • Having fun doing more, with less
Re: C++ for embedded: how to learn it in 2021?
« Reply #65 on: December 16, 2021, 11:55:44 pm »
If you included the context in which I made my statements, you would see that you are making strawman points.

In this context, generics are a complete red herring.
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 magic

  • Super Contributor
  • ***
  • Posts: 8062
  • Country: pl
Re: C++ for embedded: how to learn it in 2021?
« Reply #66 on: December 17, 2021, 12:37:52 am »
The whole exchange started with my rant about generics and poor documentation of low level aspects of the language. To which you said:  maybe it's not documented because it's implementation defined. I assumed that to be the context :-//
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 17798
  • Country: fr
Re: C++ for embedded: how to learn it in 2021?
« Reply #67 on: December 17, 2021, 12:52:25 am »
I'm not sure what exactly you guys were discussing after reading your exchange. I think you were not talking about the same thing exactly though, hence the discussion running in circles.
As to generic programming, it's an entire topic in itself, there are many ways of tackling it and I don't think C++ is the best at this either. But that using generics would potentially "blow up" code size is pretty much a given in the general case, at least if you want it to be implemented statically at compile time. From the POV of code size, the typical C approach (very textbook example with the qsort() function and siblings) with pointers and function pointers is usually still the winner, except possibly in very particular cases with *very small* functions.

There is probably room for optimization and code reuse from the compiler's side - for C++ templates for example - but I will never expect this to be particularly impressive in the general case.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 6464
  • Country: nz
Re: C++ for embedded: how to learn it in 2021?
« Reply #68 on: December 17, 2021, 02:24:32 am »
C++ doesn't make compile time computation as easy to express as runtime computation.

I would argue that with the advent of constexpr, it actually does, at least since C++14's improvements.

That does help a lot, yes.

I'm not sure it's fast. I guess I could do an experiment...

OK, given this code:

Code: [Select]
#include <stdio.h>

constexpr long fib(long n) {
  return n < 2 ? n : fib(n - 1) + fib(n - 2);
}

int main(){
  printf("%ld\n", fib(36));
  return 0;
}

... gcc -O takes 1.58 seconds to compile it, then the compiled program takes 0.01 seconds to run (basically, helloworld).

If I change the 36 to 37 then gcc takes 1.99 seconds to compile it, then the compiled program takes 0.130 seconds to run.

Basically, with 37 gcc hits a time limit and gives up on evaluating it at compile time.

If I do it for 36 but disable the compile-time evaluation then it takes 0.100 seconds to run.

So compile-time evaluation takes around 15 times as long as running compiled code. And there is a definite and fairly low upper limit to how much computation is permitted.


Clang refuses to compile-time evaluate this function for any argument except 0 or 1, no matter the optimisation level. Clang will compile-time evaluate a singly-recursive function such as factorial.
 
The following users thanked this post: MK14, SiliconWizard

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4657
  • Country: us
Re: C++ for embedded: how to learn it in 2021?
« Reply #69 on: December 17, 2021, 02:47:32 am »
Quote
a B*tree for three different types of keys: unsigned 32-bit integers, strings, and points (or axis-aligned rectangles).
OK, I'm curious.  Why did you need a B*Tree(s)?  One of my observations has been that much "embedded" software rarely needs the sort of performance one gets from "advanced" algorithms, just because "N" is rarely very large...

Quote
Do you think it will be easier in C++? If so, why?
Convince me that in C++, they would not have wrapped some polymorphism around std::map (RB trees by default, I think?  Or one of the other tree-type implementations designed to replace map) and called it done...
(No, you probably couldn't run std::map on your Arduino-class hardware.  But you wouldn't have needed to, either.  OP's system was significantly bigger.)
Call in one month to search, figure out, and add the polymorphism, instead of 3M to implement from scratch.
(quicker it it was already part of your toolbook, of course.)

It's vaguely how I feel about Python.  As an ASM/C coder I "hate" programming in an interpreted language with no clear idea how any particular library is implemented, or how "fast" or "efficient" it is.  But I put it together cause I don't want to write a GUI, or an XML or JSON parser in C or ASM, and when I'm done the python code runs ... plenty fast enough, for the cases I'm using.
(Or for that matter, CircuitPython vs Arduino vs Bare C - all zippier (on current hardware) than a BASIC Stamp.  And people did all sorts of neat things with BASIC Stamps.)
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: C++ for embedded: how to learn it in 2021?
« Reply #70 on: December 17, 2021, 02:59:04 am »
So, I did some crude code experimentation with a binary search tree, with the aim of being MISRA C++ compliant (except that I don't have the actual spec, so I'm guessing) for use in an embedded (freestanding C/C++) environment while maximising maintainability, readability, and verifiability, without compromising performance too much; all just to see what I would end up with.

Here's how I went about it.

At the very core, I defined a key comparison function that we define for each key type we need.  For example,
Code: [Select]
enum class compares_as { below, equal, above };

template <class T>
compares_as key_compare(T key1, T key2)
{
    return (key1 < key2) ? compares_as::below :
           (key1 > key2) ? compares_as::above :
                           compares_as::equal ;
}

template <>
compares_as key_compare<const char *>(const char *key1, const char *key2)
{
    const int  rc = strcmp((key1) ? key1 : "", (key2) ? key2 : "");
    return (rc < 0) ? compares_as::below :
           (rc > 0) ? compares_as::above :
                      compares_as::equal ;
}
The above implements key_compare() for all numeric types, and for strings using strcmp().  For a 2D point (or complex number) type, something like
Code: [Select]
template <>
compares_as key_compare<vec2>(vec2 key1, vec2 key2)
{
    return (key1.y < key2.y) ? compares_as::below :
           (key1.y > key2.y) ? compares_as::above :
           (key1.x < key2.x) ? compares_as::below :
           (key1.x > key2.x) ? compares_as::above :
                               compares_as::equal ;
}
should work, sorting points in ascending y coordinates, and points with the same y coordinate in ascending x coordinates.

The binary search tree node template class heavily relies on the compares_as enumeration logic above.  Omitting sensible destructors, I initially came up with
Code: [Select]
template <class K, class V>
class node {
    private:
        K          key;
        V          val;
        node<K,V> *lt;
        node<K,V> *gt;

    public:
        node(K key, V val): key(key), val(val), lt(nullptr), gt(nullptr) { }

        K get_key(void) { return key; }
        V get_val(void) { return val; }

        compares_as towards_key(K otherkey) { return key_compare(otherkey, key); }

        node<K,V> *get_child(compares_as direction) {
            return (direction == compares_as::below) ? lt :
                   (direction == compares_as::above) ? gt :
                                                       nullptr;
        }

        bool add_child(compares_as cmp, K newkey, V newval) {
            if (cmp == compares_as::below) {
                if (!lt) {
                    lt = new node<K,V>(newkey, newval);
                    return true;
                }
            } else
            if (cmp == compares_as::above) {
                if (!gt) {
                    gt = new node<K,V>(newkey, newval);
                    return true;
                }
            }
            return false;
        }

        int in_order(node<K,V> *parent, compares_as descent, int (*callback)(node<K,V> *, node<K,V> *, compares_as)) {
            int rc;

            if (lt) {
                rc = lt->in_order(this, compares_as::below, callback);
                if (rc) {
                    return rc;
                }
            }

            rc = callback(this, parent, descent);
            if (rc) {
                return rc;
            }

            if (gt) {
                rc = gt->in_order(this, compares_as::above, callback);
                if (rc) {
                    return rc;
                }
            }

            return 0;
        }
};
The node::in_order() member function is a recursive function that traverses the tree, calling the callback function for each visited node in order.  I used it to output the trees generated in Graphviz DOT format, for simple visual verification.  (EDIT: It is not supposed to be included in actual used code, and is not MISRA compliant; I included it only because it lets us verify test trees very easily, with just one helper call back function per tree/node type.)

The "trick" is the node::towards_key() member function, which compares the specified key to the key in the current node.  It simply calls the key_compare() function we defined earlier.  This way, to add new key types, one only needs to define a template specialization for key_compare().

The actual tree template class:
Code: [Select]
template <class K, class V>
class tree {
    private:
        node<K,V> *root;
    public:
        tree(): root(nullptr) { }

        bool add(K newkey, V newval) {
            if (root) {
                node<K,V>   *next = root;
                node<K,V>   *curr;
                compares_as  direction;

                do {
                    curr = next;
                    direction = curr->towards_key(newkey);
                    next = curr->get_child(direction);
                } while (next);

                return curr->add_child(direction, newkey, newval);

            } else {
                root = new node<K,V>(newkey, newval);
                return true;
            }
        }

        bool find(K thekey, V* oldval) {
            if (root) {
                node<K,V>   *curr = root;
                compares_as  direction;

                do {
                    direction = curr->towards_key(thekey);
                    if (direction == compares_as::equal) {
                        if (oldval) {
                            *oldval = curr->get_val();
                        }
                        return true;
                    }
                    curr = curr->get_child(direction);
                } while (curr);
            }

            return false;
        }

        V get(K thekey, V notfound) {
            if (root) {
                node<K,V>   *curr = root;
                compares_as  direction;

                do {
                    direction = curr->towards_key(thekey);
                    if (direction == compares_as::equal) {
                        return curr->get_val();
                    }
                    curr = curr->get_child(direction);
                } while (curr);
            }

            return notfound;
        }

        int in_order(int (*callback)(node<K,V> *, node<K,V> *, compares_as)) {
            if (root) {
                return root->in_order(nullptr, compares_as::equal, callback);
            } else {
                return 0;
            }
        }
};
To instantiate a tree with integer keys and integer values, I used
Code: [Select]
    tree<int, int> itree;
    itree.add(4, 1);
    itree.add(2, 2);
    itree.add(6, 2);
    itree.add(1, 3);
    itree.add(3, 3);
    itree.add(5, 3);
    itree.add(7, 3);
and another with C string keys and values with
Code: [Select]
    tree<const char *, const char *> stree;
    stree.add("one", "1");
    stree.add("two", "2");
    stree.add("three", "3");
    stree.add("four", "4");
    stree.add("five", "5");
    stree.add("six", "6");
It seems to work.

Because this was the first go at it, I omitted all comments, and the code is quite crude.  Remember, first sketch.  Here are my observations on this exercise:
  • Adding new key types using this scheme is very easy, as it only requires a new template specialization for the key_compare() function.
    The only requirement is that the key set is totally ordered with respect to key_compare(), without conflicts.
  • Names matter. compares_as seemed a good name while I wrote the key_compare() function, but it is a poor name for the use cases.  order would be a more descriptive name.
  • The root node is created in tree::add(), but all subsequent ones in node::add_child().  This needs documenting, and probably a comment in both places for maintenance purposes.
  • I completely omitted destructors and deleteing the nodes created with new.
  • I avoided virtual methods, to keep any overhead (when compiled with optimizations enabled) to a minimum.
    I do not claim this code is efficient, though.
  • Since the tree class only cares about the keys in their ordinal sense, an unit test with say integer keys suffices for the tree template class verification.
    All key types should be carefully verified, so that each key type forms a totally ordered set.  But since that is just one function, it should be easy to do.
  • I do think the code is a lot more compact, readable, and maintainable than the C (or GNU C) version I might write, assuming – as we are in this thread – that multiple key types are actually needed.
At this point, I'm very interested in what others think of the approach (given the aims stated at the beginning of this post), and whether DiTBho believes this kind of approach would have helped with their B*tree implementation.  The code itself is just crude first sketch; and I apologise for the lack of comments.
« Last Edit: December 17, 2021, 06:18:40 pm by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 5098
  • Country: gb
Re: C++ for embedded: how to learn it in 2021?
« Reply #71 on: December 17, 2021, 12:05:01 pm »
I'm curious.  Why did you need a B*Tree(s)?  One of my observations has been that much "embedded" software rarely needs the sort of performance one gets from "advanced" algorithms, just because "N" is rarely very large...

I call it "sewing machine" but it's an "embroidery machine" with 400 sewing needles per line, and there is a very complex motion engine in order to process embroidery on silk, microfiber, in addition to the fact that it also works curtains and tablecloths.

You can also partition the tasks and allocate one line to embroider sexy lace panties for women, and the other line to embroider napkins.

The number of items (especially when n = patterns) to search for is very large, k0 * log(n) << k1 * n , and the engine requires fast search to satisfy deadlines in the motion engine in order to drive each 400 sewing needles line.
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 5098
  • Country: gb
Re: C++ for embedded: how to learn it in 2021?
« Reply #72 on: December 17, 2021, 12:11:06 pm »
The old board (in the pic) runs VxWorks v5.3.

The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 5098
  • Country: gb
Re: C++ for embedded: how to learn it in 2021?
« Reply #73 on: December 17, 2021, 12:13:08 pm »
The current toolchain, ICE and BSP don't support C++, only C ... so, in any case, I have to pay for a new package-set (ah, Windriver ...  :o  :o  :o )
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 5098
  • Country: gb
Re: C++ for embedded: how to learn it in 2021?
« Reply #74 on: December 17, 2021, 01:19:16 pm »
So, I did some crude code experimentation with a binary search tree
[...]

thank you a lot! I will for sure study this draft  :D


Code: [Select]
void btree_methods_string_init
(
    p_btree_t p_btree
)
{
    p_btree->context.coin.method.cmp.isle = cmp_isle; /* A <= B */
    p_btree->context.coin.method.cmp.islt = cmp_islt; /* A < B */
    p_btree->context.coin.method.cmp.isge = cmp_isge; /* A >= B */
    p_btree->context.coin.method.cmp.isgt = cmp_isgt; /* A > B */
    p_btree->context.coin.method.cmp.iseq = cmp_iseq; /* A == B */
    p_btree->context.coin.method.let.show = let_show;
    p_btree->context.coin.method.let.copy = let_copy; /* A = B */
}

That's what I need to define for each key-type.

To compare strings, I use lexicographic ordering. To compare rect(x,y) and cplx numbers in Euler form, I only consider the module, unless the two modules are equal, in that case, I also consider the angle.

in rect(x,y): { x,y } are a pair of uint32
in cplx(r,theta): { r , theta} are a pair of fixedpoint

Code: [Select]
/*
 * finds and returns the record to which a coin refers
 */
btree_ans_t btree_coin_find
(
    p_btree_t p_btree,
    btree_coin_t coin
)
{
    p_btree_node_t       p_root;
    p_btree_node_t       p_node;
    btree_coin_t         penny;
    btree_item_method2_t cmp_iseq;
    btree_item_method2_t let_copy;
...
    p_root   = p_btree->context.p_root;
...
    cmp_iseq = p_btree->context.coin.method.cmp.iseq;
    let_copy = p_btree->context.coin.method.let.copy;
...
        while (...)
        {
            let_copy(p_btree, penny, p_node->as.leaf.coin[i0]);
            is_found = cmp_iseq(p_btree, penny, coin);
            if (is_found)
            {
...
That's how the code looks like with function-methods to copy and compare.

Code: [Select]
LG__ ________________ c:0002 d:01 lib_btree_z_v1.btree_coins_show
____ ________________ c:0001 d:01 lib_btree_z_v1.btree_height_get
LG__ _123456_________ c:0616 d:01 lib_btree_z_v1.btree_coin_insert
LG__ _1234___________ c:7708 d:01 lib_btree_z_v1.btree_coin_find
____ ________________ c:0139 d:01 lib_btree_z_v1.record_make
L___ ________________ c:0005 d:01 lib_btree_z_v1.btree_start_new
L___ ________________ c:0081 d:01 lib_btree_z_v1.node_make_leaf
_G__ ________________ c:0106 d:01 lib_btree_z_v1.node_make
LG__ __2_____________ c:7837 d:01 lib_btree_z_v1.node_leaf_get
L___ _12_____________ c:0059 d:02 lib_btree_z_v1.node_insert_leaf
LG__ ________________ c:0077 d:02 lib_btree_z_v1.node_insert_leaf_after_splitting
____ _12_____________ c:0183 d:01 lib_btree_z_v1.node_cutpoint_get
LG__ _123____________ c:0092 d:03 lib_btree_z_v1.node_insert_parent
LG__ ________________ c:0011 d:02 lib_btree_z_v1.node_insert_new_root
_G__ ________________ c:0081 d:01 lib_btree_z_v1.node_iLP_get
LG__ ________________ c:0067 d:02 lib_btree_z_v1.node_insert
_G__ ________________ c:0015 d:01 lib_btree_z_v1.node_insert_after_splitting
_G__ ________________ c:0005 d:02 lib_btree_z_v1.destroy_tree
LG__ _12_____________ c:0046 d:04 lib_btree_z_v1.nodes_destroy
_G__ ________________ c:0053 d:02 lib_btree_z_v1.btree_coin_delete
LG__ _1234___________ c:0099 d:04 lib_btree_z_v1.node_entry_delete
LG__ ________________ c:0099 d:02 lib_btree_z_v1.node_entry_remove
LG__ ________________ c:0051 d:04 lib_btree_z_v1.node_iNG_get
LG__ ________________ c:0047 d:03 lib_btree_z_v1.nodes_coalesce
_G__ ________________ c:0007 d:02 lib_btree_z_v1.node_root_adjust
LG__ ________________ c:0005 d:02 lib_btree_z_v1.nodes_redistribute

This is a shot with minimal coverage, considering a minimum test with
- start a new tree
- fill 20 keys
- random delete and check
- check global consistency
- check local consistency
- delete everything (tree destroy + check for memory leakage)
- fill the tree again
- fill it again with special keys to force nodes_redistribute
- selective delete and check for consistency (forces nodes_redistribute)
- fill it again with special keys to force nodes_coalesce

The most of the functions have to work with both "glue" (internal node) and "leaf" nodes.

I also need to check the maximal stack deep because the code is not "recursion free".
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

-->