Null检查vs try/catch当99%的时间对象不为空时



通常我更喜欢null检查。但是在当前的场景中,我知道大多数时候我的if条件会通过,并且很少有合法的场景中对象可能为空。

而且,负载很大(大约500万次/小时)

现在我试图从性能的角度找到哪种方式更好。在java中已经检查了try/catch和null检查,但我的情况是唯一的。

也检查了哪个更快,尝试在java中捕获或if-else (WRT性能),但这一个和上面的都是在通用上下文中,通过/失败比率的知识是不可用的。

public void process(Job job) {
    //... some code which processes job
    SubJob subJob = job.getSubJob();
    if(subJob != null) {  // 99% of the time this will pass
        //.. do something
    }               
}

try/catch版本
public void process(Job job) {
    //... some code which processes job
    SubJob subJob = job.getSubJob();
    try {
        //.. do something  
    }catch(NullPointerException e) { //This may occure only 1% of the time. 
        //...   
    }               
}

更新:

获胜者为空检查。在Try/catch中,JVM内部将进行null检查并抛出NPE,并且JVM中的异常处理(创建堆栈等)将增加开销。另外,根据另一个答案,现代cpu足够智能,可以很好地预测这些场景,在我独特的情况下,这总是有利的。

我也写了一个程序(以我的名字贴在下面),结果清楚地表明null检查在我的AMD处理器上要好得多。

谢谢你们的指导。

TL;DR:如果您不在代码中执行空检查,运行时将为您插入一个空检查。


您需要从HotSpot或优化JIT编译器的角度来看待这个问题:

在对象变量

上调用方法时
someObject.callMethod()

则运行时需要抛出一个NPE,如果变量为空(伪ASM):

check someObject ref not null else throw NPE
invoke 'callMethod' on 'someObject' 

现在,有时运行时可以确保变量不为空。这种分析称为空检查消除

-- no need: check someObject ref not null else throw NPE
invoke 'callMethod' on 'someObject' 

线索是您检查Java源代码

if (someObject != null)

足以向运行时证明变量不为空。

的基本原理:

总是更喜欢null检查而不是捕获NPE。

如果你不做空检查,那么运行时会为你插入它。

使用null检查,处理器将对其进行管道处理,并且应该始终"忽略" if并继续执行语句。

类似的,关于流水线的讨论:为什么处理排序数组比处理未排序数组更快?

我强烈倾向于您现有的null检查首选项。对于具有合法空条件的情况,我认为应该检查是否为空,并为"永远不会发生"的情况保存空指针异常。我应该提到,这更多的是一种范式偏好,而不是基于性能的偏好。然而,对我来说,必须在性能上有巨大的好处,并且极端需要保证这种方法。

也就是说,前面的注释指出,在发生异常的情况下,异常开销只会在1%的情况下产生,这是假设try块的"设置"不会产生开销。我不确定情况是否如此。

这里有两个方面,一个是性能,另一个是设计。你可以说哪个更好不是你应该问的问题,它是否足够好才是你需要的。如果99%的时间它不是空的,那么分支预测将节省你的时间,JIT可能会优化更多。但无论你选择什么,你都必须对这个特殊情况做点什么。你是怎么做的?你扔一个exception还是接住NPE然后再扔一次?那么,exceptions可能是性能瓶颈,而不是机制。

我自己也会写。

SubJob subJob = job.getSubJob();
Objects.requireNotNull(subJob);

对于想要查看我为测试而运行的代码的人…

public class TestMainResuse {
    static int load = 1000000;
    public static void main(String[] args) throws Exception {
        Object[] objects = new Object[load]; 
        for(int i = 0; i < load; i++) {
            if(i % 100 == 0 ) 
                objects[i] = null;
            else 
                objects[i] = new Object(); 
        }

        withTry(objects);
        withIf(objects);
        withTry(objects);
        withIf(objects);
        withTry(objects);
        withIf(objects);
        withIf(objects);
        withIf(objects);
        withTry(objects);
        withTry(objects);
    }
    public static void withTry(Object[] objects){
        long startTime = System.nanoTime();
        for(int i = 0; i < load; i++) {
            try {
              objects[i].getClass();
            } catch (NullPointerException e) {
                //System.out.println("this");
            }
        }
        System.out.println(" try took "+ (System.nanoTime() - startTime));
    }
    public static void withIf(Object[] objects){
        long startTime = System.nanoTime(); 
        for(int i = 0; i < load; i++) {
            if(objects[i] != null) {
                objects[i].getClass();
            }
        }
        System.out.println("null took "+ (System.nanoTime() - startTime));
    }
}

输出(以纳米为单位):

 try took 6906539
null took 3801656
 try took 13380219
null took 87684
 try took 1036815
null took 79558
 try took 1021416
null took 10693
 try took 1020989
null took 1711
null took 1284
null took 1283
null took 1283
null took 1283
null took 1711
 try took 1038954
 try took 1106107
 try took 1040237
 try took 1020134
 try took 1028261

如何逆转您的if语句

if(subJob == null){
//do something to fix it;
}

这样(如您所述),这段代码只会在1%的时间内被调用,其余时间您的程序将不关心它。

最新更新