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 은 비트 필드를 대체하면서 성능까지 유지해야해서 위처럼 사용함 +