template template

Още едно интересно парче код, което мернах из хаоса 🙂
То е за template<> template<>, ама не много в контекста на Alexandrescu.

Ето как изглежда синтаксиса, когато искаме да специализираме темплейтен метод на темплейтен клас (тествано, че се компилира в gcc и msvc).

Такива парчета код във ФМИ нямаше …

aligned hack++

Вчера на работа се сблъсках с един интересен хак, който бях забравил напоследък …

В езиците от високо ниво не можем да менажираме ръчно памета. Всичко си става автоматично. А напоследък все повече мразя автоматичните неща, защото цената им понякога е твърде висока.

В С++ например, можем експлицитно да укажем как да бъде подравнена паметта, която заделяме (на 1, 2, 4 или друга степен на 2). Oт гледна точка на програмиста, ако паметта е подравнена на 4 байта например, това означава че pointer-ите винаги по модул 4 ще дават 0 (макар в стандарта да не е специфицирано точно така, поведението днес е такова).
Подравняването е важно.

В днешно време повечето компилатори подравняват паметта самички и без да им казваме на колко. Но имат и разни атрибути, с които можем да си изберем ние.

Ето пример :

Сега е време за един що-годе известен хак.
Лесно можем да проверим, че най-младшите няколко бита, от стойноста на поинтърите към памет винаги са 0.

Причината е проста.
Още по-точно, можем да изкажем твърдението, че първите log2(n) – 1 брой бита, винаги са 0, когато ползваме подравнена памет (а тя е такава често дори без да сме указали експлицитно).

Мисля, че не е трудно да го докажем : поинтъра n се дели на число k без остатък, к е точна степен на 2 – да речем, i-та степен на 2. Тоест, n се дели на 2 i-пъти без остатък. Всяко делене можем да представим като right-shift на битове. Така, ако сме имали 1 на някое от местата 0…i-2, да речем на място p, след p шифтове ще получим нечетно число (p ще е стигнало позиция 0), а то при делене с 2 ще има остатък.

Сега, излиза така че няколко бита от стойноста на поинтъра са 0 ако ползваме подравнена памет. Всъщност, съвременните процесори обичат подравняване на около 128 бита. И двата компилатора, с които тествах автоматично правеха това (gcc & clang)
Излиза, че няколко бита във всеки поинтър са си 0 винаги. И като лоши хакери, ние трябва да се възползваме от това :).

Сега, да речем че ни трябва следната структура :

На моят компилатор (clang3.1 x64) sizeof(Node) е 24 .. 2 ptrs x 8 = 16; Oстаналите 8 идват от автоматичното подравняване заради 2-та булеви флага. Сега, можем да напишем кода така, предполагайки че данните са подравнени :

Вече sizeof(Node) е 16, което е 33% спестена памет. За няколко милярда такива node-a бая гигабайта ще се съберат 🙂 А можем всъщност и доста повече информация да компресираме по този начин в указателите.

Вероятно няма да работи на всякакви архитектури, но за x86 e супер.

п.с. знам за не една и две програми, ползвали този хак; тоест, доказал се е в битка. Компресираите си здраво и наздраве 🙂

dynаrray c++11

Попадна ми едно предложение за новия стандарт (С++14), масив чиито размер не може да се променя след като бъде създаден, но пък размера се определя runtime – dynarray.
С две думи – идеята е да ползва stack memory (когато поисканата памет не е твърде много), а в другите случаи да се ползва heap memory. Целта е и всякакви други контейнери го ползват, а резултата ще е повишена ефективност.
Разбира се, всичко това да се случва само в рамките на scope на функция (защото това е lifetime-a на stack memory).

Възможно приложение на тази структура : да речем имате multithreaded scanline render engine и всяка нишка има нужа от pixel buffer. Ако картинката е малка и се rend-ва бързо можем да ползваме stack mem, ако е голяма и се ренди бавно – heap (overhead-а от new[] ще е пренебрежим). (а можем да алокираме pixel buffer преди rend-a и да ползваме него, но да речем че не ни се занимава с това).

Сега, възниква въпроса, можем ли да имплементираме dynarray в С++11.
Ето oпитите, които направих и заключенията до които стигнах.


Единственото, което не е много ясно е как да алокираме динамично памет на стека.

Опция 1.
Заделяме костантно памет в обекта, ползваме нея ако стига, в противен случай – heap-a.

Това работи. Проблема е, че дори и да ползваме heap memory, пак имаме overhead-a от maxBytesOnStack на стека. А това не е приемливо, защото се борехме за performance и memory.

Опция 2.
Заделяме споделене памет за всички dyn_array обекти, когато ползваме stack memory търсим в нея с custom allocator. Ако няма място – ползваме heap.

Проблема тук е, че трябва да подържаме допълнителна структура от данни, за да може customAllocator-a да работи, имаме overhead от търсенето в него. От време на време трябва да извършваме “поддръжката” му и да сливаме парчетата които са едно до друго, може да получим фрагментация, имплементацията е сложна. Та трябва да има и по-добър начин.

Oпция 3.
Можем да ползваме функцията alloca(size_t bytesCount), която прави точно това – алокира памет на стека. Тази памет още повече не се освобождава при излизане от scope, а чак при излизане от функцията, която е направила извикването. Тоест, този код crash-ва със stackoverflow :

Ето и какво бихме написали с alloca

Това компилирано с clang3.1 и с прости тестове работи. Но работи заради чист късмет – dyn_array() освен конструктор е и функция, и в (1) заделената памет с alloca() спира да я има.

Опция 4.
Да се опитаме да ползваме С++11 за да излъжем компилатора 🙂
Ще оставим една член-данна на класа от тип функция, а нея ще инициализираме с ламбда, която заделя статична памет и връща указател към нея (tnx to Komitov за идеята).

Сега, ясно е че имаме overhead oт това че заделяме maxBytesOnStack всеки път, вместо elementsCount*sizeof(T). А и не можем да зачистим статичната памет (2).
Опитите да го излъжа с връщане на const&, ползването на extension-a на gcc (int i = 42; int arr[i]) и др. не завършиха успешно.
Но пък написах този ред код докато пробвах всякаквите там неща 🙂
std::function < const T(&())[maxBytesOnStack] > (lambda която връща ref към С масив).
Все още обаче имам някакви надежди, че е възможно да се напише hack с такава ламбда.

Опция 5.
Опция 5. е Опция 3., с тази разлика че статичната памет се заделя където трябва (в извикващата функция). Освен това кода е по обширен

Можем да го ползваме почти като нормална функция :

Като заключение,
(1) std::dynarray ползвайки С++11 изглежда не може да се направи (имплементацията в clang e cheat&fake – ползва единствено malloc, това не нарушава стандарта, ама все пак ..), трябват промени в компилатора и/или да се пише на assembler.

(2) В С++11 един прост конструктор отнема над 200 символа – сигурен съм че с python с толкова мога да вдигна web server :). Изглежда пичовете от С++ комитета пружинките на клавиатурата за нищо ги нямат.

(3) Може би е добре да си отворя един github 🙂

Design Pattern

Както в повечето ИТ компании и в Chaos имаме собствена библиотечка.
Книжката по-долу обаче получи специално внимание, и бе покръстена така веднага щом я докараха :D.

Ако трябва да сме честни, това четивo може и да е полезно. Чувал съм да го ползват като не-толкова-добра ракета за тенис на маса, например.


п.с. every problem can be solved using one more abstraction layer, except too many abstraction layers … or javascript-like performance.

Lets talk C++

Има едно видео за скриптовите езици, което бая ме забавлява :).
Аз се порових, stackoverflow и из стар код. Та.


C++;

Резултат: 14 … WAT.

strlen връща size_t, което бидейки unsigned по стандарт, каства -1 до unsigned, което е всъщност едно доста голямо число :).
if (strLength < -1.f) обаче работи както трябва (понеже по стандарт се каства до най-големия тип с плаваща запетая, ако има такъв).


C++; 11;

аuto като feature ползва механизма на template-ите за определяне на типа. Звездичката, която седи там е безсмислена, но може да я има.


C++;
На първият ред декларираме int* ptr, и int value = NULL – което е все едно:
int* ptr;int value = 0; … Неинициализиран ptr и int със стойност 0. nullptr решава част от недоразумението.


C++;

Недефинирна резултат … Cтандарта указва какво влияние имат битовите операции върху signed типове. В по-голямата си част са undefined, а в другата е доста оплетено.


C++;

Това се компилира. То е етикет за goto с име http, а след него има коментар code-bg.com


C++;

Унарният оператор + създава копие :).


C++;

std::cout няма предефиниран оператор с volatile .. А понеже всеки указател може да се конвертира до bool, в случая това се случва ..


Останалите за друг път.
А за бонус, този ред:

Const life

Натъкнах се на поредно ново 20 вчера 🙂
Една от често срещаните грешки, когато човек започва да пише на С++ е код като този :

Псевдоним към temporary, то няма да съществува след като функцията е била извикана и това ще бъде undefined behavior (най-вероятно кофти краш).
Лесно и ясно, но.

В (1) изглежда вземаме ref към temp, и в (2) следва да имаме проблем. Това обаче не е съвсем така.
С++ някъде из стандарта, ясно указва че const следва да удължи живота на temporary-тата 🙂 Без static, без копия.
Така, не само можем да изпълним (2), но (3) също работи.
(4) създаваме нов LoggedString, само за да видим по-късно кога неговият деструктор ще се извика.
Нещо повече, и двата компилатора, които имам дори не ми позволяват да компилирам (6).
Другото което видях, е че не са малко на брой редовете, които трябва да бъдат изписани за да имаме прост клас, дефиниран с всички необходими конструктори …
Output :

C(++)onstructors

Ето една проста С++(98) конструкция, която не рядко се бърка. Хора ползващи Java или друг език в миналото си, и С++ в настоящето си се сблъскват с това :

На пръв поглед това не трябва да се компилира. Имаме контруктор, който вика друг конструктор (и ползваме C++ < 11). А това не е разрешено в тази версия на езика (макар не всички да го знаят). За да е тотално объркването обаче, всеки компилатор успява да го компилира без грешки. Този код работи и не нарушава стандарта. Не прави обаче съвсем това, което човек би очаквал.

Причината за това е, че Foo() не вика конструктора за обекта, който се конструира във Foo(int), а създава temporary object (без име) !

За по ясно, вместо Foo(); там може да пише int(); … така ще сме създали един int на стека, който както и Foo(); ще спре да съществува след като излезем от scope-а на Foo(int). Може да стане по-ясно, ако добавим деструктор, в който има само printf.

Можем обаче да накараме нещата да сработят .. Ако ползваме placement new, бихме могли да извикаме конструктура върху обекта, който очакваме. Ето така :

Показаното по-горе работи (поне в gcc, clang && cl). Но не е съвсем гарантирано, че ще работи навсякъде и винаги. Добрият стар init() метод е по-подходящ тогава.

 

 

Some cache misses

По повод лекцията на Бярне от началото на годината, и едно леко почесване по главата по време на работа преди няколко дни на тема, лоши ли са кеш-мисовете и имат ли те почва у нас, реших днес да проверя от първа ръка 🙂

Ето една проста задачка с интересни резултати 🙂

Имаме масив с N целочислени числа от 0 до N-1, записани в произволен ред.

Искаме да намерите i-тото от тях, да гo изтрием, но да запазим реда им.
Но вместо i-тото, да вземем да изтрием всички от 0 до N-1 и да ги изтрием едно по-едно .. Питаме се каква структура от данни да ползваме, така че това да се случи най-бързо.

Та, обичайните кандидати .. линеен масив и свързан списък. Свързаният списък изглежда по-обещаващ, вместо да копираме всеки път, само ще меним разни поинтъри.

Правим тестове със std::vector, std::list, custom vector && custom list (кода се намира по-долу)
За да е по-къса цялата лудория, имплементирал съм само методите, които са нужни за демонстрацията (няма деструктури и т.н.). Съобщавайте ако има major грешки в коментарите по-долу.

Компилатора е LLVM4.1 с всички оптимизации, процесора – Core2Duo 2.26ghz (P8400), MacOS 10.8.1. Структурите custom vec && custom list всъщност не са от значение, тъй като най-често ни интересуват резултатите от std (написах ги от носталгия, след като завърших хората спряха да ме карат да ги пиша непрекъснато)
Ето и резултатите (lower is better). Замерванията са правени по 3 пъти, показано е средно аритметичното:

По всичко личи, че моя вектор не е много бърз. Това е добре, имплементацията не изглежда много грешна, но пък е бавна. +1 за STL-a 🙂
И понеже никой не харесва таблици, ето ги в арт вариант :

И понеже не се виждат тези в началото заради скалирането, ето ги и тях :

Не ми се мисли, разни езици от високо ниво, пазещи програмистите от “странични ефекти на кода {}”, ползващи за пестене на памет разни линкчета насам-натам колко бързо работят 🙂

Изглежда че vector е the silver bullet (и не е вярно, че ако има честа нужда от махане на елементи в средата list-a върши работата екстра), а и че за тези неща трябва да се говори в университета, че срамота така завършваме неуки …