Pull to refresh
1
0
Калмыков Юрий @Videoman

Пользователь

Send message

Современные процессоры устроены совсем не так, как в 80-х. Здесь, мне кажется, нужны замеры - насколько это медленнее. Нельзя же производительность в этом случае линейно, по количеству команд, мерить.

Отсутствие флагов это не убогость, а преимущество. Все современные архитектуры отказываются от флагов, т.к. это лишнее глобальное состояние, из-за которого в конвейере процессора даже не зависимые по данным команды начинают зависеть друг от друга, а это очень сильно мешает распараллеливанию выполнения.

В любом случае, если в архитектуре нет команды adc, то будут накладные расходы, но не большие.

constexpr bool addc(uint32_t& value1, uint32_t value2, bool carry) noexcept
{
    value1 += value2;
    bool carry_new = value1 < value2;
    value1 += carry;
    carry_new = carry_new || (value1 < uint32_t(carry));
    return carry_new;
}

Вот например выхлоп для RISC-V:

 add     a0, a0, a1
 sltu    a1, a0, a1
 add     a2, a2, a0
 sltu    a0, a2, a0
 or      a0, a0, a1
 add     a0, a0, a2

Выглядит очень не плохо, на мой взгляд.

Это будет в два раза медленнее, чем это возможно. Зачем так делать? Я с трудом представляю архитектуру, где не было бы adc, но даже, если это так, то перенос легко вычислить для полной суммы:

value1 += value2;
bool carry = value1 < value2;

Он и так это без костылей оптимизирует: adc, sbb всякие, simd где может использует. О каких костылях речь ? На 32-х битной архитектуре 64-х битные целочисленные операции (long int) уже через вызовы отдельных функции выполняются, а как иначе, это костыли?

Потому-что на практике это не сложно сделать в виде сторонней библиотеки, а это принцип С++ - не тащить в компилятор то, что не нужно.
Есть подход big integer - когда число не ограничено по длине и фактически его придется хранить в динамической памяти (привет Питону). В этом случае придется расплачиваться быстродействием.
Есть подход long integer - когда необходимо работать с числами большими, чем поддерживаются процессором, но при этом фиксированной длины. Такие числа можно хранить на стеке и работать с ними почти также быстро как со встроенными, с поправкой на длину. Например, данный подход используется в Simple long integer math library for C++. Такие числа почти взаимозаменяемые со стандартными, не считая некоторых нюансов в автоматических преобразованиях.

Древнее оборудование? Метод близнецов - это же, по моему, buddy-allocator, который используется во всех операционных системах для выделения памяти. За счет скорости, простоты и неподверженности фрагментации, он идеально подходил для ядра ОС, где все страницы по замеру равны степени двойки. Он используется в ядре Linux, Windows, RTOS. Да и во многих современных Си-шных быстрых аллокатарах он используется как нижележащий слой, под аренами и прочими структурами. Так что, списывать его со счетов я бы не стал.

Ладно порядок, даже размер слова и байта пропустили.

Странно, что ничего не сказано про выравнивание. х86, по умолчанию, позволяет доступ к не выровненным данным, тогда как многие другие платформы жестко требуют выравнивания.

Всё верно. Это обратное следствие. Как всегда дилемма: скорость-память.

Также ещё из минусов make_shared: custom deleter не задать, aliasing constructor не вызвать.

Дополнения к std::shared_ptr и std::make_shared:
1. Без объяснения как на практике устроен std::shared_ptr, невозможно понять что за сценой присутствует control block и без std::make_shared у нас появиться дополнительный уровень косвенности и следственно лишнее выделение памяти под него. std::make_shared объединяет эти два выделения, делая создание и удалении быстрее.
2. std::weak_ptr естественно делает увеличение счетчика ссылок, но это отдельный счетчик, который также находится в control block-е.

На БК0010(01)/11/11M - на базе PDP-11 - не было целочисленных mul и div. op-коды были, но аппаратно команды были не реализованы. Даже сдвиг битовый был только одинарным: asl r0 - сдвинуть на один бит влево и вся роскошь. Чего уж тут говорить о Floating Point.

