25 listopada 2012

Benchmark framework - zużycie pamięci - valgrind Massif

Moje poszukiwania, mechanizmu pozwalającego zdobyć informację na temat maksymalnego zużycia pamięci działającego programu zaowocowały spotkaniem z tym oto narzędziem: http://valgrind.org/docs/manual/ms-manual.html. Z pewnością są lepsze rozwiązania, ale dla moich potrzeb to jest satysfakcjonujące, przynajmniej w tej chwili.
Zaczniemy do przykładu, którym ma zając się valgrind.
#include <iostream>
#include <vector>

using namespace std;

template <typename T>
T * alloc()
{
    cout << "size " << sizeof(T) << endl;
    return new T[1000000];
}

void alloc_dealloc()
{
    int * a = alloc<int>();
    delete [] a;
}

int main()
{
    alloc_dealloc();
    vector<char> v(100000);
    alloc<long long>();
    alloc_dealloc();
    for (int i = 0; i < 300; ++i)
        alloc_dealloc();

    return 0;
}
Taki projekt trzeba skompilować, generując informacja dla debuggera.
$ g++ -g main.cpp
Teraz nasz program można poddać analizie
$ valgrind --tool=massif --time-unit=B --stacks=yes --massif-out-file=mem.out ./a.out
$ ms_print mem.out
* pierwsza linijka włącza massif do analizy (--tool=massif),
* "jednostka czasu" używana przez profiler - nie jestem pewny jak to rozumieć.
The time unit used for the profiling. There are three possibilities: instructions executed (i), which is good for most cases; real (wallclock) time (ms, i.e. milliseconds), which is sometimes useful; and bytes allocated/deallocated on the heap and/or stack (B), which is useful for very short-run programs, and for testing purposes, because it is the most reproducible across different machines.
W każdym razie, jest zalecana do małych programów i w celach testowych, więc to czego szukam.
* informuje, że interesuje nas też pamięć, która zostanie zaalokowana na stosie (--stack=yes),
* jak będzie nazywał się surowy plik z analizą zużycia pamięci (--massif-out-file=mem.out),

Massif rozdziela proces zbierania danych od ich prezentacji, w ten sposób w przyszłości mogą pojawić się nowe metody na ich prezentowanie. Plik, gdzie zostały zgromadzone dane poddajemy działaniu ms_print.
Number of snapshots: 72
 Detailed snapshots: [1 (peak), 8, 27, 32, 37, 42, 47, 55, 65]

--------------------------------------------------------------------------------
  n        time(B)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
  0              0                0                0             0            0
  1     21,983,976       12,103,920       12,100,000         3,576          344
Mnie interesują najbardziej dwie linijki z całego raportu. Pierwsza to numer snapshota, który zanotował największy "peak" (1). A druga do dokładne informacje z tego snapshota, czyli całkowite zużycie pamięci, w tym przypadku 12103920 bajtów.

23 listopada 2012

Boost.Bimap

Szybkie wprowadzanie do bimapy: http://www.boost.org/doc/libs/1_42_0/libs/bimap/doc/html/boost_bimap/one_minute_tutorial.html
Bimap-a mapuje jeden klucz na drugi. W mapach klucze muszą być niepowtarzalne, bimapach każdy występujący w nich klucz i wartość musi być niepowtarzalny (wartość też jest kluczem).
#include <iostream>
#include <boost/bimap.hpp>
#include <boost/assign/list_of.hpp>
#include <boost/foreach.hpp>

using namespace std;

template <typename T>
void show(T m)
{
    for (typename T::iterator it = m.begin(); it != m.end(); ++it)
        cout << "BiMap[" << it->first << "] = " << it->second << endl;
}

