You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

已优化的Point类耗时过高,是否存在问题及优化空间?

不可变2D Point类的缓存优化问题

我自行编写了一个不可变的2D Point类,因为需要创建大量实例,所以实现了缓存机制来复用已存在的实例,以此提升内存效率。实际使用中,唯一的Point实例约有10万个,且需要多次获取这些实例。对应用做性能分析后,发现大部分耗时都集中在这个类上。

我想知道:是当前实现存在严重问题,还是耗时确实源于需要创建大量实例?这个类能否进一步优化?(注:需要支持并发访问)


原实现代码

public class Point implements Comparable<Point> {
    private static final Map<Integer, Map<Integer, Point>> POINT_CACHE = new ConcurrentHashMap<>();
    private static final boolean USE_CACHE = true;

    public final int row;
    public final int column;

    private int hashCache = -1;

    public static Point newPoint(int row, int column) {
        if (!USE_CACHE) return new Point(row, column);
        return POINT_CACHE.computeIfAbsent(row, k -> new ConcurrentHashMap<>()).computeIfAbsent(column, v -> new Point(row, column));
    }

    public static Point newPoint(Point point) {
        return newPoint(point.row, point.column);
    }

    protected Point(int row, int column) {
        this.row = row;
        this.column = column;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return row == point.row && column == point.column;
    }

    @Override
    public int hashCode() {
        //Assuming the matrix is less than 65k x 65k, this will return unique hashes
        if (hashCache == -1) hashCache = (row << 16) | column;
        return hashCache;

        //return Objects.hash(row, column);
    }

    //Getter
}

性能分析背景

性能分析结果显示,应用的大部分耗时集中在该Point类的相关操作上。


当前实现的问题分析

  • 嵌套ConcurrentHashMap的多层开销:两层ConcurrentHashMap的设计,每次获取实例都要经历两次哈希计算、两次锁竞争检查,高并发场景下,同一row对应的内层Map会成为锁竞争热点,这是主要性能瓶颈。
  • hashCache延迟初始化的微小竞争:多线程首次调用hashCode()时,会同时尝试初始化hashCache,虽然最终结果一致,但会产生不必要的指令执行。
  • Lambda实例的频繁创建:每次调用computeIfAbsent都会生成新的Lambda对象,高频调用下会增加对象创建和GC的负担。

优化方案

方案1:单一ConcurrentHashMap替代嵌套Map

利用row和column的范围限制(≤65536),将二者编码为一个long类型的单一键,用一层Map缓存,减少哈希和锁竞争的层级:

public class Point implements Comparable<Point> {
    private static final Map<Long, Point> POINT_CACHE = new ConcurrentHashMap<>();
    private static final boolean USE_CACHE = true;

    public final int row;
    public final int column;
    private final int hashCache; // 构造时直接初始化,避免延迟竞争

    public static Point newPoint(int row, int column) {
        if (!USE_CACHE) return new Point(row, column);
        // 将row和column编码为long键
        long key = ((long) row << 32) | (column & 0xFFFFFFFFL);
        return POINT_CACHE.computeIfAbsent(key, k -> new Point(row, column));
    }

    public static Point newPoint(Point point) {
        return newPoint(point.row, point.column);
    }

    protected Point(int row, int column) {
        this.row = row;
        this.column = column;
        this.hashCache = (row << 16) | column; // 构造阶段直接计算哈希值
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return row == point.row && column == point.column;
    }

    @Override
    public int hashCode() {
        return hashCache;
    }

    @Override
    public int compareTo(Point o) {
        int rowCompare = Integer.compare(this.row, o.row);
        return rowCompare != 0 ? rowCompare : Integer.compare(this.column, o.column);
    }
}

优化点:

  • 减少一层Map操作,降低哈希计算和锁竞争的次数
  • 构造时直接初始化哈希缓存,消除延迟初始化的线程竞争
  • 整体操作链路更简洁,减少Lambda对象的创建频率

方案2:数组缓存(适用于行列范围明确且连续的场景)

如果row和column是从0开始的连续整数(比如0~99999),用二维数组替代Map,数组访问是O(1)且无锁竞争,性能最优:

public class Point implements Comparable<Point> {
    private static Point[][] POINT_CACHE;
    private static final boolean USE_CACHE = true;

    // 静态初始化数组,根据实际最大行列数调整
    static {
        int maxRow = 100000;
        int maxColumn = 100000;
        POINT_CACHE = new Point[maxRow + 1][maxColumn + 1];
    }

    public final int row;
    public final int column;
    private final int hashCache;

    public static Point newPoint(int row, int column) {
        if (!USE_CACHE) return new Point(row, column);
        // 边界检查,避免数组越界
        if (row < 0 || row >= POINT_CACHE.length || column < 0 || column >= POINT_CACHE[row].length) {
            return new Point(row, column);
        }
        Point p = POINT_CACHE[row][column];
        if (p == null) {
            // 双重检查锁定,避免并发创建重复实例
            synchronized (POINT_CACHE) {
                p = POINT_CACHE[row][column];
                if (p == null) {
                    p = new Point(row, column);
                    POINT_CACHE[row][column] = p;
                }
            }
        }
        return p;
    }

    // equals、hashCode、compareTo方法同方案1
}

注意:此方案需要提前明确行列的最大范围,若范围过大(如百万级)会占用大量内存,仅适合实例数量固定且范围明确的场景。

方案3:弱引用缓存(适用于实例可被GC回收的场景)

如果Point实例在不需要时可以被垃圾回收,避免内存占用过高,可以用Guava的CacheBuilder实现带弱引用的并发缓存:

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

public class Point implements Comparable<Point> {
    private static final Cache<Long, Point> POINT_CACHE = CacheBuilder.newBuilder()
            .concurrencyLevel(Runtime.getRuntime().availableProcessors())
            .weakValues() // 当实例无其他引用时自动回收
            .build();
    private static final boolean USE_CACHE = true;

    public final int row;
    public final int column;
    private final int hashCache;

    public static Point newPoint(int row, int column) {
        if (!USE_CACHE) return new Point(row, column);
        long key = ((long) row << 32) | (column & 0xFFFFFFFFL);
        try {
            return POINT_CACHE.get(key, () -> new Point(row, column));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // equals、hashCode、compareTo方法同方案1
}

此方案适合实例可能被废弃的场景,避免缓存长期占用内存。


总结

当前实现的主要性能瓶颈是嵌套ConcurrentHashMap的多层锁竞争和哈希开销,并非单纯的实例创建问题。优化优先级建议:

  1. 若行列范围明确且连续,优先选择数组缓存,性能最优;
  2. 若行列范围不连续,选择单一ConcurrentHashMap方案,比原嵌套Map性能提升明显;
  3. 若需要缓存自动回收,考虑使用Guava Cache等带弱引用的缓存实现。

内容的提问来源于stack exchange,提问作者Dennis Höhl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 12:43:28