Да, всё верно. Тип вызова pascal отличается от stdcall противоположным порядком проталкивания аргументов. Я не хотел вдаваться в дебри различных типов вызовов, тем более, что сейчас в x64 многие из них уже не актуальны и унифицированы. Я лишь хотел этим проиллюстрировать откуда могли взяться слухи, что Windows писался на Pascal-е и что разработчикам на конференции задавали уже этот вопрос. Ответ был - Windows писался на С.
Windows писался на С. Единственное, что там могло быть от языка Pascal, тип вызова функций - pascal (когда стек зачищает сама вызываемая функция). Так отвечали на этот вопрос сами разработчики, которых спрашивали когда они приезжали на конференцию по Windows, 20 лет назад.
Я за Open Source, но от выводов в статье остаётся ощущение недосказанности. Логика подсказывает, что коммерческая компания не просто так тратит такую уйму сил и не просто для того, что бы поделиться своими наработками с сообществом. Я хотел бы ошибаться, но мне кажется, что на самом деле здесь больше решаются юридические тонкости и основная цель - защитить те или иные российские разработки от нападок извне или наоборот, а Open Source это только форма. Опять же, не вижу в этом ничего плохого, думаю так многие компании делают, но если это действительно так, то всё встает на своё место.
Вроде фильтры совсем базовые и простые но есть одно замечание и один совет. Замечание - gamma у вас работает неправильно. В нем граничные значения динамического диапазона [0, 255] (или [0.0, 1.0], если удобно) должны переходить сами в себя. Поэтому лучше сначала отнормировать значение яркости к [0.0, 1.0], а потом уже возводит в степень, затем обратно. Совет - так как фильтры точечные и каждый канал обрабатывается независимо, лучше считать их в таблице для всех значений динамического диапазона, [0, 255] например, и уже потом брать оттуда. Во-первых, это сделает скорость любого фильтра независимой от размера изображения и от сложности самого фильтра. Во-вторых, можно применять сразу несколько фильтров к таблице, а потом обсчитывать уже каналы изображения по ней. Это еще увеличит скорость обработки.
Современные процессоры устроены совсем не так, как в 80-х. Здесь, мне кажется, нужны замеры - насколько это медленнее. Нельзя же производительность в этом случае линейно, по количеству команд, мерить.
Отсутствие флагов это не убогость, а преимущество. Все современные архитектуры отказываются от флагов, т.к. это лишнее глобальное состояние, из-за которого в конвейере процессора даже не зависимые по данным команды начинают зависеть друг от друга, а это очень сильно мешает распараллеливанию выполнения.
Это будет в два раза медленнее, чем это возможно. Зачем так делать? Я с трудом представляю архитектуру, где не было бы adc, но даже, если это так, то перенос легко вычислить для полной суммы:
Он и так это без костылей оптимизирует: adc, sbb всякие, simd где может использует. О каких костылях речь ? На 32-х битной архитектуре 64-х битные целочисленные операции (long int) уже через вызовы отдельных функции выполняются, а как иначе, это костыли?
Потому-что на практике это не сложно сделать в виде сторонней библиотеки, а это принцип С++ - не тащить в компилятор то, что не нужно. Есть подход big integer - когда число не ограничено по длине и фактически его придется хранить в динамической памяти (привет Питону). В этом случае придется расплачиваться быстродействием. Есть подход long integer - когда необходимо работать с числами большими, чем поддерживаются процессором, но при этом фиксированной длины. Такие числа можно хранить на стеке и работать с ними почти также быстро как со встроенными, с поправкой на длину. Например, данный подход используется в Simple long integer math library for C++. Такие числа почти взаимозаменяемые со стандартными, не считая некоторых нюансов в автоматических преобразованиях.
Древнее оборудование? Метод близнецов - это же, по моему, buddy-allocator, который используется во всех операционных системах для выделения памяти. За счет скорости, простоты и неподверженности фрагментации, он идеально подходил для ядра ОС, где все страницы по замеру равны степени двойки. Он используется в ядре Linux, Windows, RTOS. Да и во многих современных Си-шных быстрых аллокатарах он используется как нижележащий слой, под аренами и прочими структурами. Так что, списывать его со счетов я бы не стал.
Странно, что ничего не сказано про выравнивание. х86, по умолчанию, позволяет доступ к не выровненным данным, тогда как многие другие платформы жестко требуют выравнивания.
Дополнения к 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. С его помощью можно привести не просто к другому типу, который является наследником, а к типу вообще находящемуся во вне иерархии того или иного класса.
Да, всё верно. Тип вызова pascal отличается от stdcall противоположным порядком проталкивания аргументов. Я не хотел вдаваться в дебри различных типов вызовов, тем более, что сейчас в x64 многие из них уже не актуальны и унифицированы. Я лишь хотел этим проиллюстрировать откуда могли взяться слухи, что Windows писался на Pascal-е и что разработчикам на конференции задавали уже этот вопрос. Ответ был - Windows писался на С.
Windows писался на С. Единственное, что там могло быть от языка Pascal, тип вызова функций - pascal (когда стек зачищает сама вызываемая функция). Так отвечали на этот вопрос сами разработчики, которых спрашивали когда они приезжали на конференцию по Windows, 20 лет назад.
Я за Open Source, но от выводов в статье остаётся ощущение недосказанности. Логика подсказывает, что коммерческая компания не просто так тратит такую уйму сил и не просто для того, что бы поделиться своими наработками с сообществом. Я хотел бы ошибаться, но мне кажется, что на самом деле здесь больше решаются юридические тонкости и основная цель - защитить те или иные российские разработки от нападок извне или наоборот, а Open Source это только форма. Опять же, не вижу в этом ничего плохого, думаю так многие компании делают, но если это действительно так, то всё встает на своё место.
Вроде фильтры совсем базовые и простые но есть одно замечание и один совет.
Замечание - gamma у вас работает неправильно. В нем граничные значения динамического диапазона [0, 255] (или [0.0, 1.0], если удобно) должны переходить сами в себя. Поэтому лучше сначала отнормировать значение яркости к [0.0, 1.0], а потом уже возводит в степень, затем обратно.
Совет - так как фильтры точечные и каждый канал обрабатывается независимо, лучше считать их в таблице для всех значений динамического диапазона, [0, 255] например, и уже потом брать оттуда. Во-первых, это сделает скорость любого фильтра независимой от размера изображения и от сложности самого фильтра. Во-вторых, можно применять сразу несколько фильтров к таблице, а потом обсчитывать уже каналы изображения по ней. Это еще увеличит скорость обработки.
Современные процессоры устроены совсем не так, как в 80-х. Здесь, мне кажется, нужны замеры - насколько это медленнее. Нельзя же производительность в этом случае линейно, по количеству команд, мерить.
Отсутствие флагов это не убогость, а преимущество. Все современные архитектуры отказываются от флагов, т.к. это лишнее глобальное состояние, из-за которого в конвейере процессора даже не зависимые по данным команды начинают зависеть друг от друга, а это очень сильно мешает распараллеливанию выполнения.
В любом случае, если в архитектуре нет команды adc, то будут накладные расходы, но не большие.
Вот например выхлоп для RISC-V:
Выглядит очень не плохо, на мой взгляд.
Это будет в два раза медленнее, чем это возможно. Зачем так делать? Я с трудом представляю архитектуру, где не было бы adc, но даже, если это так, то перенос легко вычислить для полной суммы:
Он и так это без костылей оптимизирует: 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 и целесообразности такого подхода на практике уберем за скобки)
В С++ всё это называет cross cast. Идея думаю понятна.