Под независимыми, я имел в виду «независимые» по коду, в конкретном модуле. Но их может связать сторонний по отношению к модулю класс, динамически. Многие просто об это забывают и из-за этого представляют себе возможности RTTI уж слишком примитивно.
Ну и что с того, что с наследует a и b? От этого связывание не становится статическим, а всё-равно происходит динамически в рантайме, при работе программы. Пофантазируйте: определения с у вас вообще может не быть, если подлинкована либа или подгружена dll с неизвестным вам классом.
(вопросы совместимости RTTI и целесообразности такого подхода на практике уберем за скобки)
Ну например, можно рассмотреть простейшую реализацию COM на dynamic_cast<>:
#include <iostream>
#include <memory>
#include <string>

// a.h include

struct a_base {

    virtual std::string who() = 0;
    virtual ~a_base() = default;
};

struct a : a_base {

    std::string who() override { return "a class"; }
};

// b.h include

struct b_base {

    virtual std::string who() = 0;
    virtual ~b_base() = default;
};

struct b : b_base {

    std::string who() override { return "b class"; }
};

// c.h include

struct c : a, b 
{
};

// somewhere else

b_base* create_b()
{
    return new c;
}

// main

int main()
{
    b_base* b = create_b();
    std::cout << b->who() << "\n";

    a_base* a = dynamic_cast<a_base*>(b);

    if (a != nullptr) {

        std::cout << "receive a_base from pointer to b_base\n";
        std::cout << a->who() << "\n";

    } else {

        std::cout << "can't receive a_base from pointer to b_base\n";
    }

    delete b;
    b = nullptr;

    return 0;
}
Обратите внимание, что иерархия класса a и класса b могут друг о друге ничего не знать, а также оба вообще могут не знать, что в программе есть класс, который наследует их оба. Однако это не мешает dynamic_cast отслеживать эту связь в рантайме и получать через b_base указатель на a_base.
В С++ всё это называет cross cast. Идея думаю понятна.
Динамик каст делает больше чем просто енамчик, там сохраняется информация о иерархии. Например с вашей реализацией вы не сможете привести к другой базе, то есть это не точно тот тип, но находящийся в той иерархии
На самом деле мало кто задумывается как работает dynamic_cast. С его помощью можно привести не просто к другому типу, который является наследником, а к типу вообще находящемуся во вне иерархии того или иного класса.
Всё верно. Как и многие конструкции в С++, if constexpr также не является «серебряной» пулей и не предназначена для замены механизма перегрузки функций. Следовательно её нужно использовать с головой, особенно при проектировании библиотек, которые будут использоваться сторонними программистами.
Действительно, если написать общий шаблон c диспетчеризацией, условно, что-то типа:
template <typename type_t>
type_t func(type_t arg)
{
    if constexpr (std::is_same_v<type_t, type1> {
        ...
    } else if constexpr (std::is_same_v<type_t, type2> {
        ...
    } else ...
}
такой подход, создаст массу проблем с расширением, особенно если он используется во внешнем API.
С другой стороны, для того, что бы заменить кучу бойлерплейта, построенного на частичных перегрузках классов внутри реализации, это самое то:
template<uint_t sidx, typename variant_t>
inline void print_variant(const variant_t& data, size_t idx)
{
    if constexpr (sidx < std::variant_size_v<variant_t>) {

        if (idx == 0)
            format(std::get<sidx>(data));
        else
            print_variant<sidx + 1>(data, idx - 1));
    } else
        throw v2::Exception("Variant index is out of range.");
}
— выводим значение std::variant по известному индексу в рантайме.
Полностью согласен, но хотел бы еще более конкретизировать:
1. Если требуется несколько сложное состояние, инвариант, в этом случае требуется объект, который и будет поддерживать это состояние через внешний интерфейс, в строгой согласованности.
2. Также объект может быть полезен, если некие данные абсолютно независимы друг от друга, могут меняться не согласованно, но по логике очень часто передаются вместе. Частный случай — структура.

Во всех остальных случаях функции предпочтительнее и гибче, на мой взгляд.
Да, только байт в адресе IPv6 — 16 ;)
Более того, начиная с С++20 битовое представление signed integers фиксировано стандартом как дополнение до двух (two's complement).
However, all C++ compilers use two's complement representation, and as of C++20, it is the only representation allowed by the standard, with the guaranteed range from -2^(N-1) to +2^(N-1) — 1 (e.g. -128 to 127 for a signed 8-bit type).

8-bit ones' complement and sign-and-magnitude representations for char have been disallowed since C++11 (via CWG 1759), because a UTF-8 code unit of value 0x80 used in a UTF-8 string literal must be storable in a char element object.

Information

Rating
5,247-th
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity