Java 性能差异

问题描述 投票:0回答:1

我正在尝试对一些 Java 代码进行基准测试,我注意到它的性能差异很大。

作为示例,我在下面的代码片段中对函数 copyElements 的性能进行了基准测试:

public class Main {
    static class Wrapper {
        public int x;
        public Wrapper(int x) {
            this.x = x;
        }
    }

    private static <T> void shuffle(T[] array) {
        int index;
        T temp;
        Random random = new Random(420);
        for (int i = array.length - 1; i > 0; i--) {
            index = random.nextInt(i + 1);
            temp = array[index];
            array[index] = array[i];
            array[i] = temp;
        }
    }

    static public void copyElements(Wrapper[] arr1, Wrapper[] arr2) {
        for (int i=0; i < arr1.length; i++) {
            arr2[i].x = arr1[i].x;
        }
    }

    public static void main(String[] args) {
        Wrapper[] arr1 = new Wrapper[100_000_000];
        Wrapper[] arr2 = new Wrapper[100_000_000];
        for (int i=0; i < arr1.length; i++) {
            arr1[i] = new Wrapper(i);
            arr2[i] = new Wrapper(i);
        } 
        shuffle(arr1);
        shuffle(arr2);

        // Run copyElements and measure performance.
    }
}

我如何衡量 Java 性能:

  1. 运行 copyElements 64 次(预热)
  2. 运行 copyElements 4 次,每次测量 System.nanoTime,
  3. 计算这 4 个测量值的中位数,保存。
  4. 重复该过程 64 次。

我正在使用 OpenJDK 17。我已禁用 CPU 的 Turbo Boost。我使用 numactl -N 0 -m 0 来确保性能不会因为某些 NUMA 问题(64 核服务器)而发生变化。在此期间没有用户运行任何东西。数组被打乱以使内存访问模式随机。

另外,我在C++中重复了这个实验。代码使用带有标志 -std=c++20 -O3 的 g++ (GCC) 11.4.1 20230605 (Red Hat 11.4.1-2) 进行编译。也重复了64次。

#include <vector>
#include <algorithm>
#include <memory>
#include <chrono>
#include <iostream>

void copyElements(std::vector<int *> &arr1, std::vector<int *> &arr2) {
    for (int i = 0; i < arr1.size(); i++) {
        *arr2[i] = *arr1[i];
    }
}

int main() {
    std::vector<int *> arr1(1e8);
    std::vector<int *> arr2(1e8);
    for (int i = 0; i < arr1.size(); ++i) {
        arr1[i] = new int(i);
        arr2[i] = new int(i);
    }
    std::random_shuffle(arr1.begin(), arr1.end());
    std::random_shuffle(arr2.begin(), arr2.end());

    auto start = std::chrono::system_clock::now();
    copyElements(arr1, arr2);
    auto end = std::chrono::system_clock::now();

    auto elapsed_millis = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();

    std::cout << elapsed_millis << std::endl;
    for (int i = 0; i < arr1.size(); ++i) {
        delete arr1[i];
        delete arr2[i];
    }
}

结果如下:

java

平均值:1518.1782848671876ms 标准:61.13827595338004ms std/平均值 = 4%

c++

平均值:2883.3695373437504ms 标准:8.444136374009128ms std/平均值 = 0.3%

我们可以看到,C++ 的性能更加稳定。如何减少java性能的方差?

java performance jvm-hotspot microbenchmark
1个回答
0
投票

Java 性能的差异可能受到多种因素的影响,包括 Java 虚拟机 (JVM) 优化、垃圾回收和运行时预热效果。以下是一些有助于减少 Java 性能差异的建议:

  1. 热身阶段: 确保您的 Java 代码在实际基准测试之前经过充分的预热。 JVM 需要时间来执行优化。在预热阶段多次运行代码有助于 JVM 达到稳定状态。

  2. 垃圾收集: 监视和管理垃圾收集。频繁的垃圾收集会带来可变性。您可以尝试不同的垃圾收集策略或调整堆大小,看看是否会影响性能。

  3. JIT编译: Java 使用即时 (JIT) 编译,这意味着代码在运行时编译为本机机器代码。此编译可能会引入可变性。考虑使用 JVM 选项来控制 JIT 编译行为,例如

    -XX:+PrintCompilation
    -XX:CompileThreshold

  4. JVM 选项: 尝试使用 JVM 选项来针对您的特定用例进行优化。例如,您可以尝试调整堆大小(

    -Xms
    -Xmx
    )、线程堆栈大小 (
    -Xss
    ) 以及其他相关选项。

  5. 基准测试工具: 考虑使用专门的 Java 基准测试工具,例如 JMH(Java Microbenchmarking Harness)。 JMH 旨在产生可靠的基准测试结果,并解决微基准测试中的许多常见陷阱。

  6. 剖析器: 使用分析工具来识别性能瓶颈。探查器可以帮助您了解 JVM 如何优化和执行代码。这可以指导您进行优化。

  7. CPU 亲和力: 试验 CPU 关联性设置,以确保您的 Java 进程始终在特定 CPU 内核上运行。这有助于减轻 CPU 调度带来的可变性。

  8. 线程亲和力: 如果适用,请探索线程关联设置。确保线程在特定核心上一致运行可以减少可变性。

  9. 代码分析: 分析生成的字节码和机器代码以了解 JVM 如何优化您的代码。这种见解可能会指导您做出调整以获得更好的性能。

请记住,基准测试可能很敏感,结果可能会根据环境和特定 JVM 实现的不同而有所不同。必须仔细控制和监控这些因素,以获得可靠且可重复的性能测量结果。

© www.soinside.com 2019 - 2024. All rights reserved.