拥有经常修改非本地(成员)变量的代码(在单线程场景中,使用基本类型),过早的悲观化只是分配而不检查是否有必要(考虑到状态的修改没有其他方面)效果如下面的代码所示)?
e.g:
static int someState = 0;
void foo(int newState)
{
/* Which is better? This...*/
someState = newState; /*Assign regardless*/
/* ... or this... */
if (someState != newState)
{
someState = newState; /*Only write when required*/
}
}
我总觉得支票是多余的,想听听你的意见。
我可能会对此进行描述,是的,但我对这类事情的社区共识感到疑惑。无论如何,我大多只是执行任务,而且我想知道这是否是它的主要用途。
答案 0 :(得分:2)
如果您的应用程序使用观察者,或者如果值更改需要采取某些操作,则通常最好检查值是否相同,并且仅在值更改时执行操作。因此,任何调用setter的人都可以知道它总是很便宜。这会产生积极影响,即代码中只有一个位置需要进行优化。
答案 1 :(得分:2)
使用原始类型
和
过早的悲观化只是分配而不检查是否有必要?
更糟。它正在添加一个不必要的读取,比较和条件跳转到本应该写入的内容。
用户定义的类型怎么样?
让期望的分配数量= N
假设冗余分配的次数为U
让平等比较成本= C
让分配成本= A
因此冗余分配的比率为R = U / N
我们可以通过检查= RNA
来保存这样做的成本= NC
调用检查时:分配总成本= NC +(1-R)NA
什么时候不费心去检查:作业成本= NA
因此,为了确定是否值得检查平等,我们只需要满足不平等:
NA> NC + NA - RNA
NA-NC + NA> -RNA
-NC> -RNA
NC< = RNA
NC / NA< = R
C / A< = R
R> C / A
因此,如果预期冗余分配的比率大于支票成本的比率(与分配成本相比),那么值得优化。
有人可以查看我的数学吗?已经有一段时间......
答案 2 :(得分:1)
在你提到的情况下,对于没有其他效果会发生的原始类型,我说总是分配。这不是一个过早的悲观化。无论哪种方式,结果都是相同的,您可以节省负担并进行比较。我认为在分配之前添加相等性测试是过早的悲观化。
答案 3 :(得分:1)
有几件事需要考虑:
volatile
?对于原始的非易失性类型,第一个选项应该更快。它是存储与加载+比较+分支或加载+比较+分支+存储。对于现代处理器上的原始类型,存储与加载几乎相同,因此第一个选项应该总是更快。 如果条件不遵循任何常规模式,则尤其如此,并且分支预测将不起作用。在这种情况下,分支可能需要很多周期。
对于volatile变量,选择取决于你需要什么语义(它是某些硬件的控制寄存器,写入内存位置会导致某些操作,通过网络传输等等吗?)
对于类,选择取决于比较的相对成本和分配和概率的相等性。在第一个选项中,总是有一个赋值。在第二个选项中:如果值相等,则只有比较和分支;如果没有,比较,分支和分配。
一般来说,它是:
Ta vs. (Tc + Tb) * p + (Tc + Tb + Ta) * (1 - p)
其中Ta是赋值时间,Tc - 比较,Tb - 分支(如果类是复数则可以忽略),p是相等值的概率。
另请注意,某些类可以在其赋值运算符中包含此检查。