Java中对于位运算的优化以及运用与思考(上)

简介: Java中对于位运算的优化以及运用与思考(上)

引言


随着JDK的发展以及JIT的不断优化,我们很多时候都可以写读起来易读但是看上去性能不高的代码了,编译器会帮我们优化代码。之前大学里面学单片机的时候,由于内存以及处理器性能都极其有限(可能很多时候考虑内存的限制优先于处理器),所以很多时候,利用位运算来节约空间或者提高性能,那么这些优秀的思想,放到目前的Java中,是否还有必要这么做呢?我们逐一思考与验证下(其实这也是一个关于Premature optimization的界定的思考)


1. 乘法与左移位


左移一位,相当于乘以2,左移n位,相当于乘以2的n次方。

1 << 1 == 1 * 2 //true
1 << n == 1 * pow(2, n) // true
public int pow(int i, int n) {
    assert n >= 0;
    int result = 1;
    for (int i = 0; i < n; i++) {
        result *= i;
    }
    return result;
}

看上去,移位应该比乘法性能快。那么JIT与JVM虚拟机是否做了一些优化呢?优化分为两部分,一个是编译器优化,另一个是处理器优化。我们先来看看字节码是否一致判断是否有编译优化,例如直接将乘以2优化成左移一位,来编写两个函数:

public void multiply2_1() {
    int i = 1;
    i = i << 1;
}
public void multiply2_2() {
    int i = 1;
    i *= 2;
}

编译好之后,用javap -c来看下编译好的class文件,字节码是:

public void multiply2_1();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: iconst_1
       4: ishl
       5: istore_1
       6: return
  public void multiply2_2();
    Code:
       0: iconst_1
       1: istore_1
       2: iload_1
       3: iconst_2
       4: imul
       5: istore_1
       6: return

可以看出左移是ishl,乘法是imul,从字节码上看编译器并没有优化。那么在执行字节码转换成处理器命令是否会优化呢?是会优化的,在底层,乘法其实就是移位,但是并不是简单地左移

我们来使用jmh验证下,添加依赖:

<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-core</artifactId>
    <version>1.22</version>
</dependency>
<dependency>
    <groupId>org.openjdk.jmh</groupId>
    <artifactId>jmh-generator-annprocess</artifactId>
    <version>1.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/site.ycsb/core -->
<dependency>
    <groupId>site.ycsb</groupId>
    <artifactId>core</artifactId>
    <version>0.17.0</version>
</dependency>


实现思路:

  1. 被乘数的选择:被乘数固定为1,或者是一个极小值或者极大值或者是稀疏值(转换成2进制很多位是0),测试结果没啥太大的参考意义,所以我们选择2的n次方减某一数字作为被乘数
  2. 乘数生成的性能损耗:乘数是2的随机n次方,生成这个的方式要一致,我们这里要测试的仅仅是移位还有乘法运算速度,和实现复杂度没有关系。 实现代码:
@Benchmark
@Warmup(iterations = 0)
@Measurement(iterations = 300)
public void multiply2_n_shift_not_overflow(Generator generator) {
    int result = 0;
    int y = 0;
    for (int j = 0; j < generator.divide.length; j++) {
        //被乘数x为2^n - j
        int x = generator.divide[j] - j;
        int ri = generator.divide.length - j - 1;
        y = generator.divide[ri];
        result += x * y;
        //为了和移位测试保持一致所以加上这一步
        result += y;
    }
}
@Benchmark
@Warmup(iterations = 0)
@Measurement(iterations = 300)
public void multiply2_n_mul_not_overflow(Generator generator) {
    int result = 0;
    int y = 0;
    for (int j = 0; j < generator.divide.length; j++) {
        int x = generator.divide[j] - j;
        int ri = generator.divide.length - j - 1;
        //为了防止乘法多了读取导致性能差异,这里虽然没必要,也读取一下
        y = generator.divide[ri];
        result += x << ri;
        //为了防止虚拟机优化代码将上面的给y赋值踢出循环,加上下面这一步
        result += y;
    }
}

测试结果:

Benchmark                 Mode  Cnt         Score         Error  Units
BitUtilTest.multiply2_n_mul_not_overflow    thrpt  300  35882831.296 ±  48869071.860  ops/s
BitUtilTest.multiply2_n_shift_not_overflow  thrpt  300  59792368.115 ±  96267332.036  ops/s

可以看出,左移位相对于乘法还是有一定性能提升的


2. 除法和右移位


这个和乘法以及左移位是一样的.直接上测试代码:

