본문 바로가기
카테고리 없음

모던 자바 인 액션 스터디 5. 병렬 데이터 처리와 성능

by 손너잘 2021. 3. 23.

웨지

7장문제 나갑니당~~ 7장 넘 어렵네요 

 O/X 3개 낼게요 이유도 적어주셔요병렬 스트림 문제

  1. 동시성 문제를 해결했다면, 병렬 스트림을 활용하는게 항상 성능적으로 우수하다.
  2. 내부적으로 스트림이 분할되는 기준에 대해서는 개발자가 알 수 없다.
  3. Spliterator의 Characteristics 옵션은 Collectors의 것과 동일하다

나의 답

더보기

X 그렇지 않다. 원소의 개수가 적을 경우, 병렬 계산을 위한 스래드 생성 비용이 클 수도 있다.
X 그렇지 않다. 스트림의 분할 기준은 개발자가 정해서 구현한다(기본으로는 프로세서 개수 기준이다).
X 그렇지 않다. 아쉽게도 둘은 서로 다른 요소를 가지고 있다

웨지의 답

  1. x ,스레드를 스위칭하는 오버헤드가 더 들수도 있다. 벤치마크를 통해 오버헤드에 걸리는 시간 vs 병렬스트림 수행으로 보는 이득보는 시간 을 검증해보아야 한다
  1. x, spliterator를 살펴봄으로써 알 수 있다. custom spliterator를 구현함으로써 기준을 정해줄 수 도 있다.
  1. x, ORDERED, DISTINCT, SORTED, SIZED, NON-NULL, IMMUTABLE, CONCURRENT, SUBSIZED 가 있으며 Collector와 겹치는 Ordered, Concurrent가 있으나 다른 방식으로 정의되었다고 한다



완태

7장포크/조인 부분에서, fork의 개념에 대해서 다시금 생각해보면 좋을 것 같아서, 생각을 해볼만한 질문들을 남겨요!

  1. Github에서 fork를 떠서 프로젝트를 진행했었는데요, Github fork와 7장에서 설명하는 fork와는 어떤 개념적 유사성이 있을까요? 자유롭게 얘기해주세요
  2. 크롬 브라우저를 많이 사용하는데요, 동시에 여러 탭에 유튜브 영상을 틀어놓더라도 병렬로 실행이 됩니다. 크롬 브라우저의 탭 작동 방식을 fork로 설명을 해주세요!(실제로 그런지는 저도 모릅니다.. ㅎ)

다소 질문들이 추상적인데, fork의 개념을 잘 이해하냐가 이번 장에 중요한 요소라 생각해서 내봤습니다!

 

나의 답

더보기

1. 데이터를 가지고 와서 각자 처리하는 부분에서 유사성이 있다. fork를 통해 코드를 수정하고 pr을 날리는 행위가 포크조인과 비슷하다고 볼 수 있다. 다만, 데이터 일부를 가져오느냐, 전체를 가지고 오느냐의 차이점이 존재한다.

2. 개인적으로는 크롬의 탭과 포크조인의 포크의 유사성을 잘 모르겠다. 포크는 부분 작업을 처리하기 위해 스레드로 분리하고, 크롬은 각 탭이 프로세스로 작동하는 차이가 존재하며 데이터를 공유 하는지는...? 모르겠다.


<주의> 7장 아닙니다.
파즈의 stream에서 count의 감명을 받아.. 한번 해봤는데요,,,
이 테스트의 출력문은 어떻게 될까요??(주의: 함정 있음; - 나한테만 함정일수도, 함정이란 말이 함정일수도, (아무말 대잔치))

 @Test
    void withFilteringAndSorting() {
        List<Car> cars = Arrays.asList( new Car(100), new Car(1000),new Car(2000),new Car(3000));
        Stream<Car> stream1 = cars.stream();
        Stream<Car> stream2 = stream1.peek(Car::printPrice);
        Stream<Car> stream3 = stream2.filter(Car::isExpensive);
        System.out.println("filtering");
        Stream<Car> stream4 = stream3.peek(Car::printPrice);
        Stream<Car> stream5 = stream4.sorted(Comparator.comparing(Car::getPrice));
        System.out.println("sorting");
        Stream<Car> stream6 = stream5.peek(Car::printPrice);
        System.out.println(stream6.count());
    }
    public class Car{
        private final int price;
        public Car(int price) {
            this.price = price;
        }
        public boolean isExpensive() {
            return price > 1000;
        }
        public void printPrice() {
            System.out.println("Car: " + price);
        }
        public int getPrice() {
            return price;
        }
    }

나의 답

더보기

filtering
sorting
2

완태의 답

filtering
sorting
Car :100
Car :1000
Car :2000
Car :2000
Car :3000
Car :3000
Car :2000
Car :3000
2

 


와일더

1. 반복작업을 진행할 때, 반복문보다 병렬스트림으로 처리하는것이 효율적이다. (O/X)
2. LongStream.rangeClosed 메서드는 기본형 long을 직접 사용하므로 박싱과 언박싱 오버헤드가 사라진다. (O/X)
3. 처리해야할 요소 수와 처리하는데 드는 비용 중  처리하는게 드는 비용이 클수록 병렬스트림으로 성능 개선 가능성이 높아진다. (O/X)
4. Spliterator 를 사용하려면 어떻게 동작하는지 이해하고 직접 구현해야한다. (O/X)5. Custom Spliterator 구현하기
함수형으로 “검색하고자 하는 단어 수” 를 구하는 메서드를 구현해보자.
아래 기능이 동작하도록 WordCounter 를 만들어보자.

 

나의 답

더보기

1. x 데이터 요수의 수에 따라 달라진다.

2. x 어떻게 사용하느냐에 따라 다르다.

3. 맞다.

4. 아니다. 기본적으로 제공해준다.

5. 

 

나봄

 

포크/조인 프레임워크 관련 문제입니다!!
책에는 안 나오는 내용이지만 그래도 포크/조인 프레임워크를 더 잘 이해하기 위해서는 원래의 ExecutorService와 어떤 점이 다른지 아는 것이 좋을 것 같아서 이번 문제를 준비하게 되었습니다!
아래의 그림은 전형적인 ExecutorService 흐름입니다!

  1. 과연 포크/조인 프레임워크는 어떤 방식으로 ExecutorService를 새롭게 구현했을까요? (힌트 : 쓰레드 풀에서 쓰레드가 조금씩 달라져요!!)

2. 그리고 어떠한 문제점 떄문에 포크/조인 프레임워크가 나왔을까요?
3. 가끔 일반적인 ExecutorService를 사용하는 것보다 포크/조인 프레임워크가 더 느릴 때가 있는데 어떤 이유들을 생각할 수 있을까요? (edited)

나의 답

더보기

1. 모르겠다.

나봄의 답

 

  1. 각 쓰레드에게 Task queue가 추가가 된다(deque 형식으로). 그리고 각 쓰레드 테스크 큐에서 포크작업이 일어나고 다른 쓰레드는 그 테스크 큐에서 포크된 테스크를 스틸해와 작업을 시작한다.
  2. 만일 3가지 작업과 3가지 쓰레드가 있다고 가정해본다. 하나의 쓰레드는 3초, 하나의 쓰레드는 5초, 마지막 다른 쓰레드는 10초가 작업을 하는 데 걸리는 시간이라면 모든 작업을 모아서 결과물로 돌려주려면 총 10초가 걸리게 된다. (3초 걸린 쓰레드와 5초 걸린 쓰레드는 그동안 노는 쓰레드가 되어버린다.) 하지만 노는 쓰레드가 없이 모두 동등하게 작업을 나눌 수만 있다면 걸리는 시간은 총 6초로 줄일 수가 있다. 이렇게 노는 쓰레드가 없이 모두 동등하게 작업을 나누기 위해 포크/조인 프레임워크가 나오게 되었다. 그렇기때문에 동등하게 작업을 나누는 포킹작업이 아주 중요하다!
  3. 기존 ExecutorService와 가장 다른 점은 쓰레드 개별로 작업 큐를 가진다는 것과 포킹과 조인 작업이 생긴다는 것이다. 만일 기존 ExecutorService에 task가 들어올 때 이미 거의 동등하게 나눠진 task로 들어온다면 쓰레드 개별로 작업 큐를 만드는 비용과 포킹 비용이 없기때문에 더 빠르다.

찰리

7장 문제
Spliterator가 너무 어려워서 시간을 들여서 다시 봐야할거같아요..
O/X 퀴즈로 가겠습니다

  1. ForkJoin 프레임워크에서 Task를 분할할 때 fork() 했던 Task는 비동기 실행되어 해당 Task의 결과를 기다리지 않고 다음 작업을 수행한다. (책의 예시에서는 leftTask)
  2. ForkJoin 프레임워크에서 분할된 Task에 compute() 를 호출하면 더이상 분할되지 않고 실행된다.
  3. LinkedList는 병렬 스트림을 구성하는 자료구조로 좋은 편이다.
  4. 스트림에 squential()과 parallel()이 동시에 사용된 경우 뒤에 선언한 것은 무시된다.

.squential()  // 순차 스트림으로 동작한다.
.parallel()

.squential()  // 순차 스트림으로 동작한다.
.parallel()

5. 소량의 데이터를 병렬 스트림으로 사용할 때 이득을 얻을 수 없는 이유는 병렬화로 얻을 수 있는 이득이 병렬화 비용보다 크기 때문이다.(병렬화 비용 < 병렬화로 얻을 수 있는 이득) 

 

나의 답

더보기

1. 맞다. 한쪽은 비동기, 한쪽은 동기 실행 시킴으로써 스레드 생성의 성능적 이점을 얻을 수 있다.

2. 맞다. 동기적으로 실행된다.

3. 아니다. LinkedList는 특정 요서를 찾기 위해 선형탐색을 진행해야 하므로 성능상 안좋을 수 있다.

4. 아니다. 뒤에 선언된 것으로 실행된다.

5. 아니다. 병렬화로 얻을 수 있는 이득이 병렬화 비용보다 작다.

찰리의 답

 

  1. O - fork() 했던 Task는 비동기 실행되어 결과를 기다리지 않고 join()을 통해서 결과를 받습니다.
  2. X - compute()를 재귀적으로 호출해서 분할하는것
  3. X - LinkedList는 분할하기 위해 모든 요소를 탐색해야 해서 분해성이 나쁜 자료구조 입니다.
  4. X - 가장 마지막으로 호출된 메서드가 적용됩니다.
  5. X - 병렬화 비용 > 병렬화로 얻을 수 있는 이득

 

댓글