Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.77/22: Рейтинг темы: голосов - 22, средняя оценка - 4.77
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30

Избыточное копирование объекта при реализации оператора умножения и оператора присваивания

21.03.2016, 18:43. Показов 5113. Ответов 84
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Есть класс работы с матрицами. Есть операция умножения матриц, описанная как оператор класса. В данном коротком примере я просто моделирую ситуацию. Реальное наполнение класса принципиальной разницы не играет, нужно просто понимать, что оператор умножения будет иметь большой код, оператор присваивания в реализации тоже будет не пустым. Эти места в примере засвечены комментариями

C++
#include <cstdio>
 
class Matrix
{
  private:
    // внутренние данные матрицы
 
  public:
    Matrix ();
    Matrix (const Matrix&);
    ~Matrix ();
 
    Matrix& operator=(const Matrix& a);
    Matrix operator*(const Matrix& a) const;
};
 
Matrix::Matrix ()
{
  printf ("Matrix ()\n");
};
 
Matrix::Matrix (const Matrix&)
{
  printf ("Matrix (const Matrix&)\n");
};
 
Matrix::~Matrix ()
{
  printf ("~Matrix ()\n");
};
 
Matrix& Matrix::operator=(const Matrix& a)
{
  printf ("operator=(const Matrix&)\n");
 
  // тут как бы код, копирущий данные из "a" в "this"
 
  return *this;
}
 
Matrix Matrix::operator*(const Matrix& a) const
{
  printf ("operator*(const Matrix&)\n");
 
  Matrix result;
 
  // тут как бы код, умножающий "a" и "this" и записывающий
  // данные в "result"
 
  return result;
}
 
Matrix a, b, c;
 
int main (void)
{
  printf ("------------------\n");
  a = b * c;
  printf ("------------------\n");
}
Исполнение:

Code
$ g++-4.8 t.cc
$ ./a.out
Matrix ()
Matrix ()
Matrix ()
------------------
operator*(const Matrix&)
Matrix ()
operator=(const Matrix&)
~Matrix ()
------------------
~Matrix ()
~Matrix ()
~Matrix ()
Нас интересует только фрагмент печати внутри функции main (т.е. между палками)

Посмотрим на ассемблерный код. Компилирую с опцией -fno-inline, чтобы оставаться с короткой программой и не разводить геморрой по борьбе с inline'ом со стороны компилятора. В общем случае тела операторов класса Matrix будут в отдельном файле и НЕ будут доступны для inline'а. Опцию -fno-exceptions подаю, чтобы было меньше мусора в коде

Code
$ g++ t.cc -O2 -fno-inline -fno-exceptions
$ cat t.s
...
    leal    -9(%ebp), %ebx
    subl    $32, %esp
    movl    %ebx, (%esp)
    movl    $c, 8(%esp)
    movl    $b, 4(%esp)
    call    _ZN6MatrixmlERKS_  <- operator*
 
    subl    $4, %esp
    movl    %ebx, 4(%esp)
    movl    $a, (%esp)
    call    _ZN6MatrixaSERKS_  <- operator=
...
Таким образом мы видим, что построился код, схематично эквивалентный следующему примеру на Си. На всякий случай напоминаю, что с точки зрения семантики C++ выражение "a = b * c" трактуется как "a.operator= (b.operator* (c))"

C
typedef struct { /* внутренности */ } Matrix;
 
extern void operator_assign (Matrix *this, const Matrix *value);
extern void operator_mul (Matrix *result, const Matrix *this, const Matrix *operand2);
 
Matrix a, b, c;
 
int main (void)
{
  Matrix tmp;
 
  operator_mul (&tmp, &b, &c); /* tmp = b.operator* (c) */
  operator_assign (&a, &tmp);  /* a.operator= (tmp) */
}
Мы видим, что в этом коде делается ненужное действие в виде лишнего оператора присваивания. Для умножения матриц эффективный код на Си выглядел бы как

C
operator_mul (&a, &b, &c);
т.е. без заведения промежуточной переменной и дополнительного копирования (которое представляет собой ненужные накладные расходы)

Вопрос. Как правильно написать текст на C++, чтобы получить код, эквивалентный вышеприведённой эффективной реализации на C? Оставаясь при этом в объёме стандарта C++98. Оставляя исходник в понятном и читабельном виде, без использования извращений типа семиэтажных шаблонов и т.п.
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
21.03.2016, 18:43
Ответы с готовыми решениями:

Неправильная работа оператора присваивания после работы оператора суммирования
Доброго времени суток. У меня есть класс вектор class TVector {//ewde public: TVector(); //Vector(Vector &amp;v); ...

От каких ошибок страхует Const при перегрузке оператора присваивания
Здравствуйте. Вопрос имею теоретический. В классе A перегружается оператор присваивания, объявление выглядит так: const A operator =...

Почему при перегрузке оператора присваивания, возвращаемое значение не константно?
Почему при перегрузке оператора присваивания, возвращаемое значение - someClass &amp; operator=(const someClass&amp; rhl), а не const...

84
 Аватар для Nosey
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
22.03.2016, 18:15
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от ct0r Посмотреть сообщение
Да я фиг знает. Мерить надо. Я не умею считать такты с учетом конвейеров и суперскалярности, с учетом зависимостей по данным, с учетом кол-ва обращений к памяти и кол-ва промахов кэша и тд и тп. Интуиция тут не помощник. Ну если кто-то умеет, то ок.
Компилятор, когда оптимизирует, на кол-во инструкций точно не смотрит.
Да, однозначно согласен, и в этом случае - участвуют 32 флоата + временные "переменные", в мой кэш это точно влезет + отсутствие каких либо ветвлений, я с трудом догадываюсь что может показывать такую разницу кроме хорошего работающего илайнинга, и готов рискнуть в данном случае показаться идиотом мерящим качество кода количеством инструкций

Цитата Сообщение от Evg Посмотреть сообщение
Мне не интересно смотреть на исходник, в котором используется какой-то непонятный самопальный Math.h
Да рово любой math.h, от него мало что зависит, но и пожалуйста, можете проверить насколько хорошо я помню школу:
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#ifndef MATH_H_
#define MATH_H_
 
namespace ru
{
    namespace math
    {
        struct Matrix4
        {
        public:
 
            Matrix4()
            {
                mat[0] = 1.0;
                mat[4] = 0.0;
                mat[8] = 0.0;
                mat[12] = 0.0;
                mat[1] = 0.0;
                mat[5] = 1.0;
                mat[9] = 0.0;
                mat[13] = 0.0;
                mat[2] = 0.0;
                mat[6] = 0.0;
                mat[10] = 1.0;
                mat[14] = 0.0;
                mat[3] = 0.0;
                mat[7] = 0.0;
                mat[11] = 0.0;
                mat[15] = 1.0;
            }
 
            Matrix4(float x, float y, float z)
            {
                mat[0] = 1.0;
                mat[4] = 0.0;
                mat[8] = 0.0;
                mat[12] = x;
                mat[1] = 0.0;
                mat[5] = 1.0;
                mat[9] = 0.0;
                mat[13] = y;
                mat[2] = 0.0;
                mat[6] = 0.0;
                mat[10] = 1.0;
                mat[14] = z;
                mat[3] = 0.0;
                mat[7] = 0.0;
                mat[11] = 0.0;
                mat[15] = 1.0;
            }
 
            float operator[](int ind) const
            {
                return mat[ind];
            }
 
            float& operator[](int ind)
            {
                return mat[ind];
            }
 
            Matrix4 operator*(const Matrix4 &m) const
            {
                Matrix4 ret;
                operator_mul(&ret, this, &m);
                return ret;
            }
 
            static void operator_mul(Matrix4& ret, const Matrix4& mat,
                    const Matrix4& m)
            {
                ret[0] = mat[0] * m[0] + mat[4] * m[1] + mat[8] * m[2]
                        + mat[12] * m[3];
                ret[1] = mat[1] * m[0] + mat[5] * m[1] + mat[9] * m[2]
                        + mat[13] * m[3];
                ret[2] = mat[2] * m[0] + mat[6] * m[1] + mat[10] * m[2]
                        + mat[14] * m[3];
                ret[3] = mat[3] * m[0] + mat[7] * m[1] + mat[11] * m[2]
                        + mat[15] * m[3];
                ret[4] = mat[0] * m[4] + mat[4] * m[5] + mat[8] * m[6]
                        + mat[12] * m[7];
                ret[5] = mat[1] * m[4] + mat[5] * m[5] + mat[9] * m[6]
                        + mat[13] * m[7];
                ret[6] = mat[2] * m[4] + mat[6] * m[5] + mat[10] * m[6]
                        + mat[14] * m[7];
                ret[7] = mat[3] * m[4] + mat[7] * m[5] + mat[11] * m[6]
                        + mat[15] * m[7];
                ret[8] = mat[0] * m[8] + mat[4] * m[9] + mat[8] * m[10]
                        + mat[12] * m[11];
                ret[9] = mat[1] * m[8] + mat[5] * m[9] + mat[9] * m[10]
                        + mat[13] * m[11];
                ret[10] = mat[2] * m[8] + mat[6] * m[9] + mat[10] * m[10]
                        + mat[14] * m[11];
                ret[11] = mat[3] * m[8] + mat[7] * m[9] + mat[11] * m[10]
                        + mat[15] * m[11];
                ret[12] = mat[0] * m[12] + mat[4] * m[13] + mat[8] * m[14]
                        + mat[12] * m[15];
                ret[13] = mat[1] * m[12] + mat[5] * m[13] + mat[9] * m[14]
                        + mat[13] * m[15];
                ret[14] = mat[2] * m[12] + mat[6] * m[13] + mat[10] * m[14]
                        + mat[14] * m[15];
                ret[15] = mat[3] * m[12] + mat[7] * m[13] + mat[11] * m[14]
                        + mat[15] * m[15];
            }
 
            static void operator_mul(Matrix4 *ret, const Matrix4 *mat,
                    const Matrix4 *m)
            {
                operator_mul(*ret, *mat, *m);
            }
 
            float det() const
            {
//              ^^
                float ret = 0;
                for (auto& v : mat)
                {
                    ret += v;
                }
                return ret;
            }
 
            float mat[16];
        };
    }
}
 
#endif /* MATH_H_ */

Цитата Сообщение от Evg Посмотреть сообщение
что на одну итерацию цикла приходится условно 10 тактов для умножения матрицы и 1000 тактов для засовывания в вектор
В уже выделенный вектор засовывается один фоат. Так что вы с оценкой перегнули палку.

Цитата Сообщение от Evg Посмотреть сообщение
Очевидно, ты об этом не задумывался, когда писал вектор, в который нужно будет засунуть 100 миллионов экземпляров и не можешь сказать, какую долю времени всего теста занимает менеджер памяти при работе с вектором
В общем, да, и нет, вектор уже выделил себе память, и первый проход по вектору, однозначно его "прогрел". Так что даже если нагрузка менеджера памяти и будет, она будет в первом CPP проходе, а при С проходе уж её не будет совсем.
Так что даже если она высока, то?
Но да, об этом я сильно не думал. Ибо по моей оценке умножение 100 миллионов матриц стоит гораздо больше чем соответствующее "выделение" памяти менеджером.

Цитата Сообщение от Evg Посмотреть сообщение
Правда я всё равно не понимаю, что ты мне продемонстрировать этим примером хочешь.
Я демонстрирую то, что было сказано на первой странице, мистером DrOffset в этом сообщении.

Цитата Сообщение от Evg Посмотреть сообщение
Если хочешь продемонстрировать, что ненужные копирования занимают всего 1% (2%, 3%, не суть) от времени исполнения, то мне это вовсе не интересно. Если для выполнения какой-то задачи код должен быть быстрым, то борьба идёт за каждый процент времени исполнения
В том-то и суть, что в идеале оно не будет стоить и 1% - получится "идентичный" ассемблерный код.
Но опять же никто не говорит что компилятор способен решить все наши проблемы и С подход также имеет место быть, но не из-за копирований, а из-за кэш миссов в больших объемах данных и благодаря С подходу и определенному размещению ресурсов в памяти мы можем выиграть и скорее всего выиграем, ибо кэшмиссы это адски дорого. Но плюсовый оверхед сводится на нет оптимизациями компилятора.
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.03.2016, 18:20  [ТС]
Цитата Сообщение от avgoor Посмотреть сообщение
Теоретически можно
Скажем так, для меня в данный момент вопрос тоже звучит как теоретический. Просто ковырялся с чужим кодом по 3-мерной графике, заподозрил неэффективность при работе с операторами умножения и присваивания, оказалось, что действительно так

Конкретно при работе с графикой матрица действительно будет фиксированного размера (4), да ещё и квадратная. В таких условиях код получится очень конкретным (т.е. далеко не кодом в самом общем случае), а потому его можно будет по простому реализовать в "эффективной" манере и он не будут уступать коду на Си

Я плохо знаю Си++, в том смысле, что непосредственно на нём не программирую. Я знал о перегруженных операторах, но никогда особо не задумывался о том, в каких рамках оно описывается в языке (т.е. это обязательно бинарные операторы). Ну вот дорвался до конкретного примера, в котором нужно было копнуть, увидел неоптимальность. Вот и хотел понять, можно ли её простыми и неизвращёнными способами побороть
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
22.03.2016, 18:25
Цитата Сообщение от Evg Посмотреть сообщение
можно ли её простыми и неизвращёнными способами побороть
нельзя.
поскольку само желание - извращение
с точки зрения здравого смысла.
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
22.03.2016, 18:28
Цитата Сообщение от Evg Посмотреть сообщение
Можешь показать, что такое move-семантика на C++11 и её эмуляция на C++98? Хотя я так подразумеваю, что это и получится примерно реализация, похожая на operator_mul
Просто вся неэффективность растёт из того, что для реализации операции "a = b * c" на Си я могу описать функцию с тремя параметрами (dst, src1, src2). А с "культурной" реализацией на C++ это будет два оператора mul и assign. Как только оператор mul оказался невидимым для компилятора, у него сразу же пропадает возможность соптимизировать оператор assign
move семантика - это синтаксическая возможность заменить потенциально тяжелый assign на легковесный move (который в данном случае может быть реализован как swap).
Т.е., в псевдокоде, будет следующее:
C++
1
a.swap(b * c);
если матрица внутри представлена указателем на область памяти, то move сведется к обмену местами двух указателей (из а и из rvalue-объекта, который получился в результате операции умножения), вместо полноценного копирования матрицы.
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
22.03.2016, 18:35
Цитата Сообщение от DrOffset Посмотреть сообщение
move семантика
на 98 не понятно,
как отличить объект,
который можно грабить,
от того, который нельзя.
0
Игогошка!
 Аватар для ct0r
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
22.03.2016, 18:37
Цитата Сообщение от DrOffset Посмотреть сообщение
если матрица внутри представлена указателем на область памяти, то move сведется к обмену местами двух указателей
А сейчас окажется, что у нас жесткий embedded и какая уж там динамическая память
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
22.03.2016, 18:37
Цитата Сообщение от Evg Посмотреть сообщение
Я думал, что можно написать реализацию матриц так, чтобы можно было писать "a = b * c" и иметь при этом эффективный код
Используя вышепредложенную идею с "кортежами" - можно. Будут "ленивые" вычисления: после построения конечного типа в результате всех операций, исполнение оператора присваивания приведет к выполнению кода, аналогичного operator_mul (&a, &b, &c);. Техника называется expression templates. Есть статья здесь (здесь и короткий пример здесь), а также описание в книжке Йосуттиса "Шаблоны, справочник разработчика".
Возможно тебе это и не интересно в контексте темы, но возможно кого-то мимоходом заглянувшего заинтересует.
2
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.03.2016, 18:47  [ТС]
Цитата Сообщение от Nosey Посмотреть сообщение
Да рово любой math.h, от него мало что зависит, но и пожалуйста,
Собственно, тут нарушается требование, которое я описывал в начале темы - operator* должен быть не доступным для inline. Твоя реализация матрицы является "конкретной" (в том смысле, в котором я писал в посте #46), а потому к такому варианту кода ("конкретная" реализация + доступность всех операторов для инлайна) у меня нет ни вопросов, ни претензий. Современные компиляторы имеют достаточно агрессивные настройки по инлайнигну, чтобы с таким кодом справиться. А потому такой тест не подходит как методика сравнения для общего случая, хотя, как я уже говорил, если рассматривать такой тест как боевую программу, то претензий у меня нет

Цитата Сообщение от Nosey Посмотреть сообщение
В уже выделенный вектор засовывается один фоат. Так что вы с оценкой перегнули палку.
Вектор выделен на уровне Си++. При этом с точки зрения машины выделены только виртуальные страницы памяти, но не физические. Ради интереса можешь почитать Замеры скорости работы malloc'а на Windows и Linux

Цитата Сообщение от Nosey Посмотреть сообщение
Но да, об этом я сильно не думал. Ибо по моей оценке умножение 100 миллионов матриц стоит гораздо больше чем соответствующее "выделение" памяти менеджером
Скажем так, я хотел сказать то, что сделать замер правильно - это далеко не так просто, как кажется на первый взгляд. Ты пытаешься замерить скорость работы умножения матрицы, но при этом вокруг умножения появился довольно тяжёлый обвес, который может работать соизмеримое время и сильно искажать результаты

Цитата Сообщение от Nosey Посмотреть сообщение
В том-то и суть, что в идеале оно не будет стоить и 1% - получится "идентичный" ассемблерный код
Как я уже говорил - это в варианте с простой реализацией матрицы и доступными для инлайна операторам. В этом случае компилятор действительно сможет построить эффективный код. Тут вопросов нет. Вопрос появляется, когда тело оператора умножения и присваивания спрятано. Т.е. на Си мы их можем спрятать, потеряв в общем случае в эффективности какие-то сотые или тысячные доли процента, а на Си++ - уже не можем.

Добавлено через 9 минут
Цитата Сообщение от DrOffset Посмотреть сообщение
move семантика - это синтаксическая возможность заменить потенциально тяжелый assign на легковесный move (который в данном случае может быть реализован как swap).
Т.е., в псевдокоде, будет следующее:

a.swap(b * c);

если матрица внутри представлена указателем на область памяти, то move сведется к обмену местами двух указателей (из а и из rvalue-объекта, который получился в результате операции умножения), вместо полноценного копирования матрицы.
Т.е. такая реализация подразумевает обязательную реализацию внутренностей матрицы через указатель на память. Т.е. опять-таки появляются ограничения. Как минимум эта память должна быть глобальная (в том смысле, что не стековая). В общем-то опять получаются извращения

Цитата Сообщение от DrOffset Посмотреть сообщение
Возможно тебе это и не интересно в контексте темы, но возможно кому-то мимоходом заглянувшего заинтересует
Для порядку надо попробовать почитать. По ссылке описана проблема, которая довольно близко находится к обсуждаемой теме (насколько я вижу при беглом просмотре)
0
 Аватар для Nosey
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
22.03.2016, 19:01
Цитата Сообщение от Evg Посмотреть сообщение
Собственно, тут нарушается требование, которое я описывал в начале темы - operator* должен быть не доступным для inline.
Я не могу не спросить, почему? Это какой-то адский embedded? Или вы забыли о существовании link time optimization?
Отказываться от супер киллерфичи это очень грустно.

Касательно памяти, да, да, да, именно об этом я говорил о "прогретости" памяти, которая играет на руку С подходу.
Собственно я "прогрел" память и проверил и СРР подход стабильно быстрее С подхода. Надеюсь не нужно бесполезные листинги кода прикладывать)

Ну а если инлайнинг недоступен, т.е. код не в заголовочном файле, аля библиотека какая-то, хотя и тут же можно LTO прикрутить, то тут конечно проблемы.
И будет наверное единственный вариант, писать шаблонные врапперы скрывающие за собой С подход. Но я бы не назвал это "не извращениями".
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.03.2016, 21:17  [ТС]
Цитата Сообщение от Nosey Посмотреть сообщение
Я не могу не спросить, почему?
Причин может быть миллион. Например, библиотека с закрытым кодом. Другая причина - это когда ты имеешь библиотеку, которая хорошо оптимизирована под разные модели процессоров. Т.е. создаётся код одной и той же функции в нескольких экземплярах, каждый экземпляр оптимизирован под конкретную модель процессора. В итоге пользователь библиотеки занимается фактически просто вызовами нужных функций (как, например, это делается в opengl), но совершенно не занимается ковырянием с компилятором, с настройкой его оптимизаций, с настройкой под модель процессора. Поскольку программа основное время жизни проводит внутри библиотеки, то пользовательский код может быть написан сколь угодно безобразно, но итоговая программа будет работать быстро. Внутренности функции, в конце концов, могут быть реализованы таким образом, что выполняются на GPU, а наружу торчит лишь интерфейс. Как только ты попытаешься такую технологию затащит в файл *.h, то это автоматически означает, что пользователю нужно заниматься онанизмом с компилятором GPU, чего ему явно делать не хочется. В мире довольно много вещей, которые плохо дружат с базовой концепцией C++, когда для того, чтобы код был эффективным, компилятор должен видеть всё

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

Добавлено через 10 минут
Цитата Сообщение от Nosey Посмотреть сообщение
И будет наверное единственный вариант, писать шаблонные врапперы скрывающие за собой С подход
Просто если построить умножение двух матриц 4x4, то это будет 32 load, 64 операции умножения, 48 операций сложения, 32 операции store. Т.е. весьма длинная колбаса из операций. Достаточно длинная, чтобы её инлайн не играл практически никакой роли. Инлайн оператора умножения нужен исключительно ради того, чтобы результат этой операции схлопнуть с оператором присваивания, избавившись в конечном итоге от лишнего копирования

С подходом Си эта колбаса нормально ляжет в функцию с закрытым кодом, а накладные расходы на её вызов будут намного меньше, чем время выполнения тела функции. С подходом Си++ колбасу не получится упрятать в закрытый код. Точнее, если упрятать, то не получится избавиться от оператора присваивания (а это 16 операций load и 16 операций store). Даже если тело оператора будет доступно, но будет содержать вызов функции, то компилятор, видя взятие адреса на объект, не сможет избавится от копирования
0
 Аватар для Nosey
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
22.03.2016, 21:53
Цитата Сообщение от Evg Посмотреть сообщение
Причин может быть миллион. Например, библиотека с закрытым кодом.
Если библиотека предполагает динамическую линковку и также предполагает такое плотное сотрудничество с клиенстким кодом как "операторы умножения матриц" - то хотел бы я оторвать руки этому архитектору.

Если же статическая линковка, то LTO отлично отработает, да, придется поставлять в два раза больше библиотек, но это возможно. Я вот например и буст собираю с LTO, тьфу тьфу работает под 7-мью платформами.

Цитата Сообщение от Evg Посмотреть сообщение
как, например, это делается в opengl
Собственно у openGl очень легкий интерфейс взаимодействия по сравнению с внутренним кодом. Поэтому это плохенький пример. Но согласен, такое имеет место быть.

Цитата Сообщение от Evg Посмотреть сообщение
Просто если построить умножение двух матриц 4x4 ..... адреса на объект, не сможет избавится от копирования
Всё именно так, но инструмент-то для инлайнинга есть.


Я бы согласился с следующей причиной, что какой-то старый код не собирается с O3 и lto и этого кода много, тут я могу лишь посочувствовать и согласится с причиной. Но в остальных случаях - просто не использование предоставленных инструментов и объявление что технология без них плоха - как-то не серьёзно звучит.
0
Комп_Оратор)
Эксперт по математике/физике
 Аватар для IGPIGP
