변경된 코드


 public List<CombinationDto> sortByDistance(
            List<Store> firstStores,
            List<Store> secondStores,
            List<Store> thirdStores) {

        List<CombinationDto> combinationDtos = combineForDistance(firstStores, secondStores, thirdStores);
        List<CombinationDto> list = combinationDtos.stream()
                .sorted(Comparator.comparingDouble(CombinationDto::getTotalDistance)
                        .reversed()).limit(20)
                .toList();

        return list;
    }

    public List<CombinationDto> combineForDistance(
            List<Store> firstStores,
            List<Store> secondStores,
            List<Store> thirdStores) {

        DistanceCalculator distanceCalculator = new DistanceCalculator();
        ConcurrentLinkedQueue<CombinationDto> combinations = new ConcurrentLinkedQueue<>();
        int firstSize = firstStores.size();
        int secondSize = secondStores.size();
        int thirdSize = thirdStores.size();

        IntStream.range(0, firstSize).parallel().forEach(i -> {
            Store firstStore = firstStores.get(i);
            IntStream.range(0, secondSize).parallel().forEach(j -> {
                Store secondStore = secondStores.get(j);
                IntStream.range(0, thirdSize).parallel().forEach(k -> {
                    Store thirdStore = thirdStores.get(k);
                    CombinationDto combinationDto = new CombinationDto(List.of(firstStore, secondStore, thirdStore));
                    double totalDistance = distanceCalculator.calculateDistance(combinationDto);
                    combinationDto.setDistance(totalDistance);
                    combinations.add(combinationDto);
                });
            });
        });

        return new ArrayList<>(combinations);
    }
    
    
    public class DistanceCalculator {

    public static final double RADIUS = 6371; //지구 반지름(km)
    public static final double TO_RADIAN = Math.PI / 180;
    private final ConcurrentMap<String, Double> distanceCache = new ConcurrentHashMap<>();

    public double calculateDistance(CombinationDto combination) {
        List<Store> stores = combination.getStores();
        double distance = 0;

        for (int i = 0; i < stores.size() - 1; i++) {
            distance += calculate(
                    new Coordination(stores.get(i).getLatitude(), stores.get(i).getLongitude()),
                    new Coordination(stores.get(i + 1).getLatitude(), stores.get(i + 1).getLongitude()));
        }

        return distance;
    }

    private double calculate(Coordination firstCoordination, Coordination secondCoordination) {

        String key = firstCoordination.getLatitude() + "," + firstCoordination.getLongitude() +
                "-" + secondCoordination.getLatitude() + "," + secondCoordination.getLongitude();

        // 캐시에서 거리 찾기
        double cahedDistance = distanceCache.getOrDefault(key, Double.NaN);
        if (!Double.isNaN(cahedDistance)) {
            return cahedDistance;
        }

        double firstLatitude = firstCoordination.getLatitude();
        double firstLongitude = firstCoordination.getLongitude();
        double secondLatitude = secondCoordination.getLatitude();
        double secondLongitude = secondCoordination.getLongitude();

        double deltaLatitude = Math.abs(firstLatitude - secondLatitude) * TO_RADIAN;
        double deltaLongitude = Math.abs(firstLongitude - secondLongitude) * TO_RADIAN;

        double sinDeltaLatitude = Math.sin(deltaLatitude / 2);
        double sinDeltaLongitude = Math.sin(deltaLongitude / 2);

        double squareRoot = Math.sqrt(
                sinDeltaLatitude * sinDeltaLatitude +
                        Math.cos(firstLatitude * TO_RADIAN) *
                                Math.cos(secondLatitude * TO_RADIAN) *
                                sinDeltaLongitude * sinDeltaLongitude);

        double distance = 2 * RADIUS * Math.asin(squareRoot);
        distanceCache.put(key, distance);

        return distance;
    }

    public void distanceCacheClear() {
        distanceCache.clear();
    }
}

기존 코드

    public List<CombinationDto> sortByDistance(
            List<Store> firstStores,
            List<Store> secondStores,
            List<Store> thirdStores) {

        DistanceCalculator distanceCalculator = new DistanceCalculator();

        List<CombinationDto> combinationDtos = combineForDistance(firstStores, secondStores, thirdStores);

        combinationDtos.sort((a, b) -> {
            double totalDistanceOfA = distanceCalculator.calculateDistance(a);
            double totalDistanceOfB = distanceCalculator.calculateDistance(b);

            return Double.compare(totalDistanceOfB, totalDistanceOfA);
        });

        distanceCalculator.distanceCacheClear();

        return combinationDtos;
    }

    public List<CombinationDto> combineForDistance(
            List<Store> firstStores,
            List<Store> secondStores,
            List<Store> thirdStores) {

        List<CombinationDto> combinations = new ArrayList<>();
        int firstSize = firstStores.size();
        int secondSize = secondStores.size();
        int thirdSize = thirdStores.size();

        IntStream.range(0, firstSize).forEach(i -> {
            Store firstStore = firstStores.get(i);
            IntStream.range(0, secondSize).forEach(j -> {
                Store secondStore = secondStores.get(j);
                IntStream.range(0, thirdSize).forEach(k -> {
                    Store thirdStore = thirdStores.get(k);
                    combinations.add(new CombinationDto(List.of(firstStore, secondStore, thirdStore)));
                });
            });
        });

        return combinations;
    }
  1. 기존 코드의 문제점 —>병렬 처리의 효율성이 떨어짐

병렬 처리를 해도 각 스레드의 연산이 가벼워서 오히려 병렬 처리의 단점이 부각됨

  1. sort과정에서 병목 현상이 일어남 —>거리 계산을 하고 동시에 sorting을 하는 과정에서 병목이 나타난 것으로 예상

해결 방법

  1. combination하는 과정에서 거리를 계산한다. —>그리고 dto에 넣는다.
@Getter
@RequiredArgsConstructor
public class CombinationDto {

    private final List<Store> stores;
    private double totalDistance;

    public void setDistance(double totalDistance) {
        this.totalDistance = totalDistance;
    }
}

  1. 스레드 하나의 작업이 생각보다 오래 걸리기 때문에 병렬 처리를 하면 효율성이 나타날 수 있다.

  2. 정렬을 할 때는 미리 계산해둔 총 거리만을 가져와서 sorting을 하게 한다.