int main()
{
    typedef boost::bimap<int, string> BMap;
    BMap bm = boost::assign::list_of<BMap::relation>(1, "aaa")
                                                    (1, "zzz")
                                                    (2, "bbb")
                                                    (3, "ccc")
                                                    (4, "aaa");

    show(bm.left);
    show(bm.right);

    return 0;
}
W przykładzie nie możemy do bimapy wprowadzić nowych kluczy bo już wcześniej zastały wstawione.
Linijka 19 - istnieje już klucz 1 (linijka 18)
Linijka 22 - istnieje już klucz "aaa" (linijka 18)
Aby wyszukać odpowiednią wartość, musimy wiedzieć co w danej chwili jest kluczem. W tym celu należy skorzystać z obiektów "left" (klucz1 -> klucz2) lub "right" (klucz2 -> klucz1).
Wyniki:
BiMap[1] = aaa
BiMap[2] = bbb
BiMap[3] = ccc
BiMap[aaa] = 1
BiMap[bbb] = 2
BiMap[ccc] = 3

7 listopada 2012

Struct - inicjalizacja zerami i -Wextra (część II)

A co gdyby, do inicjalizacji użyć pustych klamerek? Na stronie http://www.ex-parrot.com/~chris/random/initialise.html można przeczytać:
It would be neater to be able to say struct foo bar = {}, and some compilers (e.g. GCC) permit this, but the ANSI grammar requires an initialiser list to be non-empty.
Czyli generalnie nie można, bo niezgodne ze standardem. Trzeba więc przekonać się na własnej skórze.
struct Stru2 {
    int c;
    int d;
};

struct Stru1 {
    int a;
    Stru2 b;
};

int main()
{
    struct Stru1 aaa = {};
    return aaa.b.c;
}
Mimo, że GCC nam na to pozwala, to chciałem wymusić zgodność ze standardem ANSI, (chociaż bardziej mnie interesuje co się stanie w C++, a nie w C).
gcc -Wall -pedantic -ansi main.cpp
Hmm, obyło się bez protestów. Jeszcze jedna próba tym razem z -Wextra
gcc -Wall -Wextra -pedantic -ansi main.cpp
Tutaj się już coś dzieje. Ciekawe sprawa, bo nawet gdy skorzystam z zapisu "Stru1 aaa = {0}", to dalej są protesty o nie zainicjowane zmienne.
main.cpp:36:25: warning: missing initializer for member ‘Stru1::a’ [-Wmissing-field-initializers]
main.cpp:36:25: warning: missing initializer for member ‘Stru1::b’ [-Wmissing-field-initializers]
Zasięgnąłem opinii na stackoverflow.com i z odpowiedzi wynika, że -Wextra, martwi się bardziej niż pozawala na to standard i choć wszystko zostanie zainicjalizowane zerami, to teraz kompilator chciałby, żebyśmy sami zainicjalizowali każde pole z osobna. Jeszcze rzut oka, na to co wygenerował kompilator. Wszystko na zero ;)
.LFB0:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    subl    $16, %esp
    movl    $0, -12(%ebp)
    movl    $0, -8(%ebp)
    movl    $0, -4(%ebp)
    movl    -8(%ebp), %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc

4 listopada 2012

[C++] "Variadic template" oraz "fold expression"

Variadic template

W C++11 do szablonów można przekazywać nie tylko typy ale też wartości (non-type template parameter). Przykład poniżej pokazuje obliczanie średniej w tych dwóch wersjach. Dodatkowo pojawia się nowy sizeof...(), który potrafi powiedzieć z iloma parametrami mamy do czynienia.
#include <iostream>

using namespace std;


template <typename T>
int add(T last) {
    return last;
}

template <typename T, typename... Nums>
int add(T first, Nums... nums) {
    return first + add(nums...);
}

// non-type template parameter
template<int first, int... Nums>
double average1() {
    return (static_cast<double>(first) + add(Nums...)) / (sizeof...(Nums) + 1);
}

// type template parameter
template <typename T, typename... Args>
double average2(T first, Args... args) {
    return (static_cast<double>(first) + add(args...)) / (sizeof...(Args) + 1);
}

int main()
{
    cout << average1<1, 2, 3, 4>() << endl;
    cout << average2(1, 2, 3, 4) << endl;
}
Wynik:
2.5
2.5

Fold expression

