From 31820f785632a68d089361211a5abc7a74918eef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EA=B9=80=EA=B2=BD=EB=AF=B8?=
<109158497+kyum-q@users.noreply.github.com>
Date: Fri, 7 Jun 2024 00:42:07 +0900
Subject: [PATCH] =?UTF-8?q?[7=ED=9A=8C=EC=B0=A8]=20=EC=95=84=EC=9D=B4?=
=?UTF-8?q?=ED=85=9C=2048,=2053=20-=20=EC=BC=AC=EB=AF=B8(=EA=B9=80?=
=?UTF-8?q?=EA=B2=BD=EB=AF=B8)=20(#69)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 아이템 48. 스트림 병렬화는 주의해서 적용하라
* 아이템 53. 가변인수는 신중히 사용하라
* Rename 스트림_병렬화는_주의해서_적용하라_켬미.md to 07장/아이템_48/스트림_병렬화는_주의해서_적용하라_켬미.md
* Update 스트림_병렬화는_주의해서_적용하라_켬미.md
* Update 스트림_병렬화는_주의해서_적용하라_켬미.md
---
...0\353\235\274_\354\274\254\353\257\270.md" | 240 ++++++++++++++++++
...0\353\235\274_\354\274\254\353\257\270.md" | 115 +++++++++
2 files changed, 355 insertions(+)
create mode 100644 "07\354\236\245/\354\225\204\354\235\264\355\205\234_48/\354\212\244\355\212\270\353\246\274_\353\263\221\353\240\254\355\231\224\353\212\224_\354\243\274\354\235\230\355\225\264\354\204\234_\354\240\201\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md"
create mode 100644 "08\354\236\245/\354\225\204\354\235\264\355\205\234_53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md"
diff --git "a/07\354\236\245/\354\225\204\354\235\264\355\205\234_48/\354\212\244\355\212\270\353\246\274_\353\263\221\353\240\254\355\231\224\353\212\224_\354\243\274\354\235\230\355\225\264\354\204\234_\354\240\201\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md" "b/07\354\236\245/\354\225\204\354\235\264\355\205\234_48/\354\212\244\355\212\270\353\246\274_\353\263\221\353\240\254\355\231\224\353\212\224_\354\243\274\354\235\230\355\225\264\354\204\234_\354\240\201\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md"
new file mode 100644
index 0000000..8336c00
--- /dev/null
+++ "b/07\354\236\245/\354\225\204\354\235\264\355\205\234_48/\354\212\244\355\212\270\353\246\274_\353\263\221\353\240\254\355\231\224\353\212\224_\354\243\274\354\235\230\355\225\264\354\204\234_\354\240\201\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md"
@@ -0,0 +1,240 @@
+# 자바에서 병렬 처리 방식
+
+(자바 5부터)
+ 자바에 동시성 컬렉션 'java.util.concurrent' 라이브러리
+ 실행자(Executor) 프레임워크 지원
+
+(자바 7부터)
+고성능 병렬 분해(parallel decom-position) 프레임워크인 포크-조인(fork-join) 패키지
+
+(자바 8부터)
+parallel 메서드만 한 번 호출하면 파이프라인을 병렬 실행할 수 있는 스트림을 지원
+
+
+
+# 잘 사용해야겠죠?
+
+> 동시성 프로그래밍 할 때 안전성과 응답 가능 상태를 유지하기 위해 애써야 한다.
+
+```java
+public static void main(String [] args) {
+ primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE))
+ .filter(mersenne -> marsenne.isProbablePrime(50))
+ .limit(20)
+ .forEach(Sysrem.out::println);
+}
+
+static Stream primes() {
+ return Stream.iterate(TWO, BigInteger::nextProbablePrime);
+}
+```
+
+> 그냥 실행 : 12.5초
+> 스트림 파이프라인의 parallel()을 호출해서 병렬처리 실행 : 응답 없음 진짜 오래 걸림
+
+
+
+### 스트림 라이브러리가 이 파이프라인을 병렬화로는 성능 개선을 기대할 수 없다.
+
+환경이 아무리 좋아도 데이터 소스가 `Stream.iterate`거나 중간 연산자로 `limit`를 쓰면 파이프라인 병렬화로는 성능 개선을 기대할 수 없다.
+
+
+
+### Stream.iterate 는 왜?
+
+iterate 연산은 순서에 의존하는 연산이기 때문에 스트림 원소를 분할하기 어렵다.
+
+#### limit 왜?
+
+> 가정 : limit를 다룰 때 CPU 코어가 남는다면 원소를 몇개 더 처리한 후 제한된 개수 이후의 결과를 버려도 아무런 해가 없다.
+
+이 코드의 경우 새롭게 메르센 소수를 찾을 때마다 그 전 소수를 찾을 때보다 두 배 정도 오래거린다.
+즉, 원소 하나를 계산한느 비용이 대략 그 이전까지의 원소 전부를 계산한 비용을 합친 것만큼 든다는 뜻
+
+
+![](https://i.imgur.com/3raz8Jx.png)
+
+> 그래서 파이프라인은 자동 병렬화 알고리즘이 제 기능을 못하게 마비시키다.
+
+### 그럼 더 안좋아지고 이런데 어떻게 해?
+
+스트림 파이프라인을 마구잡이로 병렬화하면 안된다.
+
+**성능이 오히려 나빠질 수 있다.**
+
+
+
+# 그럼 어떻게 사용하면 좋아요?
+
+
+
+## 생성/중간 연산 좋은 예
+
+- 스트림의 소스 : `ArrayList`, `HashMap`, `HashSet`, `ConcurrentHashMap`의 인스턴스
+- 배열 범위 : int 범위, long 범위
+
+**병렬화 효과 굿 !**
+
+
+
+### 왜 좋은데?
+
+
+
+#### 분배가 편해요
+
+이 자료구조들은 모두 데이터를 **원하는 크기로 정확하고 손쉽게 나눌 수 있어**서 일을 **다수의 스레드에 분배하기에 좋다**는 특징이 있다.
+
+작업 나누기는 `Spliterator` 가 담당
+- `Spliterator` 객체는 `Stream` 이나 `Iterable` 의 `spliterator` 메서드로 얻어올 수 있음
+
+
+
+#### 참조 지역성이 뛰어나요
+
+**이웃한 원소의 참조들이 메모리에 연속**해서 **저장**되어 있다는 뜻
+참조들이 가리키는 실제 객체가 메모리에서 서로 떨어져 있을 수 있는데, 그러면 참조 지역성이 나빠진다.
+
+![](https://i.imgur.com/E3ioJbu.png)
+
+
+참조 지역성이 낮으면 데이터가 주 메모리에서 캐시 메모리로 전송되어 오기를 기다리며 대부분 시간을 멍하니 보내게 된다.
+
+그래서 참조 지역성은 다량의 데이터를 처리하는 벌크 연산을 병렬화 할 때 아주 중요한 요소로 작용한다.
+
+
+
+## 종단 연산 좋은 예
+
+
+
+### 축소(reduction) 이 접합하다
+
+- Stream.reduce
+- min, max, count, sum (완성된 형태로 제공되는 메서드)
+- anyMatch, allMatch, noneMatch (조건에 맞으면 바로 반환되는 메서드)
+
+
+
+### 가변 축소 (mutable reduction) 을 수행하는 stream의 collect 메서드는 병렬화에 적합하지 않다
+
+Why? 컬렉션들을 합치는 부담이 크기 때문
+
+
+> 직접 구현한 Stream, Iterable, Collection 이 병렬화의 이점을 제대로 누리게 하고 싶다면?
+>
+> - `spliterator` 메서드를 반드시 재정의하고 결과 스트림의 병렬화 성능을 강도 높게 테스트하라
+
+
+
+# 그러니까 주의하자 ~
+
+### 성능 더 나빠지거나 결과가 잘못될 수 있음
+
+스트림을 잘못 병렬화하면 (응답 불가를 포함해) 성능이 나빠질 뿐만 아니라 결과 자체가 잘못되거나 예상 못한 동작이 발생할 수 있다.
+
+> 안전 실패 : 결과가 잘못되거나 오동작하는 것
+
+안전 실패는 병렬화한 파이프라인이 사용하는 mappers, filters, 혹은 프로그래머가 제공한 다른 함수 객체가 명세대로 동작하지 않을 때 벌어질 수 있다.
+Stream 명세는 이때 사용되는 함수 객체에 관한 엄중한 규약을 정의해놨다.
+
+Stream의 reduce 연산에 건네지는 accumulator(누적기) 와 combiner(결합기) 함수는 다음 요건을 따라야한다.
+
+- associative : 반드시 결합 법칙을 만족한다.
+- non-interfering : 간섭받지 않는다.
+- stateless : 상태를 갖지 않아야한다.
+
+
+물론 이 요구사항을 지키지 못하는 상태라도 파이프라인을 순차적으로만 수행한다면야 올바른 결과 GET
+
+BUT 요구사항도 안지키고, 병렬로 수행하면 결과 처참 !
+
+
+그러니 위에 메르센 소수도 올바르게 출력하고 싶으면 종단 연산 `forEach`, `forEachOrdered`로 바꿔주면 된다.
+
+> -> 병렬 스트림들을 순회하며 소수를 발견한 순서대로 출력되도록 보장해줌
+
+
+
+### 성능 향상이 정말 되는지 확인해봐, 병렬화는 성능 최적화 수단일 뿐!
+
+파이프라인이 수행하는 진짜 작업이 병렬화에 드는 추가 비용을 상쇄하지 못한다면 성능 향상은 미미할 수 있다.
+
+> 실제로 성능이 향상될지를 추정해보는 간단한 방법
+>
+> 스트림 안에 원소 수와 원소당 수행되는 코드 줄 수를 곱해봐라.
+> (이 값이 최소 수십만은 되어야 성능 향상을 맛볼 수 있다.)
+
+#### 성능 테스트 하세요
+
+다른 최적화와 마찬가지로 **변경 전 후 반드시 성능을 테스트**하여 병렬화를 사용할 가치가 잇는지 확인 해라 !
+
+> 이상적으로는 운영 시스템과 흡사한 환경에서 테스트하는 것이 좋다
+
+
+
+#### 스레드 문제도 일으킬 수 있어요
+
+보통 병렬 스트림 파이프라인도 공통의 포크-조인 풀에서 수행 (같은 스레드 풀 사용)되므로, 잘못된 파이프라인 하나가 시스템의 다른 부분의 성능에까지 악영향을 줄 수 있음을 유념하자
+
+
+
+# 그냥 안쓸까..?
+
+
+
+### 그래도 돼
+
+네 여러분이 스트림 파이프라인을 병렬화 할 일이 적습니다 ~
+
+스트림을 많이 사용하는 수백만 줄짜리 코드를 여러 개 관리하다보면 그 중 스트림 병렬화가 효과가 많지 않음을 알게 됩니다 !
+
+
+
+### 그치만 좋을 때도 있긴해 !
+
+**하지만**
+조건이 잘 갖춰지면 parallel 메서드 호출 하나로 거의 프로세서 코어 수에 비례하는 성능 향상을 만끽할 수도 있어요 !
+
+> ex. 머신러닝, 데이터 처리
+
+
+
+#### 효과 굿 예시
+
+> 소수 계산 스트림 파이프라인
+
+
+```java
+static long pi(long n) {
+ return LongStream.rangeClosed(2, n)
+ .parallel() // 병렬
+ .mapToObj(BigInteger::valueOf)
+ .filter(i -> i.isProbablePrime(50))
+ .count();
+}
+```
+
+
+> 저자 컴퓨터 (n = 100000000)
+> 원래 - 31초
+> 병렬 - 9.2초
+
+> 켬미 컴퓨터 (n = 100000000)
+> 원래 ![](https://i.imgur.com/EiiPMgm.png)
+
+>
+> 병렬 ![](https://i.imgur.com/Xd3X6Nm.png)
+
+
+
+
+# 추가 꿀팁
+
+무작위 수들로 이뤄진 스트림을 병렬화 하려거든 `ThreadLocalRandom` 보다는 `SplittableRandom` 인스턴스를 이용하자
+
+> 만든 의도도 이렇다
+>
+> `SplittableRandom` 은 멀티 스레드 -> 병렬화 하면 성능이 선형으로 증가한다.
+> `ThreadLocalRandom` 은 단일 스레드
+
diff --git "a/08\354\236\245/\354\225\204\354\235\264\355\205\234_53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md" "b/08\354\236\245/\354\225\204\354\235\264\355\205\234_53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md"
new file mode 100644
index 0000000..b96ff11
--- /dev/null
+++ "b/08\354\236\245/\354\225\204\354\235\264\355\205\234_53/\352\260\200\353\263\200\354\235\270\354\210\230\353\212\224_\354\213\240\354\244\221\355\236\210_\354\202\254\354\232\251\355\225\230\353\235\274_\354\274\254\353\257\270.md"
@@ -0,0 +1,115 @@
+
+# 가변 인수가 뭔데?
+
+```java
+public int sum(int... args) {}
+```
+
+`원하는 타입` + `...` 을 사용하면 0개 이상의 인수를 유동적으로 입력 받을 수 있게 해주는 인수
+
+> 그래서 인수 개수가 정해져 있지 않을 때 좋다!
+
+
+
+# 어떻게 동작?
+
+인수의 개수와 길이가 같은 ==배열==을 만들고 인수들을 해당 배열에 저장하여 가변인수 메서드에 전
+
+
+
+# 문제 있어?
+
+
+
+## 아니 동작 잘 되고 좋아~
+
+```java
+static int sum(int... args) {
+ int sum = 0;
+ for(arg : args) {
+ sum += arg;
+ }
+ return sum;
+}
+```
+
+**Good !**
+
+
+## 가끔 문제 있을 수도?
+
+```java
+static int min(int... args) {
+ int min = Integer.MAX_VALUE;
+ for(arg : args) {
+ if(min > arg) {
+ min = arg;
+ }
+ }
+
+ return min;
+}
+```
+
+**Bad !**
+
+인자가 0개 이면 가장 작은 값이 `Integer.MAX_VALUE` 인게 맞아?
+
+
+
+### 인자가 0개 일 때, 또 따로 처리 해줘야 함
+
+```java
+static int min(int... args) {
+ if(args.length == 0) {
+ throw new IllegalArgumentException("아무것도 없는데 최소를 어떻게 구해?");
+ }
+ int min = args[0];
+ for(int i = 1; i < args.length; i++) {
+ if(min > args[i]) {
+ min = arg[i];
+ }
+ }
+
+ return min;
+}
+```
+
+for-each 문도 사용 못하고 유효성 검사해야하고 귀찮다
+
+그래서 인자가 0개이면 안되는 메서드는 다음과 같이 작성한다
+
+```java
+static int min(int firstArg, int... args) {
+ int min = firstArg;
+ for(arg : args) {
+ if(min > arg) {
+ min = arg;
+ }
+ }
+
+ return min;
+}
+```
+
+
+
+### 성능에 민감한 경우 가변인수는 걸림돌
+
+가변인수는 메서드 호출 때 마다 배열을 할당하고 초기화 한다
+
+#### 그래도 난 유동적이면서 성능은 지키고 싶어 !
+
+자주 사용되는 개수까지는 값을 받고, 그 이상은 가변인수를 쓰자
+
+```java
+public void foo() { }
+public void foo(int a1) { }
+public void foo(int a1, int a2) { }
+public void foo(int a1, int a2, int... rest) { } // 이거 사용되는 경우는 5%라고 하면 성능 최적화됨
+```
+
+특수한 상황에서는 이렇게 하면 사막의 오아시스처럼 성능 향상에 도움을 줌
+
+> EnumSet 은 비트 필드를 대체하면서 성능까지 유지해야해서 위처럼 사용함
+