반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • item7
  • 플라이웨이트패턴
  • prerendering
  • 이펙티브자바
  • 느린 쿼리
  • 이펙티브 자바
  • 프로그래머의 뇌
  • 싱글톤 패턴
  • 팩터리 메서드 패턴
  • 추상 팩터리 패턴
  • 자바
  • 객체복사
  • 자바스크립트 면접
  • MSSQL
  • 스프링 검증
  • 참조 해제
  • 외래키 제약조건
  • 도커공식문서
  • 자바 검증
  • 슬로우 쿼리
  • rust
  • 싱글턴
  • Javadoc 자바독 자바주석 주석 Comment
  • 자바스크립트 인터뷰
  • 이펙티브 자바 item9
  • 메이븐 페이즈
  • bean Validation
  • NEXT JS
  • 자료구조
  • Next.js
  • 러스트
  • 서버리스 컴퓨팅
  • 디자인패턴
  • 작업기억공간
  • 토비의 스프링
  • try-with-resources
  • item8
  • pnpm
  • Pre-rendering
  • 빈 검증
  • Java
  • 싱글톤
  • 메이븐 라이프사이클
  • item9
  • next js app
  • 메이븐 골
  • serverless computing
  • 알고리즘
  • 자바 디자인패턴
  • 자바스크립트

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

Java/이펙티브 자바

이펙티브 자바, 쉽게 정리하기 - item 48. 스트림 병렬화는 주의해서 적용하라

2023. 5. 31. 22:46

이펙티브 자바, 쉽게 정리하기 - item 48. 스트림 병렬화는 주의해서 적용하라

자바 언어와 동시성

  • 동시성 프로그래밍에서는 항상 앞서가있었다.
    • 1996년부터 스레드, 동기화, wait/notify를 지원
    • 자바 5부터 java.util.concurrent, Executor 등을 선도적으로 지원했다.
    • 자바 7부터 fork/join 패키지를 추가
    • 자바 8부터 병렬 스트림을 지원
  • 스트림에서는 parallel()을 통해 손쉽게 동시성을 제공했다.

동시성 주의점

  • 안전성(safety) 과 응답 가능(liveness)

메르센 소수 구하기 예제로 parallel() 문제 살펴보기

메르센 소수란 2의 n승 빼기 1로 표현되는 소수를 말한다.

@Test
public void mersenne() {
    primes()
            .map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
            .filter(mersenne -> mersenne.isProbablePrime(50))
            .limit(20)
            .forEach(mp -> System.out.println(mp.bitLength() + ": " + mp));
}
  • 메르센 소수의 20번째까지 구하는 메서드이다.
  • 총 소요시간으로 5356 ms 가 출력됐다.
@Test
    public void mersenne() {
        primes()
                .parallel()
                .map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
                .filter(mersenne -> mersenne.isProbablePrime(50))
                .limit(20)
                .forEach(mp -> System.out.println(mp.bitLength() + ": " + mp));
    }
  • 병렬 연산을 이용해 퍼포먼스를 낫게 해보고 싶어 parallel()을 붙인다.
    • 그러나 이 코드 때문에 되려 연산이 끝나지 않는다.
    • 자바는 파이프라인을 병렬화할 방법을 찾아내지 못했기 때문이다.
  • 데이터 소스가 Stream.iterate 거나 중간 연산으로 limit 을 쓰면 병렬화로 성능 개선을 기대할 수 없다.
    • 파이프라인 병렬화는 limit 이 있을 때, CPU 코어가 남는다면 원소를 몇개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다고 가정한다.
    • 계속 버려지기 때문에 계속 이전까지의 값을 다시 구해야 한다.

스트림 파이프라인을 마구잡이로 병렬화하면 성능이 오히려 끔찍하게 나빠질 수 있다.

병렬화(parallel())를 적용하는 기준

  • 스트림의 소스가 ArrayList, HashMap, HashSet, ConcurrentHashMap의 인스턴스거나, 배열, int 범위, long 범위 등 쪼개기 쉬울 때 병렬화의 효과가 가장 좋다.
    • 데이터를 원하는 크기로 정확하고 손쉽게 나눌 수 있어서 스레드에 분배하기 좋다.
    • 위 자료구조는 참조 지역성이 뛰어나다. 이웃한 원소의 참조들이 메모리에 연속해서 저장되어 있다. 이 경우 성능이 좋아진다.
      • 기본타입 배열이 참조 지역성이 제일 좋다.
    • 참조 지역성이 낮으면, 스레드는 데이터가 주 메모리에서 캐시 메모리로 전송되어 오기를 기다리며 시간을 보내게 된다.

