运算符链接,为什么要增长左分支?
Operator chaining, why grow left branch?
根据this question(也就是说,OP 陈述了信念并且没有被更正)-链式运算符向左增长:
a << b << c << d;
// ==
operator<<( operator<<( operator<<(a, b), c ), d);
为什么会这样?这样做效率会不会更高:
operator<<( operator<<(a, b), operator<<(c, d) );
即尽可能平衡?
编译器肯定能解决这个问题以获得更好的运行时性能吗?
- 通过将操作转变为 CISC 可以更好地为 "better runtime performance" 解决这个问题。 (例如 MULADD。)
- 两个处理器之间的同步成本将远远超过挤在一条线上的任何合理数量的运算符所带来的好处。
- 这将导致乱序处理无法有效地使每个内核以 Pentium Pro 的速度运行。
- 除了Mystical提到的
std::cout
,想想下面的整数算术问题:3 * 5 / 2 * 4
应该得到(15 / 2) * 4
,意思是28
, 但如果将其分开,将导致 15 / 8
,即 1
。即使运算符具有相同的意义也是如此。
编辑:
很容易想到我们可以通过跨核心拆分交换操作来逃脱,例如 3 * 5 * 2 * 4
。
现在让我们考虑一下核心之间通信的唯一方法是通过共享内存。并且平均加载时间比平均 CPU 操作时间慢一个数量级。仅此一项就意味着需要进行大量的数学运算才能使容纳 1 个负载变得有意义。但更糟糕的是:
- 主核必须将要执行的数据和操作写入内存。因为这些必须是分开的,所以写入时可能会出现页面错误。
- 主内核完成其工作后,它必须加载脏位以检查从内核是否已完成处理。它可能正忙或已被抢占,因此这可能需要多次加载。
- 然后必须加载从核心的结果。
你可以看到考虑到负载费用会有多糟糕。让我们看看如果一切都在一个核心上可以完成的优化:
- 如果提前知道某些值,则可以将它们分解为 2 秒,然后进行移位。更好的转变和添加可能一次执行比 1 个更多的操作。
- 对于 32 位或更小的数字,可以使用 64 位处理器的高位和低位进行加法运算。
乱序处理可以由CPU自行决定,例如浮点运算比整数运算慢,所以在这种情况下:1.2 * 3 * 4 * 5
CPU 将首先执行 3 * 4 * 5
并且只执行 1 个浮点运算。
当您在 C++ 中重载运算符时,它会保留运算符未重载时相同的优先级和结合性。
对于移位运算符(<<
和 >>
),标准要求:
The shift operators << and >> group left-to-right.
这意味着像 a<<b<<c
这样的操作必须被解析为 (a<<b)<<c
。
重载运算符会更改为每个操作调用的代码,但对分组没有影响——无论 a
、b
还是 [=16=,分组都相同] 是将使用编译器提供的运算符的内置类型(例如 int
),或者它们是否是将使用重载运算符的某些 class 类型。处理分组的 解析器 无论如何都保持相同。
但是请注意,order of evaluation is independent of precedence or associativity,因此这不一定会影响执行速度。
根据this question(也就是说,OP 陈述了信念并且没有被更正)-链式运算符向左增长:
a << b << c << d;
// ==
operator<<( operator<<( operator<<(a, b), c ), d);
为什么会这样?这样做效率会不会更高:
operator<<( operator<<(a, b), operator<<(c, d) );
即尽可能平衡?
编译器肯定能解决这个问题以获得更好的运行时性能吗?
- 通过将操作转变为 CISC 可以更好地为 "better runtime performance" 解决这个问题。 (例如 MULADD。)
- 两个处理器之间的同步成本将远远超过挤在一条线上的任何合理数量的运算符所带来的好处。
- 这将导致乱序处理无法有效地使每个内核以 Pentium Pro 的速度运行。
- 除了Mystical提到的
std::cout
,想想下面的整数算术问题:3 * 5 / 2 * 4
应该得到(15 / 2) * 4
,意思是28
, 但如果将其分开,将导致15 / 8
,即1
。即使运算符具有相同的意义也是如此。
编辑:
很容易想到我们可以通过跨核心拆分交换操作来逃脱,例如 3 * 5 * 2 * 4
。
现在让我们考虑一下核心之间通信的唯一方法是通过共享内存。并且平均加载时间比平均 CPU 操作时间慢一个数量级。仅此一项就意味着需要进行大量的数学运算才能使容纳 1 个负载变得有意义。但更糟糕的是:
- 主核必须将要执行的数据和操作写入内存。因为这些必须是分开的,所以写入时可能会出现页面错误。
- 主内核完成其工作后,它必须加载脏位以检查从内核是否已完成处理。它可能正忙或已被抢占,因此这可能需要多次加载。
- 然后必须加载从核心的结果。
你可以看到考虑到负载费用会有多糟糕。让我们看看如果一切都在一个核心上可以完成的优化:
- 如果提前知道某些值,则可以将它们分解为 2 秒,然后进行移位。更好的转变和添加可能一次执行比 1 个更多的操作。
- 对于 32 位或更小的数字,可以使用 64 位处理器的高位和低位进行加法运算。
乱序处理可以由CPU自行决定,例如浮点运算比整数运算慢,所以在这种情况下:1.2 * 3 * 4 * 5
CPU 将首先执行3 * 4 * 5
并且只执行 1 个浮点运算。
当您在 C++ 中重载运算符时,它会保留运算符未重载时相同的优先级和结合性。
对于移位运算符(<<
和 >>
),标准要求:
The shift operators << and >> group left-to-right.
这意味着像 a<<b<<c
这样的操作必须被解析为 (a<<b)<<c
。
重载运算符会更改为每个操作调用的代码,但对分组没有影响——无论 a
、b
还是 [=16=,分组都相同] 是将使用编译器提供的运算符的内置类型(例如 int
),或者它们是否是将使用重载运算符的某些 class 类型。处理分组的 解析器 无论如何都保持相同。
但是请注意,order of evaluation is independent of precedence or associativity,因此这不一定会影响执行速度。