ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MultiThread 적용 에피소드
    카테고리 없음 2023. 6. 6. 19:03

     

    스파르타 코딩클럽에서 제공해준 강의를 보다 배운적 없는 Thread 에 흥미가 생겼다

    너무 기본을 알려주고 말아서 추가로 더 알아봤는데 ExecutorService 인터페이스를 알게됬다 간단히 보자면

     

    newFixedThreadPool() 반복할 횟수를 넣어 쓰레드를 생성하고,

    executorService.execute(() -> { ... }); 으로 영역을 지정해준뒤 교착상태 방지를 위해 예상지점에

    synchronized(){ } 를 설정해주고 try catch 로 Exception 에 대한 처리해주면 간단하게 사용할 수 있었다.

     

    이제 기존 코드와 다중 스레드 응답속도를 체크해보자

     

    기존 코드 = Origin, 다중 스레드 = Multi 로 칭함

    public ListApiResponse<String> statisticalNumberMulti(StatisticalNumberRequest request, User user) {
       // Service 생략
       ExecutorService executorService = Executors.newFixedThreadPool(value);
    
        for (int i = 0; i < value; i++) {
            executorService.execute(() -> {
                Map<Integer, Integer> localCountMap = new HashMap<>(countMap);
                for (int j = 0; j < repetition; j++) {
                    Set<Integer> set = new HashSet<>();
    
                    while (set.size() < 6) {
                        int num = rd.nextInt(45) + 1;
                        set.add(num);
                    }
    
                    for (int num : set) {
                        int count = localCountMap.get(num);
                        localCountMap.put(num, count + 1);
                    }
                }
    
                List<Integer> list = new ArrayList<>(localCountMap.keySet());
                list.sort((num1, num2) -> localCountMap.get(num2).compareTo(localCountMap.get(num1)));
    
                List<Integer> checkLotto = list.subList(0, 6);
                Collections.sort(checkLotto);
                String result = checkLotto.stream().map(Object::toString).collect(Collectors.joining(" "));
                synchronized (topNumbers) {
                    topNumbers.add(result);
                }
            });
        }
    
       executorService.shutdown();
       long endTime = System.currentTimeMillis();
       long executionTime = endTime - startTime;
       System.out.println("멀티스레드 방식: " + executionTime + " milliseconds");
    
       try {
          executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
       } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
       }
    
       SixNumber sixNumber = new SixNumber(user.getId(), LocalDate.now(), topNumbers);
       sixNumberRepository.save(sixNumber);
       saveMainLottoListMulti(topNumbers);
       return ListApiResponse.ok("요청 성공", topNumbers);
    }

     

    value = 5, repetition 1000, 888888 두개를 체크해볼 예정이다.

    전자는 개인 프로잭트 최소 단위이고,

    후자는 필자가 로또 번호 찍는데 이용하는 횟수 ( 결과는 처참하다 5만원 넣어서 5000원 2번 당첨... )

     

    repetition 1000 의 경우 두배 이상 느리게 나온다

     

    repetition 888888 의 경우 약 23.5% 개선

     

    여기서 의문이 생겼다 그럼 value 값을 높여 오버헤드 증가시켰을 때 병렬 처리 이점보다 비용이 더 커질까 라는 그래서 요번엔 value = 2000,  repetition 1000 으로 체크해보겠다.

    음 ..? 많은 쓰레드를 생성했는데도 Multi 가 빠르다.

    value = 2000,  repetition 888888 도 비슷한 결과를 보인다.

     

    그럼 오버헤드라는건 어디서 어떻게 발생하는걸까 ? 라는 의문이 있었는데 넘어갔었다 하지만 곧 그 문제를 직면하게 된다

     

    다중 스레드로 처리속도 재미를 봐서 처리가 많은 생길만한 다른 코드에도 적용해보기로 했고 요번단 병렬 처리를 지원하는 parrllelStream() 을 이용하여 처리했는데

     

    private void saveMainLottoListMulti(List<String> list) {
       Lotto lotto = lottoRepository.findByMain()
          .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 정보"));
    
       List<Integer> countList = lotto.getCountList();
       long startTime = System.nanoTime();
       list.parallelStream().forEach(sentence -> {
          String[] numbers = sentence.split(" ");
          for (String numberStr : numbers) {
             int num = Integer.parseInt(numberStr) - 1;
             synchronized (countList) {
                countList.set(num, countList.get(num) + 1);
             }
          }
       });
    
       long endTime = System.nanoTime();
       long executionTime = endTime - startTime;
       System.out.println("parallelStream: " + executionTime + " nanoTime");
    }

     

    해당 로직을 Multi 에서 값을 받아 처리하게 되면 처리속도가 앞도적으로 느리게 찍힌다. 약 130배

     

    Origin :

    Multi :

     

    조건식을 바꿔도 결과값을 비슷하다. 이러한 현상을 설명할 순 없지만 필자는 일단 이것이 오버헤드 비용이라 생각하고 더 공부하려고 한다. 서로 다른 Thread 를 한 로직안에서 사용할 때 말도 안되는 비용이 든다라고, 한 로직 안에 하나의 쓰레드만 사용하자라고 이만 마치겠다

Designed by Tistory.