9005 / 4706 / 630
Регистрация: 04.12.2011
Сообщений: 14,003
Записей в блоге: 16
23.03.2016, 00:12
Evg, я наверное вообще ничего не понял. Потому и спрошу. Скажи, вот ты сравниваешь перегруженный оператор класса С++ с функцией С. А почему? На С++ легко можно написать функцию принимающую три адресных аргумента. По ссылке ли по указателю ли не суть. И нет копирования в том же смысле в котором его нет в псевдо операторе - функции mul.
Пользовательский оператор же упрощает написание кода. Звезда, кстати используется для разыменования, хотя и умножать никто не запретит. Но неважно. Важно, что компилятор и так сложен, зачем заставлять его строить выражения не создавая промежуточных переменных?
Если скорость нужна, то можно же функциями писать. Если это не изврат тогда что? Хотя и быстро. Это можно делать и на С++.
Но ведь чтобы сравнивать пользовательский оператор С++ корректно, его нужно сравнивать с пользовательским оператором С. А и нету его. А есть функция принимающая три адреса. А почему не функция принимающая два адреса и возвращающая значение (копия)?
Наверное, опять же, я не понимаю самую суть вопроса.
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
23.03.2016, 10:39  [ТС]
Цитата Сообщение от Nosey Посмотреть сообщение
Если же статическая линковка, то LTO отлично отработает, да, придется поставлять в два раза больше библиотек, но это возможно. Я вот например и буст собираю с LTO, тьфу тьфу работает под 7-мью платформами
Ещё раз. Вариант с LTO у тебя работает в варианте, когда ты ВСЁ можешь пересобрать из исходников. Собери на одной машине с gcc-5 библиотеку с LTO, затем скопируй её бинарник на машину с gcc-3. Заработает оно у тебя? - Нет, конечно. Перенеси бинарник библиотеки на машину, на которой испольуется компилятор, отличный от gcc. Заработает оно у тебя? - Конечно же нет. А вариант с хорошо оптимизированными кодами будет работать всегда и везде, причём быстро и никак не завися от того, что умеет пользовательский компилятор, а что нет (в плане оптимизаций)