W C++17 wprowadzono fold expression, które pozwala na stosowanie operatorów dla pakietu parametrów. W przykładzie ze średnią, nie trzeba więc stosować rekurencji. Trochę dziwne jest to że kompilator wymaga dwóch nawiasów, jeżeli chcemy rzutować wynik sumowania do double.
#include <iostream>

using namespace std;


template <typename... Args>
double average(Args... args) {
    return static_cast<double>((args + ...)) / sizeof...(Args);
}

int main()
{
    cout << average(1, 2, 3, 4) << endl;
}
Wynik:
2.5

Zadanie domowe

Na GoingNative 2012 Andrei Alexandrescu, przedstawił swój wykład "Variadic Templates are Funadic". Andrei bardzo lubi templejty, co widać ;). Variadic template mają w zamyśle być następcą/lepszą wersją wielokropka ("...") obecnego od czasów C, korzysta z niego np. printf(), z którego każdy lubi korzystać. Do lepszego zrozumienia mechanizmu rozwijania variadic template, autor zaproponował zadanie domowe - produkt kartezjański. Ale, żeby się do niego zabrać, trzeba było zrozumieć wykład (sic!), z pomocą przyszedł poniższy link, gdzie, ktoś rozwinął w kod, to co przedstawił Andrei na slajdach:

Wpierw, trzeba zmusić kompilator/CMake (gcc 4.6.3) do pracy z nowym C++11, więc poniżej modyfikacja CMakeList.txt
project(variadic_cpp11)
cmake_minimum_required(VERSION 2.8)
aux_source_directory(. SRC_LIST)
add_executable(${PROJECT_NAME} ${SRC_LIST})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
A teraz moje rozwiązanie - tworzą się pary wartości trzech typów i działa tylko dla wersji "A x A". W linijce 25 rozwinięcie, które było potrzebne, aby to osiągnąć. Aby można było przemnożyć między sobą dwa zbiory, trzeba by chyba skorzystać z nowego std::tuple. Ale w to się jeszcze nie wgłębiałem.
#include <iostream>
using namespace std;

template<class T>
int cartesianMult(T t)
{
    return 0;
}

template<class T, class U, class... Us>
int cartesianMult(T t, U u, Us... us)
{
    cout << endl << " (" << t << ", " << u << "),";
    cartesianMult<T, Us...>(t, us...);
    return 0;
}

template <class... T>
void fun(T...) {}

template <class... Ts>
void cartesianProduct(Ts... ts)
{
    cout << "{";
    fun( cartesianMult(ts, ts...)... );
    cout << endl << "}" << endl;
}

int main()
{
    cartesianProduct(1, 3.14, "abc");
    return 0;
}
A oto wyniki i ciekawostka. Typy, a więc i wartości brane są w odwrotnej kolejności, niż były wkładane do funkcji.
{
 (abc, 1),
 (abc, 3.14),
 (abc, abc),
 (3.14, 1),
 (3.14, 3.14),
 (3.14, abc),
 (1, 1),
 (1, 3.14),
 (1, abc),
}

1 listopada 2012

Kompilacja kodu assemblera i łączenie z g++

Jak zmusić kompilator do wygenerowania kodu assemblera, by go zmodyfikować i ponownie użycia do kompilacji?

Magiczny ciąg komend:
Po modyfikacji kodu assemblera, generujemy object-file, z którego ponownie skorzysta g++ (linijka 2).
g++ -S main.cpp 
as main.s -o main.o
g++ main.o -o main
Nasz kod, który chcemy zobaczyć w assemblerze:
#include <stdio.h>
int main()
{
    printf("Hello world\n");
    return 0;
}
Nasza modyfikacja w kodzie assemblerze (main.s):
.file    "main.cpp"
    .section    .rodata
.LC0:
    .string    "Hello Kitty ;)"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushl    %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    movl    $.LC0, (%esp)
    call    puts
    movl    $0, %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
    .section    .note.GNU-stack,"",@progbits
Wynik:
Hello Kitty ;)