C++Guns – RoboBlog blogging the bot

30.01.2019

C++ Guns: C++20 Aggregates can no longer declare constructors (schon wieder anders...)

Filed under: Allgemein — Tags: — Thomas @ 21:01

Ab GCC 9.
P1008 Prohibit aggregates with user-declared constructors
Initialization in modern C++ - Timur Doumler - Meeting C++ 2018

Das Thema ist ziemlich lang und eigentlich total überflüssig. Wenn nicht immer die ganzen Altlasten da wären ... but we have to deal with it. Es gibt genügend Artikel im Internet wie gut oder schlecht die Initialisierung in C++ kaputt ist. Für einen guten Überblick, und wie man versucht es zu reparieren, empfehle ich obiges verlinktes PDF.

struct A {
    A() = delete;
};

auto func() {
    // ok does not compile
    // A a; 
    // compiles in c++17 but not in 20
    A a{};      // error: use of deleted function 'A::A()'
}

27.01.2019

C++ Guns: Stop using std::endl start using acpl::newline and std::clog std::cerr

Filed under: Allgemein — Tags: — Thomas @ 13:01

Schaut euch mal C++ Weekly - Ep7 Stop Using std::endl an. Eigentlich hat er ja recht, std::endl wird nicht gebraucht. Problem damit (wie immer), es macht mehr als man vermutet. Denn "end line" suggeriert nicht, dass nun ein Zeilenumbruch kommt und auch kein flush(). Das ganze hat bestimmt wieder historische Gründe.

Jedenfalls gibt es neben std::cout auch noch std::cerr und std::clog. Die letzten beiden schreiben nicht nach stdout sondern nach stderr. Wobei std::cerr ungebuffert schreibt. Das macht auch Sinn. Fehlermeldungen und Debug Ausgaben müssen sofort raus geschrieben werden, bevor das Programm abstürzt. Die Log Meldungen können erste gepuffert werden um die Performance nicht zu beeinflussen.

Durch den richtigen Einsatz von std:cout std::cerr und std::clog wird kein std::endl mehr benötigt. Wenn es denn ein normaler Weg gäbe einen Zeilenumbruch zu schreiben...

Wie lernen denn Programmieranfänger in C++ ein Zeilenumbruch zu schreiben? Natürlich mit std::endl ! Wenn die Leute schon bereit sind, will man sie ja nicht gleich wieder mit ASCII Kontrollzeichen verschrecken. Dabei wäre eine newline() Funktion genau wie die endl() so einfach zu implementieren und standardisieren.

Bsp:

template<typename CharT, typename Traits>
inline std::basic_ostream<CharT, Traits>& newline(std::basic_ostream<CharT, Traits>& os) {
    os << '\n';
    return os;
}

std::cout << "Test" << newline;

26.01.2019

Installation von Devuan

Filed under: Allgemein — Thomas @ 18:01

Wie ich schon im vorherigen Artikel Installation von FreeBSD schrieb, geht mir Linux auf die Nerven, vorallem der systemd Teil. FreeBSD war allerdings nicht so das Wahre für den Desktop. Für einen Router ist es super klasse, ohne Frage.
Also ist heute Devuan dran. Das netinstall Image war schon geladen und mit dd auf die Festplatte kopiert. Der alte Rechner bootet ja nicht von USB. Der Devuan Installiert ging auch an und frage mich dann nach einer CDROM :/ Ja, ne, ist nicht. Nach kurzen Suchen fand ich die Möglichkeit manuell den richtigen Path /dev/sdb1 anzugeben.
Jetzt wird das Basissystem installiert. XFCE und SSH hätte ich genr. Neustart.... hmpf. Aus irgendeinem Grund kann mein neuer Monitor nicht mit den Frequenzen dieser uralten Grafikkarte mithalten.
Das Problem hatte ich beim FreeBSD auch schon. Das lässt sich auch leicht in der Xorg.conf umstellen

Und da fängt es schon an zu nerven. GRUB Menu? Neee brauchen wir nicht. Und bis mein Dummer Bildschirm sich wieder einschaltet, ist der längst wieder im X. Im Ausschalten ist der Bildschirm aber schnell ...
Irgendwie bin ich in eine root Konsole gekommen (Das Hacken mit Hardware Zugriff ist echt einfach)

SubSection "Display"
    Depth    24
    Modes     "1024x768" "800x600"
EndSubSection

Und schon ist der Monitor glücklich. Und schon bin ich drin. Devuan XFCE4.

apt-get install vim gkrellm gcc 

Was man eben so braucht.

Youtube in Firefox funktioniert mit 360p ruckelfrei. Da sind die 2 Kerne zu etwas mehr als 50% ausgelastet. Nur die 500MB RAM und die 500MB SWAP sind VOLL. Aber dass Browser Speicher wie dumm fressen ist ja bekannt.
Den Speicher konnte ich sogar auf 1.5GB aufrüsten. Der Intel Pentium 4 3GHz war doch auch nicht schlecht. Sogar die Grafikkarte lies sich tauschen. Von ATI Rage 128 PRO Ultra AGP 4x mit 32MB auf eine NVIDIA GeForce FX 5700 mit 256MB. Linux Treiber waren überhaupt kein Problem. Alles schon installiert. Auch in 3D!

Auch den Temperatur und Spannungssensoren funktionieren. Nur die Drehzahl vom Lüfter nicht. Aber das könnte daran liegen, dass der CPU Lüfter kein Tacho Signal hat. Mehr Ventilatoren braucht das Teil nicht.
Seit Kernel 2.6.xx wurde aber irgendein Sicherheits ACPI Dumm Feature eingebaut, so dass der Treiber w83637hf mit einem Device Busy erst nicht laden wollte. Das ist übrigens auch die Bezeichnung von dem Chip. Das einfügen von

GRUB_CMDLINE_LINUX="acpi_enforce_resources=lax"

in /etc/default/grub und ein anschließendes update-grub samt reboot lösten das Problem.

24.01.2019

Installing gcc, g++ and gfortran 8 from source

Filed under: Allgemein — Tags: , , , — Thomas @ 22:01

The procedure is quite the same as for gcc 4.8 as you can see in my older post Installing gcc, g++ and gfortran 4.8 from source

Read the manual.
Download, unpack, switch dir, download, unpack, link.

$ wget ftp://ftp.gwdg.de/pub/misc/gcc/snapshots/LATEST-8/gcc-8-20190118.tar.xz
$ xz -d gcc-8-20190118.tar.xz
$ tar xf gcc-8-20190118.tar
$ gcc-8-20190118/
$ wget ftp://ftp.gmplib.org/pub/gmp-6.1.2/gmp-6.1.2.tar.bz2
$ tar xjf gmp-6.1.2.tar.bz2
$ ln -s gmp-6.1.2 gmp
$ wget https://www.mpfr.org/mpfr-current/mpfr-4.0.1.tar.bz2
$ tar xjf mpfr-4.0.1.tar.bz2
$ ln -s mpfr-4.0.1 mpfr
$ wget https://ftp.gnu.org/gnu/mpc/mpc-1.1.0.tar.gz
$ tar xzf mpc-1.1.0.tar.gz
$ ln -s mpc-1.1.0 mpc

Make a build directory, start configure, start make, wait
$ cd ..
$ mkdir build-gcc-8-20190118
$ cd build-gcc-8-20190118/
$ ../gcc-8-20190118/configure --prefix=/opt/gcc-8 --enable-threads --enable-languages=c,c++,fortran
$ make -j 32
# make install

23.01.2019

C++ Guns: ACPL: Conway's Game of Life

Filed under: Allgemein — Tags: — Thomas @ 18:01

In den Heise Kommentaren (Ja ich schau da ab und zu rein) gab es letztens ein Vergleich zum Spaß von Conway's Game of Life einmal in C++ und GO. Natürlich war GO furchtbar langsam. Aber der C++ Code sah auch nicht sehr sinnvoll aus. Es ist an der Zeit es mit meinem ACPL Framework und den 2D Array auch mal zu probieren.

Hier die Laufzeiten [sec] ganz grob zum vergleichen. Intel i7-2640M GCC 8.1 go1.6.2 linux/amd64

GO: 1.9
g++ -O1: 0.61
g++ -O2: 0.60
g++ -O3: 0.30

Da bleibt nichts mehr übrig. Mal das Spielfeld und die Anzahl der Iterationen jeweils um Faktor 10 vergrößern. 1000x1000 mit 10000 Iterationen.

GO: 32m6
g++ -O1: 498.3 (8m18)
g++ -O2: 493.3 (8m13)
g++ -O3: 191.5 (3m11)

Jetzt sieht man erst, das GO total unbenutzbar ist. Mit C++ ist man noch mit einer Kaffeepause dabei.
Bei meiner Variante habe ich schon einmal die zwei 3er Schleifen von Hand ausgerollt. Das ist ja trivial. Ansonsten als Container nur acpl::ArrayND benutzt mit 2 Dimensionen und bool Werte. Folgende Zeiten haben sich ergeben:

g++ -O1: 366.6 (6m6)
g++ -O2: 341.1 (5m41)
g++ -O3: 210.0 (3m30)

Die Kaffeepause wird immer kürzer. Sehr interessant, dass bei Optimierung O3 die Laufzeit schlechter wurde. Dies betrifft das Schleifenausrollen. Das kann der Compiler wohl besser als ich. So soll es auch sein!
Anbei der Quellcode. Ist ja wirklich nicht viel. Zu beachten ist, dass der rechte Index der schnell laufende Index ist. Gedanklich läuft man die Zeile entlang und spring dann runter zur nächsten Zeile. Daher werden die Koordinaten in y,x übergeben. In dieser Reihenfolge. Das könnte man in der nächsten ACPL Version auch weg optimieren und ein NDArray-Index benutzen.

Update 15.09.2019
Schöneren Code hochgeladen

#include <iostream>
#include <chrono>
#include <thread>

#include <core/util/ArrayND.hpp>

using std::cout;
using namespace std::chrono_literals;

// Torusartiger Spielraum. Wrap around.
struct Spielfeld : acpl::Array2D<bool>
{
    using Base = acpl::Array2D<bool>;
    using Base::Base;

    // If the x or y coordinates are outside the field boundaries they are wrapped
    // toroidally. For instance, an x value of -1 is treated as width-1.
    bool isAlive(int y, int x) const {
        x += nCols();
        x %= nCols();
        y += nRows();
        y %= nRows();
        return operator()(y,x);
    }

    bool next(int y, int x) const {
        // Count the adjacent cells that are alive.
        int alive = 0;
        if(isAlive(y-1,x-1)) alive++;
        if(isAlive(y-1,  x)) alive++;
        if(isAlive(y-1,x+1)) alive++;

        if(isAlive(y, x-1)) alive++;
        if(isAlive(y, x+1)) alive++;

        if(isAlive(y+1, x-1)) alive++;
        if(isAlive(y+1, x))   alive++;
        if(isAlive(y+1, x+1)) alive++;

        // Return next state according to the game rules:
        //   exactly 3 neighbors: on,
        //   exactly 2 neighbors: maintain current state,
        //   otherwise: off.
        return alive==3 or (alive==2 and isAlive(y,x));
    }
};

struct ConwayGameOfLife {
    Spielfeld a,b;

    ConwayGameOfLife(unsigned int w, unsigned int h)
        : a({h,w}), b({h,w})
    {
        for(unsigned int i=0; i<(w*h/4); i++) {
            a(rand()%h, rand()%w) = true;
        }
    }

    void step() {
        for(unsigned int y=0;y < a.nRows(); y++) {
            for(unsigned int x=0; x < a.nCols(); x++) {
                b(y,x) = a.next(y,x);
            }
        }

        std::swap(a,b);
    }

    auto width() const {
        return a.nCols();
    }

    auto height() const {
        return a.nRows();
    }
};

std::ostream& operator<<(std::ostream& s, const ConwayGameOfLife& game) {
    for(unsigned int x=0; x < game.width()+2; x++) {
        s << '-';
    }
    s << '\n';
    for(unsigned int y=0;y < game.height(); y++) {
        s << "|";
        for(unsigned int x=0; x < game.width(); x++) {
            if(game.a.isAlive(y,x)) {
                s << '*';
            } else {
                s << ' ';
            }
        }
        s << "|\n";
    }

    for(unsigned int x=0; x < game.width()+2; x++) {
        s << '-';
    }
    s << "\n";

    return s;
}

int main() {
    srand(2);
    auto start = std::chrono::high_resolution_clock::now();

    int maxIter = 220;
    ConwayGameOfLife game(60,30);
    for(int i=0; i < maxIter; i++) {
        game.step();

        system("clear");
        std::cout << i << " / " << maxIter << "\n";
        std::cout << game;
        std::this_thread::sleep_for(0.2s);
    }

    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed = end-start;
    std::cout << elapsed.count() << " seconds\n";

    //std::cout << game << "\n\n\n";
}

17.01.2019

(QGIS) Debian dummy / fake packet

Filed under: Allgemein — Tags: — Thomas @ 15:01

I want to install QGIS today and run into following issue:

qgis : Depends: gdal-abi-2-3-0 but it is not installable

It is not installable because it does not exist in buster, only in sid. It is a virtual package for libgdal20.
So I installed libgdal20 and crate a fake packed.

$ cat gdal_abi.txt 
Section: misc
Priority: optional
Standards-Version: 3.9.2

Package: gdal-abi-2-3-0
Version: 2.3.0
Depends: libgdal20
Description: fake package for qgis which needs a gdal-abi-2-3-0

# apt-get install equivs

$ equivs-build gdal_abi.txt

# dkpg -i gdal-abi-2-3-0_2.3.0_all.deb

All fine :)

16.01.2019

C++ Guns: Interval

Filed under: Allgemein — Tags: — Thomas @ 15:01

Es gibt eine Menge Implementationen und Arbeit über Intervall Datentypen in C++, aber nichts, was einfach mal einfach wäre.

Riesen Linksammlung http://www.cs.utep.edu/interval-comp/

Doc No: A Proposal to add Interval Arithmetic o the C++ Standard Library (revision 2) von 2008 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2137.pdf

BOOST natürlich https://www.boost.org/doc/libs/1_69_0/libs/numeric/interval/doc/interval.htm

Und sogar ein Versuch in ganz modernen C++ Moore: Interval Arithmetic in C++20 https://arxiv.org/abs/1802.08558

Die alle behandeln Intervall Arithemtik. Das meinte ich eigentlich nicht. Ehr sowas wie Qt hier:

QMediaTimeRange QMediaTimeInterval

Ein geschlossenes / halboffenes Intervall. Für Zeiten oder Daten. Obe und Untergrenze. Vergleiche ob ein Wert im Interval liegt. Das langt doch schon.

DataInterval

15.01.2019

C++ Guns: How NOW to design FORTRAN -> C++ Interfaces

Filed under: Allgemein — Tags: , — Thomas @ 00:01

This is one wrong way to design a FORTRAN to C++ interface.
But let's start at the beginning. From FORTRAN callable C++ function must be declared with extern "C" to disable name mangling.
The function funcCPP expect a 2D array. Two items a 3 values. Calculate the sum and return it.
Rember: C counts from 0 and the right most index is the fastest index.

#ifdef __cplusplus
    extern "C" {
#endif

double funcCPP(double arr[2][3]);

#ifdef __cplusplus
    }
#endif

double funcCPP(double arr[2][3]) {
  return arr[0][0] + arr[0][1] + arr[0][2] + arr[1][0] + arr[1][1] + arr[1][2];
}

The generated ASM code (GCC -O1) looks straight forward. Almost perfect (except of SIMD). Nothing more to say.

funcCPP:
        movsd   (%rdi), %xmm0
        addsd   8(%rdi), %xmm0
        addsd   16(%rdi), %xmm0
        addsd   24(%rdi), %xmm0
        addsd   32(%rdi), %xmm0
        addsd   40(%rdi), %xmm0
        ret

Now we implement an FORTRAN interface for that function. Using iso_c_binding module to make life easier. But the compiler didn't to any real check. If you add an parameter in the C function and forget to add it in the FORTRAN interface -> BOOM.
The array is pass as an 1D array.

module CPPinterface_m
  use, intrinsic :: iso_c_binding
  implicit none

  interface
    function funcCPP(arr)  bind(c, name="funcCPP")
      use, intrinsic :: iso_c_binding
      implicit none
      real(c_double), dimension(*) :: arr
      real(c_double) :: funcCPP
    end function
  end interface
end module

The main code is a subroutine func which get an 2D array with N items a 3 values. The variable idx contains the position of the items we are interested in. This values are passed to funcCPP. But the two items at position idx(1) and idx(2) are not consecutive stored in RAM. So the compiler has to generate code which make a copy of the six floating point values. That's not what we intend to.

module prog_m
  use CPPinterface_m
  implicit none
  integer :: N

  contains

  subroutine func(arr, idx)
    implicit none
    real(8), intent(inout) :: arr(3,N)
    integer, intent(in) :: idx(2)
    real(8) :: res

    res = funcCPP(arr(:,idx))
  end subroutine
end module

You can see it right here in the assembler code. I commented it for you. The data is passed through stack.

__unrunoff_m_MOD_func:
    subq    $56, %rsp              # allocate 56 bytes on stack
    movslq    (%rsi), %rax         
    leaq    (%rax,%rax,2), %rax    # calculate address of first item
    leaq    (%rdi,%rax,8), %rax
    movsd    -24(%rax), %xmm0      # copy arr[0][0]
    movsd    %xmm0, (%rsp)
    movsd    -16(%rax), %xmm0      # copy arr[0][1]
    movsd    %xmm0, 8(%rsp)
    movsd    -8(%rax), %xmm0       # copy arr[0][2]
    movsd    %xmm0, 16(%rsp)
    movslq    4(%rsi), %rax
    leaq    (%rax,%rax,2), %rax    # calculate address of second item
    leaq    (%rdi,%rax,8), %rax
    movsd    -24(%rax), %xmm0      # copy arr[1][0]
    movsd    %xmm0, 24(%rsp)
    movsd    -16(%rax), %xmm0      # copy arr[1][1]
    movsd    %xmm0, 32(%rsp)
    movsd    -8(%rax), %xmm0       # copy arr[1][2]
    movsd    %xmm0, 40(%rsp)
    movq    %rsp, %rdi
    call    funcCPP@PLT            # call the C++ function
    addq    $56, %rsp              # release stack space
    ret

We have 6 ASM instruction to process the data and 21 instructions to access the data. Nope. Fail.

13.01.2019

C++ Guns: -Wshadow for constructor arguments

Filed under: Allgemein — Tags: — Thomas @ 14:01

-Wshadow is not that bad. We live in a modern world and don't use C if we can use C++ with a better defined scope and namespaces. So we can catch more errors on compile time.

First: what kind or errors can be detected with -Werror=shadow

Warn whenever a local variable or type declaration shadows another variable, parameter, type, class member, or whenever a built-in function is shadowed.

struct A {
    int i;              // note: shadowed declaration is here

    void func(int i) {} // error: declaration of 'i' shadows a member of 'A' [-Werror=shadow]
};

This kind or error also occurs if you define a constructor like following code. It defines a type Axis which has a title and a range. The range must be valid. The constructor of the Axis does something. So aggregate initialization is not possible.

struct Axis {
    string title;
    Range range;

    Axis(string title, Range range) 
    : title(title), range(range)       // error: declaration of 'range' shadows a member of 'Axis'
                                       // error: declaration of 'title' shadows a member of 'Axis'
    {
        // check if range is valid, error otherwise
    }
};

You can, of course, rename the constructor arguments or member variables. But that is not nice and results in either ugly code or a less proper interface.

The right solution is to split your type into two. One which only store data and another which does something with that data. Let's call the data-type AxisData. Try again:

struct Axis {
    struct AxisData {
        string title;
        Range range;
    };

    AxisData d;

    Axis(string title, Range range) 
    : d{title, range}
    {
        // check if range.isValid()
    }
};

No more errors! The extra variable d is no overhead, as you can see in my previous post.
To hide this implementation details from the programmer, provide some access function and switch from struct to class because Axis is now a type which manage it's data.

So this is the (final, add some references) implementation:

class Axis {
    struct AxisData {
        string title;
        Range range;
    };

    AxisData d;

public:

    Axis(string title, Range range) 
    : d{title, range}
    {
        // check if range.isValid()
    }

    string title() const {
        return d.title;
    }

    Range range() const {
        return d.range;
    }
};

C++ Guns: NOT another level of indirection

Filed under: Allgemein — Tags: — Thomas @ 11:01

The following code snips does NOT create a level of indirection. You can see it by introspect the assembler code.
So don't worry, start structure your data. See the next post for a practice example.

Example 1

Lets begin with a simple struct contains 3 members: double, float, int. Sum them up. And see what the compile generate

struct A {
    double d;
    float f;
    int i;
};

static_assert(sizeof(A) == 16);

auto func(const A& a){
    return a.d + a.f + a.i;
}

With the static assert sizeof you can be sure there is no padding in the data structure. The generate ASM code compiled with GCC 8.1 -O1 I comment it to make it easier for you.

func(A const&):
        pxor    %xmm0, %xmm0              # xmm0 = 0
        cvtss2sd        8(%rdi), %xmm0    # convert float to double and store it in xmm0
        addsd   (%rdi), %xmm0             # add double and the converted float to xmm0
        pxor    %xmm1, %xmm1              # xmm1 = 0
        cvtsi2sd        12(%rdi), %xmm1   # convert integer to double and store it in xmm1
        addsd   %xmm1, %xmm0              # add the already added float+double to the converted integer to xmm0
        ret                               # return xmm0 as result

You can see two conversions from int/float to double and two additions. You can also see the offsets. The double value has offset 0, the float value 8 bytes and the integer value (8+4)=12 bytes. This is quite straight. So add more code.

Example 2

This example create a nested struct Bdata inside struct B. Sum the values up again and see what the compiler generate.

struct B {
    struct Bdata {
        double d;
        float f;
        int i;
    };

    Bdata b;
};

static_assert(sizeof(B) == 16);

auto func2(const B& a){
    return a.b.d + a.b.f + a.b.i;
}

It is exactly, 100%, the same assembler code as example1 ! So i don't show it here again. The memory layout of struct B is the same as struct A, so the same ASM code is generated. No overhead by the extra variable b.

Example 3

In the next example a std::tuple is used to store the 3 values. The access is provided with the function std::get. It looks different. But wait...

struct C {
    std::tuple<double, float, int> c;
};

static_assert(sizeof(C) == 16);

auto func3(const C& a){
    return std::get<0>(a.c) + std::get<1>(a.c) + std::get<2>(a.c);
}

This time, the generated code is different

func3(C const&):
        pxor    %xmm0, %xmm0             # xmm0 = 0
        cvtss2sd        4(%rdi), %xmm0   # convert float to double and store it in xmm0
        addsd   8(%rdi), %xmm0           # add double and the converted float to xmm0
        pxor    %xmm1, %xmm1             # xmm1 = 0
        cvtsi2sd        (%rdi), %xmm1    # convert integer to double and store it in xmm1
        addsd   %xmm1, %xmm0             # add the already added float+double to the converted integer to xmm0
        ret                              # return xmm0 as result

But if you take a closer look, it is still the same! Only the memory layout has changed. First the integer, then the float, then the double. A std::stuple store it's values in reverse order! But the generated assembler code is 100% identical to the first example!

Example 4

In the last example I change the structure a little bit. But you can guess, the result is still the same.

struct D {
    double d;

    struct Ddata {    
        float f;
        int i;
    };

    Ddata x;
};

static_assert(sizeof(D) == 16);

auto func4(const D& a){
    return a.d + a.x.f + a.x.i;
}

See the next post for a practice example.

Older Posts »

Powered by WordPress