Цитата Сообщение от Nosey Посмотреть сообщение
Всё именно так, но инструмент-то для инлайнинга есть
После инлайна компилятор избавится от копирования. Но чтобы эффективно спланировать всю проинлайненную арифметику, пользователю придётся ой как сильно попотеть. Потому как в моём случае он получал предельно соптимизированный бинарник и тупо им пользовался, в твоём случае ему придётся самому заниматься оптимизациями (через опции компилятора). Да ещё и заниматься исследованием полученного кода, чтобы понять, эффективен ли код. А чтобы понять, достигнута ли предельная эффективность на таком-то процессоре, ещё и читать систему команд и описание конвейера.

Цитата Сообщение от IGPIGP Посмотреть сообщение
Скажи, вот ты сравниваешь перегруженный оператор класса С++ с функцией С. А почему?
Потому что хочу понять основы эффективного написания кода на Си++. У меня у самого практики программирования на Си++ нет (и, надеюсь, не будет). Но язык как технологию для порядку понимать надо. Так же надо понимать места, где возникают потенциальные проблемы с производительностью

Цитата Сообщение от IGPIGP Посмотреть сообщение
Пользовательский оператор же упрощает написание кода
Именно так. Но своими силами я не мог получить "упрощённый" текст программы, оставив закрытым тело оператора, но не потеряв при этом эффективность. Я сначала подумал, что что-то делаю не правильно. Сейчас понял, что неправильным было прятать тело оператора, и именно это действие послужило источником потерь в производительности. Упрятывание тела оператора - это вовсе не академический эксперимент, а вариант реального практического применения, описанный выше (т.е. поставка эффективной сильно соптимизированной библиотеки)