@Benchmark
@Warmup(iterations = 0)
@Measurement(iterations = 300)
public void divide2_1_1(Generator generator) {
    int result = 0;
    for (int j = 0; j < generator.divide.length; j++) {
        int l = generator.divide[j];
        result += Integer.MAX_VALUE / l;
    }
}
@Benchmark
@Warmup(iterations = 0)
@Measurement(iterations = 300)
public void divide2_1_2(Generator generator) {
    int result = 0;
    for (int j = 0; j < generator.divide.length; j++) {
        int l = generator.divide[j];
        result += Integer.MAX_VALUE >> j;
    }
}

结果:

Benchmark                                    Mode  Cnt         Score           Error  Units
BitUtilTest.divide2_n_div                   thrpt  300  10219904.214 ±   5787618.125  ops/s
BitUtilTest.divide2_1_shift                 thrpt  300  44536470.740 ± 113360206.643  ops/s

可以看出,右移位相对于除法还是有一定性能提升的


3. “取余”与“取与”运算


对于2的n次方取余,相当于对2的n次方减一取与运算,n为正整数。为什么呢?通过下图就能很容易理解:

十进制中,对于10的n次方取余,直观来看就是:


微信图片_20220624120602.jpg


其实就是将最后n位取出,就是余数。 对于二进制,是一样的:


微信图片_20220624120637.jpg



相关文章
|
20天前
|
存储 缓存 算法
优化 Java 后台代码的关键要点
【4月更文挑战第5天】本文探讨了优化 Java 后台代码的关键点,包括选用合适的数据结构与算法、减少不必要的对象创建、利用 Java 8 新特性、并发与多线程处理、数据库和缓存优化、代码分析与性能调优、避免阻塞调用、JVM 调优以及精简第三方库。通过这些方法,开发者可以提高系统性能、降低资源消耗,提升用户体验并减少运营成本。
|
21天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【4月更文挑战第3天】 在Java并发编程中,线程池是一种重要的资源管理工具,它能有效地控制和管理线程的数量,提高系统性能。本文将深入探讨Java线程池的工作原理、应用场景以及优化策略,帮助读者更好地理解和应用线程池。
|
1月前
|
监控 Java
Java并发编程中的线程池优化技巧
在Java并发编程中,线程池扮演着至关重要的角色。本文将深入探讨如何优化Java线程池,从线程池的创建与配置、任务队列的选择、拒绝策略的制定、线程池状态的监控等多个方面进行详细阐述。通过本文的阅读,您将了解到如何合理地利用线程池,提高系统的并发性能,从而更好地应对各种并发场景。
|
8天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0
|
9天前
|
SQL 缓存 Java
Java数据库连接池:优化数据库访问性能
【4月更文挑战第16天】本文探讨了Java数据库连接池的重要性和优势,它能减少延迟、提高效率并增强系统的可伸缩性和稳定性。通过选择如Apache DBCP、C3P0或HikariCP等连接池技术,并进行正确配置和集成,开发者可以优化数据库访问性能。此外,批处理、缓存、索引优化和SQL调整也是提升性能的有效手段。掌握数据库连接池的使用是优化Java企业级应用的关键。
|
11天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
11天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第13天】 在Java并发编程中,锁是一种常见的同步机制,用于保证多个线程之间的数据一致性。然而,不当的锁使用可能导致性能下降,甚至死锁。本文将探讨Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁降级等方法,以提高程序的执行效率。
13 4
|
17天前
|
设计模式 缓存 安全
分析设计模式对Java应用性能的影响,并提供优化策略
【4月更文挑战第7天】本文分析了7种常见设计模式对Java应用性能的影响及优化策略:单例模式可采用双重检查锁定、枚举实现或对象池优化;工厂方法和抽象工厂模式可通过对象池和缓存减少对象创建开销;建造者模式应减少构建步骤,简化复杂对象;原型模式优化克隆方法或使用序列化提高复制效率;适配器模式尽量减少使用,或合并多个适配器;观察者模式限制观察者数量并使用异步通知。设计模式需根据应用场景谨慎选用,兼顾代码质量和性能。
|
20天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第5天】随着多核处理器的普及,并发编程在提高程序性能方面发挥着越来越重要的作用。在Java中,锁是实现并发控制的关键机制。本文将探讨Java并发编程中的锁优化策略,包括锁粗化、锁消除、锁排序等技术,以提高程序的执行效率和降低资源争用。
|
22天前
|
Java
深入理解Java并发编程:线程池的应用与优化
【4月更文挑战第2天】本文将深入探讨Java并发编程中的重要组件——线程池。我们将了解线程池的基本概念,应用场景,以及如何优化线程池的性能。通过本文,你将能够更好地理解和使用线程池,提高你的Java并发编程能力。