The International Simutrans Forum

 

Author Topic: Modern C++?  (Read 5992 times)

0 Members and 1 Guest are viewing this topic.

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #70 on: November 26, 2018, 07:18:35 PM »
Please do not suggest putting "using namespace std;", even as a quick fix.  Such a statement is only used for extremely quick feature R&D for a small program, or for beginners in the first few programs during their C++ language learning.  AVOID IT.
The proper way would be to insert "using std::whatever_it_is;" which would allow that name and only that name to be brought in from the standard.
I totally agree. It defeats the point of namespaces. (One could perhaps argue that the standard C++ library should have been divided into more namespaces, which appears to be what other languages have done, but that is beyond our control.)

It's quite interesting that while there is a complaint about using standard allocation, ie. new/delete, in some parts of the code, that at no time was an customized allocator mentioned.
I think it has been mentioned in earlier discussions that were very focused on Simutrans' custom collections. Possibly even by me. I don't know more about them than that they exist, though.

I personally am reveling in the ability to have a std::vector<std::unique_ptr<My_Type>> that allows polymorphism and safer memory guarantees.
It is certainly better than std::vector<My_Type*> (as long as this collection owns the pointers and the pointers have a limited life span), but both have the drawback that you have to de-reference two pointers to get that actual data, which may be significant in performance critical situations.

Granted, this would require each child class of convoy to separately inherit from overtaking, but this would seem to make better sense, especially since an aircraft wouldn't really know about overtaking.
Composition might be even better, although I'm not sure if C++'s multiple inheritance makes that less of an issue than the Java I'm used to.

Simutrans is ancient, it was top notch when there were 486 CPUs and 4 MB DOS main memory still nothing special, and 1024x768 has high resolution graphics.
I don't think I've even played Transport Tycoon on such a poorly equipped computer! But then I've gotten the impression that the Simutrans crowd lags somewhat behind other gamers.



All in all, I think the biggest problem Simutrans has is the combination of game logic and display logic in karte_t. It makes it pretty much impossible to add GPU rendering as an alternative back-end, as the information need for that is quite different from the current algorithm that always draws everything to the backbuffer and only then figures out, in display coordinates, where the changes are. And I suspect it is a drawback for multiplayer as well.


I don't see a way to change that gradually, though.


Direct2D has an API that looks more suited for what Simutrans does than OpenGL (or Direct3D for that matter), but I haven't gotten around to checking if it has some tricks to avoid the serious performance problems the OpenGL attempts have had. It is also Windows only, which makes it significantly less interesting for a cross-platform game.

Offline TrainMith

  • *
  • Posts: 58
Re: Modern C++?
« Reply #71 on: December 05, 2018, 05:13:48 PM »
(One could perhaps argue that the standard C++ library should have been divided into more namespaces, which appears to be what other languages have done, but that is beyond our control.)
Yes, but that is why the "using" construct was made; only bring a name into the current namespace if necessary, and as a preventative of multiple full specificiers (ie. std::vector this, std::vector that). 

Composition might be even better, although I'm not sure if C++'s multiple inheritance makes that less of an issue than the Java I'm used to.
As with any good tool there is a wrong way, and also the wrong time, to use the proper tool.  Although I've stayed away from Java, the analog to Java's interface is the pure virtual C++ base class, which has the slight downside of a vtable indirection, but the benefit in being able to use it with multiple inheritance to present different facets for different uses of the derived class with less entanglement.

I'm constantly vexed by simutran's use of forwarding declarations within files that merely use the class, whenever I try to use vim's [-Ctrl-i or [-I code keyword search to find where exactly the class is truly forward declared within an interface header and then its accompanying source file.  I'm sure that it might have helped originally coding each module, but now it is wasting coder's time to find these and  about 60% of the compiler's time trying to match these forwarding declarations.  I'm sure this is also a big dissuasion for some new coders.
I don't know more about them than that they exist, though.
I've yet to make one myself, mostly because C++98's allocator was poorly designed and documented.  Yet they have been improved, and what better time to learn?
It is certainly better than std::vector (as long as this collection owns the pointers and the pointers have a limited life span), but both have the drawback that you have to de-reference two pointers to get that actual data, which may be significant in performance critical situations.
Your fears may be unfounded:  C++Now 2018: Matt Godbolt “What Else Has My Compiler Done For Me Lately?” https://www.youtube.com/watch?v=nAbCKa0FzjQCppCon 2017: Matt Godbolt “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” https://www.youtube.com/watch?v=bSkpMdDe4g4Also CppCon 2016: Jason Turner “Rich Code for Tiny Computers: A Simple Commodore 64 Game in C++17” https://www.youtube.com/watch?v=zBkNBP00wJEOne profound observation was that compilers might be better at creating machine code and, when set to higher optimization levels, don't always expect the machine code to exactly follow the source code while also providing better instructions.

Interesting point to make:  if the dirty bit is being used to mark a graphic to be redrawn, it is being done for two reasons: once for the object needing to move or be placed/removed, and otherwise for redrawing itself because something in front of it moved.  Thus, I'd suggest having the dirty bit marked in two places; first for the object actually being moved/placed/removed (ie, OpenGL happy, perhaps also for marking a Display List, VBO, or VAO as needing to be remade), and the other for the graphics in back to redisplay (ie, not needing work if using OpenGL).

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #72 on: December 05, 2018, 06:36:41 PM »
Yes, but that is why the "using" construct was made; only bring a name into the current namespace if necessary, and as a preventative of multiple full specificiers (ie. std::vector this, std::vector that). 
"using" is not the solution. It is equally the problem. What I'm talking about is that "using" can either import a single symbol, or the entire standard C++ library. Other languages, like Java and Python, have divided their standard library into multiple parts so that you can import for instance everything related to file access or GUI with a single line. In C++, the fact that you also need to use #include allows you to filter down somewhat, but since included files often include other files, you get more than you implicitly ask for.

On the other hand, there is debate on whether importing symbols in bulk is a good idea in Java, even though the bulks are far smaller than in C++.

Your fears may be unfounded
Compilers can't work around language rules and physical realities. Although I haven't read the C++ standard itself, it is apparently not freely available, it is my impression from other sources that C++ dictates that std::vector<int*>, and by extension std::vector<std::unique_ptr<int>>, basically boils down to int**. Getting hold of the actual int value requires two memory accesses. std::vector<int> boils down to int*, where only one memory access is needed to get the int value. std::vector<std::unique_ptr<int>> is of course a strange thing to have, but the same holds true for other types than int.

There are some potential benefits of having a vector of some kind of reference to something, rather than just a vector of something. What ultimately is best depends on the size of that something, the access patterns, and whether execution speed is more important than development speed.

Offline TrainMith

  • *
  • Posts: 58
Re: Modern C++?
« Reply #73 on: December 07, 2018, 02:09:55 PM »
Apparently the forum has a problem with code blocks, and decided to delete all of my commentary.  My apologies for the original seemingly empty post. Upon further forum testing, code blocks have problems with line returns and some other anomaly.  I also had to add an extra line return after every each code line. Anyways, back to my commentary!

it is my impression from other sources that C++ dictates that std::vector, and by extension std::vector
Intuitively I had thought the same, although was more optimistic due to both the gcc and clang compilers, which optimize the following code back into an int array access:
Code: [Select]
#include <vector>#include <memory>
int check_it(const std::vector<std::unique_ptr<int>> &vupi)
{
int result(0);
for(size_t i(0); i < vupi.size(); ++i)
result += *vupi[i];
return result;
}
MS VS2017 apparently isn't as clever, and does the expected double indirection.  This apparently is a specification for int, maybe even for any built-in, type, since the following code results in the double indirection we both were expecting:
Code: [Select]
#include <vector>
#include <memory>
class A
{
    public:
        virtual int get() const = 0;
        virtual ~A() {}
};
class B : public A
{
        int b;
    public:
        B(int bb) : b(bb) {}
        int get() { return b; }
};
class C : public A
{
        int c;
    public:
        C(int cc) : c(cc) {}
        int get() { return c++; }
};
int check_it(const std::vector<std::unique_ptr<A>> &vupi)
{
    int result(0);
    for(size_t i(0); i < vupi.size(); ++i)
        result += vupi[i]->get();
    return result;
}
Tested this at cpp.godbolt.com by the methods mentioned within the videos I linked earlier.
« Last Edit: December 07, 2018, 02:53:47 PM by TrainMith »

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #74 on: December 07, 2018, 05:12:02 PM »
I don't see how std::vector<std::unique_ptr<int>> can be turned into int[] without breaking certain things. In particular:
Code: [Select]
int *a = vec[1].get();
vec.erase(vec.begin());
int *b = vec[0].get();
assert(a == b);
Only if the compiler can be certain that it sees all usages of vec can if do such a substitution.

Offline isidoro

  • Devotee
  • *
  • Posts: 1124
Re: Modern C++?
« Reply #75 on: December 08, 2018, 12:45:27 AM »
Not a good example.  Instead of moving the content of the int[], the compiler can make the vec start now at int[1]...
 

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #76 on: December 08, 2018, 09:01:53 AM »
Not a good example.  Instead of moving the content of the int[], the compiler can make the vec start now at int[1]...
That is std::deque, isn't it? But the point is that insert and remove can cause the elements of the vector to be moved to a different memory address. What if I did an insert at index 0 with a million new elements instead of one remove? Are you suggesting that the compiler can just allocate a million elements ahead of the start just in case? Remember that the compiler will not see all operations being done on the vector, as the vector is accessed from multiple compilation units. You'd need some serious link-time optimizations to improve this.

Offline isidoro

  • Devotee
  • *
  • Posts: 1124
Re: Modern C++?
« Reply #77 on: December 08, 2018, 08:51:08 PM »
I only said that yours wasn't a good example, not that there wasn't the problem you mention.   ;) The best thing can be to look at the assembly code in compilers that do such an optimization, if there are...

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #78 on: December 08, 2018, 11:22:00 PM »
The best thing to do is pick good algorithms and data structures, using the collective experience built up over decades, then measure the performance. Trying to read the optimized assembly output from a compiler will drive most people, even most developers, half crazy. One can try small examples, but there are so many things that come into play, that making examples covering them all is very hard. GCC has some intermediate form that can be dumped for analysis, but it is very verbose, so it is also not really usable on actual production code.

Offline TrainMith

  • *
  • Posts: 58
Re: Modern C++?
« Reply #79 on: December 09, 2018, 09:01:00 AM »
I don't see how std::vector<std::unique_ptr<int>> can be turned into int[] without breaking certain things.
Ter, it didn't make int[], rather it was int*[].  The compiler optimized for a single pointer dereference from the vector since there was an iteration over all the elements, and then did a per element dereference from the unique_ptr.  Your example wouldn't cause an assertion error, btw, because of C++11 move semantics introduced into the standard containers.  Pointing into the vector would still cause a problem due to elements being relocated.
Ter, even the creator of Compiler Explorer mentioned that it is best for analyzing small portions of code, and to see how they relate using different compilers.  It even highlights between the original code and the assembly produced, and it has buttons for reducing the verbosity (even name mangling).  I'm thinking you'd like it a lot more than you think.

So should effort on changing coding style to do the same thing with different compiler input not rather invested more wisely in doing something not yet possible? The future might be rewriting the core from scratch to optimise for GPU and threads and reuse parts from current Simutrans (like the routing) and the right graphics. Then all the "modern" concepts can be used to their fullest potential.
I think the biggest problem Simutrans has is the combination of game logic and display logic in karte_t. It makes it pretty much impossible to add GPU rendering as an alternative back-end
I'm in agreement with Ter here; karte_t is needing to be reworked.  Perhaps game_entity_t would be a good idea for the per-game aspect, keeping karte_t for the karte it is supposed to be.    Also we could have a lobby_t, allowing us to select the game_entity_t for play.

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #80 on: December 09, 2018, 09:53:31 AM »
Ter, it didn't make int[], rather it was int*[].  The compiler optimized for a single pointer dereference from the vector since there was an iteration over all the elements, and then did a per element dereference from the unique_ptr.
You were the one who wrote that the compiler turned it into an int array, when I claimed that it could not do better than an array of pointers to integers, which in turn had to be accessed via a pointer (in all but the most trivial of cases).

Offline isidoro

  • Devotee
  • *
  • Posts: 1124
Re: Modern C++?
« Reply #81 on: December 09, 2018, 11:53:58 PM »
The best thing to do is pick good algorithms and data structures, using the collective experience built up over decades, then measure the performance. Trying to read the optimized assembly output from a compiler will drive most people, even most developers, half crazy. One can try small examples, but there are so many things that come into play, that making examples covering them all is very hard. GCC has some intermediate form that can be dumped for analysis, but it is very verbose, so it is also not really usable on actual production code.

The best thing to do to solve a mystery about how some thing or other is done by a compiler is to look at the assembly generated by it.  It's not that difficult.  Give it a try.   ;)

Offline Ters

  • Coder/patcher
  • Devotee
  • *
  • Posts: 5368
  • Languages: EN, NO
Re: Modern C++?
« Reply #82 on: December 10, 2018, 06:53:43 AM »
The best thing to do to solve a mystery about how some thing or other is done by a compiler
I'm not here to solve mysteries, except for why Simutrans is as it is.

Give it a try.
I don't have to try. I've read more than enough x86 assembly, and some Java equivalent, to risk my sanity (if I had any to begin with).

Offline TrainMith

  • *
  • Posts: 58
Re: Modern C++?
« Reply #83 on: December 10, 2018, 06:12:31 PM »
You were the one who wrote that the compiler turned it into an int array, when I claimed that it could not do better than an array of pointers to integers, which in turn had to be accessed via a pointer (in all but the most trivial of cases).
Ter, forgive me for misreading your post to which I was originally replying, as that we must have been thinking of the same array of pointers to integers.  I had only been inferring that the vector might turn into an optimized array access (especially in a for-loop), but did not intend that the contained pointer would be simplified. 

As for other aspects of modern C++, the usage of constexpr for both constants and functions returning compile-time constants would help some with compile and runtime times.

Offline isidoro

  • Devotee
  • *
  • Posts: 1124
Re: Modern C++?
« Reply #84 on: December 11, 2018, 12:15:33 AM »
I'm not here to solve mysteries, except for why Simutrans is as it is.

Good for you!