Цитата Сообщение от IGPIGP Посмотреть сообщение
Если скорость нужна, то можно же функциями писать. Если это не изврат тогда что? Хотя и быстро. Это можно делать и на С++
Конечно можно. Но я думал, что это можно сделать, оставаясь в рамках "удобной" пользовательской модели с использованием операторов, а не функций. Т.е. хотелось, чтобы было и удобно и быстро (в смысле скорости исполнения)

Цитата Сообщение от IGPIGP Посмотреть сообщение
Но ведь чтобы сравнивать пользовательский оператор С++ корректно, его нужно сравнивать с пользовательским оператором С. А и нету его. А есть функция принимающая три адреса
Я сравниваю не сами языки, а общепринятые подходы программирования на этих языках

Цитата Сообщение от IGPIGP Посмотреть сообщение
Наверное, опять же, я не понимаю самую суть вопроса
На форуме много раз мелькала мысль о том, что если хотите написать эффективный код, то пишите его на Си++. Иногда там возникали возражения, что код надо писать на Си. Если вернуться к этой задаче с матрицами, то опять-таки условные 90% программистов написали бы его на Си++ также, как это сделал я. Т.е. если тело оператора умножения было бы большим, то могли бы его перенести в отдельный модуль *.cc (т.е. спрятать от инлайна), совершено не задумавшись о том, что появляется источник потери производительности в виде оператора присваивания. Другими словами, писать эффективный код на Си++ намного сложнее, чем на Си. В силу большой сложности языка. Потому что глядя на текст программы зачастую очень сложно понять, где и что там на самом деле вызовется и что и как надо подсунуть компилятору для инлайна, чтобы он смог схлопнуть длинный паровоз из копирований. Безусловно, на Си тоже надо во многом понимать работу компилятора, но количественно и качественно это намного более простые вещи. Ну и если рассмотреть частный случай в виде библиотеки хорошо оптимизированных кодов, то насколько я понял, задача не реализуется через общепринятый подход для языка Си++ через операторы и нормально решается только через подход в стиле языка Си
2
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
23.03.2016, 10:53
Цитата Сообщение от Evg Посмотреть сообщение
затем скопируй её бинарник на машину с gcc-3
Немного хочу добавить, что бинарник, скомпилированный одним компилятором С++ (например 4.х), и используемый с другим (например 3.х) скорее всего жить не будет (даже без всякого LTO). Т.к. каждая версия ABI, хоть и заявлена как совместимая, тем не менее имеет нюансы. А если мы спускаемся ниже, на версию 2.x, то и надежды на совместимость ABI уже нет. Таким образом поставлять кросскомпиляторный интерфейс на С++, где все функции упрятаны в бинарник в принципе не получится на данном этапе.

Лично я в таких ситуациях пишу интерфейс на С. Прячу оптимизированные варианты функций в бинарник (так же как и ты хотел). К интерфейсу на С прикладываю заголовочный файл на С++, который с помощью простых inline оберток (или шаблонных махинаций) добавляет этому интерфейсу "С++-сность". Естественно вся эта тонкая прослойка инлайнится и в результате имеем машинный код, построенный так, как будто бы мы напрямую использовали С интерфейс. При этом не страдает кросскомпиляторность, при желании можно перебиндить интерфейс в какой-нибудь питон или C# и т.п.
0
Комп_Оратор)
Эксперт по математике/физике
 Аватар для IGPIGP
9005 / 4706 / 630
Регистрация: 04.12.2011
Сообщений: 14,003
Записей в блоге: 16
23.03.2016, 11:13
Цитата Сообщение от Evg Посмотреть сообщение
Потому что хочу понять основы эффективного написания кода на Си++. У меня у самого практики программирования на Си++ нет (и, надеюсь, не будет). Но язык как технологию для порядку понимать надо. Так же надо понимать места, где возникают потенциальные проблемы с производительностью
Значит я всё-таки правильно понял суть. Evg, ты достаточно остро заметил, что операторы (а если по-шерудить и не только) запускают механизмы копирования. У С. Майерса, в частности, этому вопросу уделяется много внимания.
Истоки, - в общем подходе сокращающем трудозатраты. Передача и возврат по значению сами по себе обязаны своим существованием механизму стека. Стек это удобно, и иногда даже быстро, но иногда и накладно.
С операторами то же самое как я понимаю. Можно создать компилятор который будет строить вычислительную цепь не используя промежуточных переменных везде где возможно. Иногда приходится всё равно. Выражение:
Code
1
(a+b)*(c+d)
нельзя построить без промежуточных переменных, в общем случае. Но, повторю, можно создать компилятор который будет строить вычислительную цепь не используя промежуточных переменных везде, где только возможно. Он будет не быстр. И он будет очень сложен. Но ценность компилятора и в простоте реализации на новых платформах...
С другой стороны, на С++ вполне можно писать функции.
В целом, мне понравилась тема. Не каждый задумывается о подобных резервах производительности. Может если скорость нужна любой ценой, то это один из путей. Особенно если алгоритм не слишком поднимает трудозатраты.
1
Игогошка!
 Аватар для ct0r
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
23.03.2016, 12:40
Цитата Сообщение от Evg Посмотреть сообщение
Для порядку надо попробовать почитать. По ссылке описана проблема, которая довольно близко находится к обсуждаемой теме (насколько я вижу при беглом просмотре)
http://eigen.tuxfamily.org/dox... ation.html
Это она и есть. И решение куда элегантнее "сишного подхода" в лоб.

Цитата Сообщение от Evg Посмотреть сообщение
Потому что хочу понять основы эффективного написания кода на Си++. У меня у самого практики программирования на Си++ нет (и, надеюсь, не будет). Но язык как технологию для порядку понимать надо. Так же надо понимать места, где возникают потенциальные проблемы с производительностью
Как-то наивно считать, что можно хорошо понять технологию без должной практики.

Цитата Сообщение от Evg Посмотреть сообщение
Т.е. хотелось, чтобы было и удобно и быстро (в смысле скорости исполнения)
Цитата Сообщение от Evg Посмотреть сообщение
Ну и если рассмотреть частный случай в виде библиотеки хорошо оптимизированных кодов, то насколько я понял, задача не реализуется через общепринятый подход для языка Си++ через операторы и нормально решается только через подход в стиле языка Си
Тебе уже сказали про expression templates.
0
 Аватар для Nosey
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
23.03.2016, 13:06
Цитата Сообщение от Evg Посмотреть сообщение
Ещё раз. Вариант с LTO у тебя работает в варианте, когда ты ВСЁ можешь пересобрать из исходников. Собери на одной машине с gcc-5 библиотеку с LTO, затем скопируй её бинарник на машину с gcc-3. Заработает оно у тебя? - Нет, конечно. Перенеси бинарник библиотеки на машину, на которой испольуется компилятор, отличный от gcc. Заработает оно у тебя? - Конечно же нет. А вариант с хорошо оптимизированными кодами будет работать всегда и везде, причём быстро и никак не завися от того, что умеет пользовательский компилятор, а что нет (в плане оптимизаций)
Да, ABI различаются, это потребует дополнительной работы по изменению билд скрипта, слишком сложная работа по сравнению с написанием кода?

И насчёт предельной эффективности см. ниже.

Цитата Сообщение от Evg Посмотреть сообщение
После инлайна компилятор избавится от копирования. Но чтобы эффективно спланировать всю проинлайненную арифметику, пользователю придётся ой как сильно попотеть.
Далеко не только избавится компилятор от копирования, он сделает гораздо больше. И планировать там в 99% ситуациях ничего не надо:
флаги: -O3 -flto
Math.h
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#ifndef MATH_H_
#define MATH_H_
 
namespace ru
{
    namespace math
    {
        struct Matrix4
        {
        public:
 
            Matrix4()
            {
                mat[0] = 1.0;
                mat[4] = 0.0;
                mat[8] = 0.0;
                mat[12] = 0.0;
                mat[1] = 0.0;
                mat[5] = 1.0;
                mat[9] = 0.0;
                mat[13] = 0.0;
                mat[2] = 0.0;
                mat[6] = 0.0;
                mat[10] = 1.0;
                mat[14] = 0.0;
                mat[3] = 0.0;
                mat[7] = 0.0;
                mat[11] = 0.0;
                mat[15] = 1.0;
            }
 
