当前位置: 首页 > 面试题库 >

在写入变量之前检查变量是否具有特定值是否明智?

尹庆
2023-03-14
问题内容
if (var != X)
  var = X;

明智与否?编译器会始终优化if语句吗?是否有任何可从if语句中受益的用例?

如果var是可变变量怎么办?

我对C ++和Java答案都感兴趣,因为volatile变量在两种语言中都有不同的语义。Java的JIT编译也可以有所作为。

如果我们总是用X覆盖var,则if语句会引入分支和附加读取,这是不可能的,所以这很不好。另一方面,如果var == X随后使用此优化,则仅执行读取操作,而不执行写入操作,这可能会对缓存产生一些影响。显然,这里需要权衡取舍。我想知道实际情况。有人对此做过任何测试吗?

编辑:

我最感兴趣的是在多处理器环境中的外观。在琐碎的情况下,首先检查变量似乎没有多大意义。但是,当必须在处理器/内核之间保持高速缓存一致性时,额外的检查实际上可能是有益的。我只是想知道它会产生多大的影响?处理器本身也不应该进行这种优化吗?如果var == X再分配一次,则X不应“抹平”缓存。但是我们可以依靠吗?


问题答案:

是的,在某些情况下肯定是明智的,并且正如您所建议的那样,volatile变量就是其中一种情况,即使对于单线程访问也是如此!

从硬件和编译器/
JIT的角度来看,易失性写入都很昂贵。在硬件级别上,这些写操作可能比普通写操作贵10到100倍,因为必须清空写缓冲区(在x86上,具体信息因平台而异)。在编译器/
JIT级别,易失性写会抑制许多常见的优化。

但是,投机只能带给您如此远的好处-
证明始终在基准测试中。这是一个微基准测试,可尝试您的两种策略。基本思想是将值从一个数组复制到另一个数组(几乎是System.arraycopy),具有两个变体-
一个变体无条件复制,另一个变体首先检查值是否不同。

这是简单的非易失性情况的复制例程(此处提供完整源代码):

        // no check
        for (int i=0; i < ARRAY_LENGTH; i++) {
            target[i] = source[i];
        }

        // check, then set if unequal
        for (int i=0; i < ARRAY_LENGTH; i++) {
            int x = source[i];
            if (target[i] != x) {
                target[i] = x;
            }
        }

使用Caliper作为我的微基准测试工具,使用上述代码复制数组长度为1000的结果是:

    benchmark arrayType    ns linear runtime
  CopyNoCheck      SAME   470 =
  CopyNoCheck DIFFERENT   460 =
    CopyCheck      SAME  1378 ===
    CopyCheck DIFFERENT  1856 ====

每次运行还需要大约150 ns的开销,才能每次重置目标阵列。跳过检查要快得多-每个元素大约0.47 ns(或者在除去设置开销后每个元素大约0.32
ns,所以我的盒子上几乎恰好1个周期)。

当阵列相同时,检查速度要慢大约3倍,而不同时则要慢4倍。鉴于支票被完美预测,我对支票有多糟糕感到惊讶。我怀疑罪魁祸首很大程度上是JIT-
循环主体更加复杂,展开的次数可能更少,并且其他优化可能不适用。

让我们切换到易失性案例。在这里,我用作AtomicIntegerArray易失性元素的数组,因为Java没有任何带有易失性元素的本机数组类型。在内部,此类仅使用进行直接写入数组sun.misc.Unsafe,从而允许进行易失性写入。生成的程序集与易失性方面(以及可能的范围检查消除,在AIA情况下可能无效)不同,基本上类似于常规阵列访问。

这是代码:

        // no check
        for (int i=0; i < ARRAY_LENGTH; i++) {
            target.set(i, source[i]);
        }

        // check, then set if unequal
        for (int i=0; i < ARRAY_LENGTH; i++) {
            int x = source[i];
            if (target.get(i) != x) {
                target.set(i, x);
            }
        }

结果如下:

arrayType     benchmark    us linear runtime
     SAME   CopyCheckAI  2.85 =======
     SAME CopyNoCheckAI 10.21 ===========================
DIFFERENT   CopyCheckAI 11.33 ==============================
DIFFERENT CopyNoCheckAI 11.19 =============================

桌子已经翻了。首先检查比通常的方法快约3.5倍。总体而言,一切都慢得多-在检查情况下,每个循环我们要付出约3 ns的代价,在最坏的情况下,我们要付出约10
ns的代价(上面的时间在我们身上,涵盖了整个1000个元素数组的副本)。易失性写入确实更昂贵。DIFFERENT情况下包含大约1
ns的开销,以在每次迭代时重置阵列(这就是为什么对于DIFFERENT而言,即使简单也稍微慢一点)的原因。我怀疑“检查”情况下的许多开销实际上是边界检查。

这都是单线程的。如果您实际上对volatile有跨核心的争用,那么对于简单方法而言,结果将是非常糟糕的得多,并且与上述检查情况一样好(缓存行仅处于共享状态-
否所需的一致性流量)。

我也只测试了“每个元素相等”与“每个元素不同”的极端。这意味着“检查”算法中的分支总是可以完美预测的。如果混合使用相等和不同,那么您将不会仅获得SAME和DIFFERENT情况下时间的加权组合-
由于预测错误(在硬件级别,甚至在JIT级别),您的情况会更糟,无法再针对始终采用的分支进行优化)。

因此,即使对于volatile,它是否明智也取决于特定的上下文-
相等和不相等的值的混合,周围的代码等等。通常我不会在单线程情况下仅针对volatile进行此操作,除非我怀疑大量的集合是多余的。但是,在高度多线程的结构中,读取然后进行易失性写(或其他昂贵的操作,如CAS)是一种最佳做法,您会看到它是诸如java.util.concurrent结构之类的高质量代码。



 类似资料:
  • 问题内容: 有什么最短的方法吗? 如果我是正确的,应该用? 有可能,并且在一行中写(也许是数组?)而不重复? 问题答案: 如果要测试变量是否 确实 是,请使用标识运算符: 如果要检查是否未设置变量: 或者,如果变量不为空,则为空字符串,零,..: 如果要测试变量是否不是空字符串,也将足够:

  • 我想检查变量是否为空: 此代码基于:https://www.thomasmaurer.ch/2010/07/powershell-check-variable-for-null/,但这不起作用。 我该怎么解决这个问题? ps。堆栈中有东西

  • 问题内容: 与空检查相比,变量分配是否昂贵?例如,在将foo分配为null之前是否值得检查其是否为null? 还是这无所顾忌? 问题答案: 这是一个微微优化(可能还是由编译器处理)。不用担心 通过专注于程序的实际算法,您将获得更大的回报。 我们应该忘记效率低下的问题,例如大约97%的时间:过早的优化是万恶之源。-唐纳德​​·努斯

  • 我有一个Typescript项目,我需要知道变量的值是否在对象的任何属性中。 这就是目标: 这是声明的变量: 以下是我检查它是否存在的步骤: 问题:做三次检查会在其他机器上运行三次 我需要知道的是,如何检查它是否存在,而不需要迭代对象,如果它不存在,只需操作一次

  • 问题内容: 我想检查一个变量是否存在。现在我正在做这样的事情: 是否有其他方法无一例外? 问题答案: 要检查是否存在局部变量: 要检查是否存在全局变量: 要检查对象是否具有属性:

  • 问题内容: 我们有n个变量,它们没有任何结构。 例如在python中,我可以这样做: 在Java中,我必须这样做: 您知道一种改进此语法的简单方法吗?(想象一下很长的变量名以及很多) 谢谢。 问题答案: 如果您有很多这样的变量,是否考虑过将它们放在集合中,而不是将它们作为单独的变量?此时有多种选择。 如果发现自己经常这样做,则可能要编写辅助方法,可能使用varargs语法。例如: glowcode