종단 연산과 병렬 수행 효율

  • 스트림 파이프라인의 종단 연산의 동작방식 역시 병렬 수행 효율에 영향을 준다.
    • 병렬 연산에 가장 적합한 것은 축소(reduction)이다.
    • 이도 역시 같은 원리를 따라서 쪼개기 쉬울수록 병렬화의 효과가 좋다.
      • 특정 집계 연산, 조건에 맞으면 반환하는 등의 메서드 (min, max, count, sum) 는 병렬화하기 좋다.
      • 조건에 맞으면 바로 반환하는 메서드들 (anyMatch, allMAtch, noneMatch) 도 적합하다.
  • spliterator()를 재정의하고 스트림의 병렬화 성능을 강도높게 테스트 후에 병렬화를 적용해야 한다.
  • 조건에 맞으면 parallel() 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 맛볼 수 있다.

스트림을 잘못 병렬화하면 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작이 발생할 수 있다. 결과가 잘못되거나 오동작하는 것을 안전 실패 (safety failure) 라고 한다.

안전 실패가 일어나지 않게 만들기 위해서는 Stream 의 명세를 잘 지켜야 한다. 이를테면 accumulator 와 combiner 함수는 반드시 결합 법칙을 지켜야 하며, 간섭받지 않아야 하고, 상태를 갖지 않아야 한다.

스트림 병렬화가 효과를 보는 경우의 코드: 2~n까지의 소수 구하기

public long pi(long n) {
    return LongStream.range(2, n)
            .parallel() // 1 sec 412 ms
            // 8 sec 192 ms, 약 5.81배 성능 향상
            .mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(50))
            .count();
}
  • 위 작업은 쪼개기 쉬우므로, parallel() 메서드가 효과가 있다.
    • 숫자들을 일정부분씩 쪼개 isProbablePrime()의 결과에 따라 나누면 된다.

무작위 수로 이뤄진 스트림을 병렬화하고 싶다면 ThreadLocalRandom 혹은 Random 보다는 SplittableRandom 인스턴스를 이용하는 것이 좋다. 처음부터 SplittableRandom 인스턴스의 목적은 이것이었다.

그냥 Random 의 경우 모든 연산을 동기화하기 때문에 병렬 처리하면 최악의 성능을 보일 수 있다.

핵심 정리

  • 무작정 병렬화를 한다고 속도가 빨라질 것이라 생각하지 말자.
  • 병렬화 시 오동작 등의 부작용도 항상 고려해야 한다.
    • 성능지표를 항상 유심히 관찰하자.
반응형
저작자표시 비영리 (새창열림)

'Java > 이펙티브 자바' 카테고리의 다른 글

이펙티브 자바, 쉽게 정리하기 - item 50. 적시에 방어적 복사본을 만들라  (0) 2023.06.07
이펙티브 자바, 쉽게 정리하기 - item 49. 매개변수가 유효한지 검사하라  (0) 2023.06.02
이펙티브 자바, 쉽게 정리하기 - item 47. 반환 타입으로는 스트림보다 컬렉션이 낫다  (0) 2023.03.31
이펙티브 자바, 쉽게 정리하기 - item 46. 스트림에서는 부작용 없는 함수를 사용하라  (0) 2023.03.30
이펙티브 자바, 쉽게 정리하기 - item 44. 표준 함수형 인터페이스를 사용하라  (0) 2023.03.29
    'Java/이펙티브 자바' 카테고리의 다른 글
    • 이펙티브 자바, 쉽게 정리하기 - item 50. 적시에 방어적 복사본을 만들라
    • 이펙티브 자바, 쉽게 정리하기 - item 49. 매개변수가 유효한지 검사하라
    • 이펙티브 자바, 쉽게 정리하기 - item 47. 반환 타입으로는 스트림보다 컬렉션이 낫다
    • 이펙티브 자바, 쉽게 정리하기 - item 46. 스트림에서는 부작용 없는 함수를 사용하라
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바