            Matrix4(float x, float y, float z)
            {
                mat[0] = 1.0;
                mat[4] = 0.0;
                mat[8] = 0.0;
                mat[12] = x;
                mat[1] = 0.0;
                mat[5] = 1.0;
                mat[9] = 0.0;
                mat[13] = y;
                mat[2] = 0.0;
                mat[6] = 0.0;
                mat[10] = 1.0;
                mat[14] = z;
                mat[3] = 0.0;
                mat[7] = 0.0;
                mat[11] = 0.0;
                mat[15] = 1.0;
            }
 
            float operator[](int ind) const
            {
                return mat[ind];
            }
 
            float& operator[](int ind)
            {
                return mat[ind];
            }
 
            Matrix4 operator*(const Matrix4 &m) const
            {
                Matrix4 ret;
                operator_mul(&ret, this, &m);
                return ret;
            }
 
            static void operator_mul(Matrix4& ret, const Matrix4& mat,
                    const Matrix4& m);
 
            static void operator_mul(Matrix4 *ret, const Matrix4 *mat,
                    const Matrix4 *m)
            {
                operator_mul(*ret, *mat, *m);
            }
 
            float det() const
            {
//              ^^
                float ret = 0;
                for (auto& v : mat)
                {
                    ret += v;
                }
                return ret;
            }
 
            float mat[16];
        };
    }
}
 
#endif /* MATH_H_ */


Math.cpp
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "Math.h"
 
namespace ru
{
    namespace math
    {
        void Matrix4::operator_mul(Matrix4& ret, const Matrix4& mat,
                const Matrix4& m)
        {
            ret[0] = mat[0] * m[0] + mat[4] * m[1] + mat[8] * m[2]
                    + mat[12] * m[3];
            ret[1] = mat[1] * m[0] + mat[5] * m[1] + mat[9] * m[2]
                    + mat[13] * m[3];
            ret[2] = mat[2] * m[0] + mat[6] * m[1] + mat[10] * m[2]
                    + mat[14] * m[3];
            ret[3] = mat[3] * m[0] + mat[7] * m[1] + mat[11] * m[2]
                    + mat[15] * m[3];
            ret[4] = mat[0] * m[4] + mat[4] * m[5] + mat[8] * m[6]
                    + mat[12] * m[7];
            ret[5] = mat[1] * m[4] + mat[5] * m[5] + mat[9] * m[6]
                    + mat[13] * m[7];
            ret[6] = mat[2] * m[4] + mat[6] * m[5] + mat[10] * m[6]
                    + mat[14] * m[7];
            ret[7] = mat[3] * m[4] + mat[7] * m[5] + mat[11] * m[6]
                    + mat[15] * m[7];
            ret[8] = mat[0] * m[8] + mat[4] * m[9] + mat[8] * m[10]
                    + mat[12] * m[11];
            ret[9] = mat[1] * m[8] + mat[5] * m[9] + mat[9] * m[10]
                    + mat[13] * m[11];
            ret[10] = mat[2] * m[8] + mat[6] * m[9] + mat[10] * m[10]
                    + mat[14] * m[11];
            ret[11] = mat[3] * m[8] + mat[7] * m[9] + mat[11] * m[10]
                    + mat[15] * m[11];
            ret[12] = mat[0] * m[12] + mat[4] * m[13] + mat[8] * m[14]
                    + mat[12] * m[15];
            ret[13] = mat[1] * m[12] + mat[5] * m[13] + mat[9] * m[14]
                    + mat[13] * m[15];
            ret[14] = mat[2] * m[12] + mat[6] * m[13] + mat[10] * m[14]
                    + mat[14] * m[15];
            ret[15] = mat[3] * m[12] + mat[7] * m[13] + mat[11] * m[14]
                    + mat[15] * m[15];
        }
    }
}

main.cpp
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "math/Math.h"
#include <iostream>
#include <vector>
 
const size_t c = 100000000;
 
__attribute__ ((noinline))
ru::math::Matrix4 fcppni(const ru::math::Matrix4& mat1)
{
    ru::math::Matrix4 mat2 { 20, 30, 40 };
    return mat1 * mat2;
}
 
ru::math::Matrix4 fcpp(const ru::math::Matrix4& mat1)
{
    ru::math::Matrix4 mat2 { 20, 30, 40 };
    return mat1 * mat2;
}
 
ru::math::Matrix4 mat1 { 10, 20, 30 };
ru::math::Matrix4 mat3;
ru::math::Matrix4 mat2 { 20, 30, 40 };
 
void fc(const ru::math::Matrix4& mat)
{
    ru::math::Matrix4::operator_mul(mat3, mat, mat2);
}
 
int main(void)
{
    std::vector<float> resVec;
    resVec.reserve(c);
 
    size_t i = c;
 
    clock_t startFill = clock();
    while (--i)
    {
        resVec.push_back(0);
    }
    clock_t endFill = clock();
 
    std::cout << resVec.size() << " " << endFill - startFill << "-fill"
            << std::endl;
    resVec.clear();
 
    i = c;
 
    clock_t startCpp = clock();
    while (--i)
    {
        resVec.push_back(fcpp(mat1).det());
    }
    clock_t endCpp = clock();
 
    std::cout << resVec.size() << " " << endCpp - startCpp << "-cpp"
            << std::endl;
    resVec.clear();
 
    i = c;
 
    clock_t startCppNi = clock();
    while (--i)
    {
        resVec.push_back(fcppni(mat1).det());
    }
    clock_t endCppNi = clock();
 
    std::cout << resVec.size() << " " << endCppNi - startCppNi
            << "-cpp no-inline + 1 copy ctor" << std::endl;
 
    resVec.clear();
    i = c;
    clock_t startC = clock();
    while (--i)
    {
        fc(mat1);
        resVec.push_back(mat3.det());
    }
    clock_t endC = clock();
    std::cout << resVec.size() << " " << endC - startC << "-c" << std::endl;
}


Результаты:
99999999 196025-fill
99999999 1958025-cpp
99999999 2202373-cpp no-inline + 1 copy ctor
99999999 2784633-c

Я если честно сам несколько удивлен результатами, слишком радужны, особенно no-inline версия. Но я не сомневаюсь, что дав сегодня возможность компилятору оптимизировать код - он сделает его лучше, чем мои потуги с С подходом.
0
 Аватар для avgoor
1550 / 877 / 179
Регистрация: 05.12.2015
Сообщений: 2,555
23.03.2016, 13:48
Цитата Сообщение от Nosey Посмотреть сообщение
Да, ABI различаются, это потребует дополнительной работы по изменению билд скрипта, слишком сложная работа по сравнению с написанием кода?
Возможно и так. Если библиотека скомпилирована в g++, например, а вы используете ms (который правила декорирования вообще не публикует)
Цитата Сообщение от DrOffset Посмотреть сообщение
Лично я в таких ситуациях пишу интерфейс на С. Прячу оптимизированные варианты функций в бинарник (так же как и ты хотел). К интерфейсу на С прикладываю заголовочный файл на С++, который с помощью простых inline оберток (или шаблонных махинаций) добавляет этому интерфейсу "С++-сность".
В общем, большинство так и делает.
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
23.03.2016, 14:35  [ТС]
Цитата Сообщение от DrOffset Посмотреть сообщение
Немного хочу добавить, что бинарник, скомпилированный одним компилятором С++ (например 4.х), и используемый с другим (например 3.х) скорее всего жить не будет (даже без всякого LTO). Т.к. каждая версия ABI, хоть и заявлена как совместимая, тем не менее имеет нюансы. А если мы спускаемся ниже, на версию 2.x, то и надежды на совместимость ABI уже нет. Таким образом поставлять кросскомпиляторный интерфейс на С++, где все функции упрятаны в бинарник в принципе не получится на данном этапе
Согласен, немного перегнул палку. Но если заменить gcc-5 и gcc-3 на, например, gcc-4.8 и gcc-4.7, то можем получить точно такую же картину. LTO - это режим, на который нет в принципе никаких ABI и он является внутренней самодеятельностью компилятора, в общем случае завязанной на конкретную версию (в том числе и версию minor)

Цитата Сообщение от DrOffset Посмотреть сообщение
Лично я в таких ситуациях пишу интерфейс на С. Прячу оптимизированные варианты функций в бинарник (так же как и ты хотел). К интерфейсу на С прикладываю заголовочный файл на С++, который с помощью простых inline оберток (или шаблонных махинаций) добавляет этому интерфейсу "С++-сность". Естественно вся эта тонкая прослойка инлайнится и в результате имеем машинный код, построенный так, как будто бы мы напрямую использовали С интерфейс. При этом не страдает кросскомпиляторность, при желании можно перебиндить интерфейс в какой-нибудь питон или C# и т.п.
Да, об этом уже выше обсуждали. Но в контексте того, что ты написал выше про C++ABI, это уже становится жизненной необходимостью, а не просто технологией для упрощения жизни

Цитата Сообщение от IGPIGP Посмотреть сообщение
Выражение:

(a+b)*(c+d)

нельзя построить без промежуточных переменных, в общем случае
С этим-то понятно. Т.е. вычисление правой части, состоящей более, чем из одной операции, в обязательном порядке потребует промежуточной переменной, от этого никуда не деться. Меня возмутило её необходимость при построении присваивания. Но avgoor сказал одно предложение про бинарные и тернарные операторы (пост #24), после чего у меня в голове начало всё постепенно раскладываться по полочкам в этом вопросе

Цитата Сообщение от ct0r Посмотреть сообщение
Как-то наивно считать, что можно хорошо понять технологию без должной практики.
Зачем же сразу технологию. Достаточно понимать базу, на которой строится технология. При всей кажущейся большой разнице между подходами в языках Си и Си++, они очень и очень близки, особенно когда понимаешь работу компилятора изнутри. То, что у меня нет практики в Си++, всего лишь означает, что я не знаю, что и как на нём правильно писать. Я много лет знал про возможность написания операторов над классами, но вот только сейчас увидел, что операторы всего лишь бинарные. Да, это порушило практически всё моё сознание на тему "как бы я реализовал класс матрицы на Си++", но это не так уж и важно, особенно, если всё-таки это можно сделать в желаемом виде (т.е. работа через операторы)

Цитата Сообщение от ct0r Посмотреть сообщение
Тебе уже сказали про expression templates
Я пока не читал, но подозреваю, что там опять всё построено на том же принципе, что компилятор должен видеть потроха операторов
0
 Аватар для avgoor
1550 / 877 / 179
Регистрация: 05.12.2015
Сообщений: 2,555
23.03.2016, 14:44
Цитата Сообщение от Evg Посмотреть сообщение
Я пока не читал, но подозреваю, что там опять всё построено на том же принципе, что компилятор должен видеть потроха операторов
Правильно подозреваешь. Подругому компилятор никак не может их оптимизировать.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
23.03.2016, 14:44
Помогаю со студенческими работами здесь

Какое значение получит переменная p при выполнении следующего оператора присваивания?
var p: set of 0..9; i, j: integer; Если i=2 и j=5, то какое значение получит переменная p при выполнении следующего оператора...

Ошибка при выполнении оператора присваивания производного класса через указатель на базовый
Здравствуйте, не могу понять из-за чего ошибка. Надо в производном классе объявить оператор сложения и обратится к нему через базовый. ...

Ошибка при реализации перегрузки оператора <<
Добрый день. Прошу помощи. Имеется такой класc. class DList { ... public: ... void Save(std::ofstream &amp;b_out); //...

Ошибка в вводе данных из HTML и переносе их в JavaScript, при выполнении оператора IF или оператора swithc
доброго времени суток, при выполнении одной учебной задачи столкнулся с проблемой: при введении любого значения в поле, код выдает только...

Переопределение оператора присваивания
Имеется такой простой класс: class TClass { private: float* A; int N; public: TClass(int _N) ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
60
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru