실제로 Java를 쓰는 사람들은 CPU와 아무 관련이없는 것 같습니다. 최대 CPU를 실행하는 방법과 앞에서 언급 한 스레드 수를 설정하는 방법과 관련이 있습니다. 그러나 그 알고리즘은 단지 참조 일뿐입니다. 많은 다른 시나리오에는 실질적인 수단이 필요합니다. 또한 CPU를 실행 한 후 CPU를 가득 채우지 않는 방법도 고려할 것입니다. 하하, 인간, 그게 다야. 하하, 좋아,이 기사는 다른 것들에 관한 것입니다. 어쩌면 당신은 거의 Java에서 코드를 작성하지 않을 것입니다. 비즈니스를 만족시키는 것이 첫 번째 중요한 것이기 때문에 CPU에주의를 기울이십시오. 프레임 워크 수준을 달성하고 많은 공유 데이터 캐시를 프레임 워크에 제공하려면 중간에 많은 데이터 요청 문제가 있어야합니다. 물론 Java는 많은 클래스의 동시 패키지를 제공하고 사용할 수 있지만 내부적으로 수행되는 방식은 더 잘 사용하려면 세부 사항을 이해해야합니다. 그렇지 않으면 사용하지 않는 것이 좋습니다. 이 기사는 타이틀 파티와 마찬가지로 CPU에 대해 이야기하고 싶습니다.
마찬가지로 Java는 CPU와 아무 관련이없는 것 같습니다. 그래서 지금 무슨 일이 일어나고 있는지 이야기 해 봅시다.
1. 공유 요소를 만나면 첫 번째 아이디어는 휘발성, 즉 절대 가시성을 통해 일관된 읽기 작업을 보장하는 것입니다. 소위 가시성은이 데이터를 사용할 때마다 CPU가 캐시 컨텐츠를 사용하지 않으며 메모리에서 데이터를 가져옵니다. 이 프로세스는 여전히 여러 CPU에 유효합니다. 이는 현재 CPU와 메모리가 동기화되어 있음을 의미합니다. CPU는 버스와 같은 Lock Addl 0과 유사한 어셈블리 명령을 발행하지만 +0이지만 아무것도 관련이 없습니다. 그러나 명령이 완료되면 후속 작업은 더 이상이 요소의 다른 스레드의 액세스에 영향을 미치지 않으며, 이는 달성 할 수있는 절대적인 가시성이지만 일관된 작업을 구현할 수는 없습니다. 즉, 휘발성이 달성 할 수없는 것은 I ++ 작업이 분해되기 때문에 I ++ (여러 스레드의 동시성)와 같은 작업의 일관성입니다.
int tmp = i; tmp = tmp + 1; i = tmp;
이 세 단계는 완료되었습니다. 이 시점에서 I ++가 다른 일을 먼저 수행 한 다음 다른 변수에 값을 할당하기 때문에 왜 1을 추가 할 수 있는지 알 수 있습니다.
2. 멀티 스레드 동시성 일관성을 사용하려면 잠금 메커니즘을 사용해야합니다. 현재 원자*와 같은 것은 기본적으로 이러한 요구 사항을 충족시킬 수 있습니다. 많은 안전하지 않은 수업 방법이 내부적으로 제공됩니다. 절대 가시성 데이터를 지속적으로 비교함으로써 획득 한 데이터가 최신 상태인지 확인할 수 있습니다. 다음으로 다른 CPU 문제에 대해 계속 이야기 할 것입니다.
3. 과거에는 CPU를 채우기 위해 CPU를 실행할 수 없었지만 메모리와 CPU 사이의 지연을 어떻게 무시하기 시작하든 만족하지 못했습니다. 오늘 우리가 언급 한 이후로, 우리는 지연에 대해 간단히 이야기 할 것입니다. 일반적으로, 현재 CPU는 3 단계 캐시를 가지며 지연은 연령에 따라 다르므로 특정 숫자는 대략적으로 만 가능합니다. 오늘날의 CPU는 일반적으로 1-2ns의 지연이 있고, 두 번째 레벨 캐시는 일반적으로 NS에서 약 10 NS이며, 세 번째 수준 캐시는 일반적으로 30Ns에서 50ns 사이이며 메모리 액세스는 일반적으로 70ns 이상에 도달합니다 (컴퓨터는 매우 빠르게 개발 되며이 값은 일부 CPU의 데이터에 대한 데이터입니다). 이 지연은 매우 작지만 모두 나노초 수준에 있으며 프로그램이 명령 작업으로 나뉘어지면 많은 CPU 상호 작용이 있음을 알 수 있습니다. 각 상호 작용의 지연이 너무 커지면 현재 시스템 성능이 변경됩니다.
4. 지금 언급 한 휘발성으로 돌아갑니다. 메모리에서 데이터를 얻을 때마다 캐시를 포기합니다. 물론 일부 싱글 스레드 작업에서 느리게되면 느리게됩니다. 때때로 우리는 이것을해야합니다. 읽기 및 쓰기 작업조차도 일관성이 필요하며 전체 데이터 블록조차 동기화됩니다. 우리는 잠금의 세분성을 어느 정도 만 줄일 수 있지만 잠금을 전혀 가질 수는 없습니다. CPU 수준 자체조차도 지시 수준 제한이 있습니다.
5. CPU 수준에서의 원자 작전은 일반적으로 장벽, 쓰기 장벽 등으로 장벽이라고합니다. 일반적으로 한 지점에 의해 트리거됩니다. 프로그램의 여러 지시 사항이 CPU로 전송되면 프로그램의 순서대로 일부 지침을 실행하지 않을 수 있으며 일부 지침은 프로그램의 최종 순서에서 일관되게 보장 할 수있는 한 프로그램의 순서로 실행되어야합니다. 정렬 측면에서 JIT는 런타임 중에 변경되며 CPU 명령 수준도 변경됩니다. 주된 이유는 런타임 지침을 최적화하여 프로그램을 더 빨리 실행하도록하는 것입니다.
6. CPU 레벨은 메모리에서 캐시 라인을 작동합니다. 소위 캐시 라인은 일반적으로 CPU 모델 및 아키텍처와 관련된 메모리 조각을 지속적으로 읽습니다. 오늘날, 많은 CPU는 일반적으로 매번 연속 메모리를 읽을 것이며 초기 CPU는 32BITE를 가질 것이므로 일부 배열을 가로 질러 가면 더 빠릅니다 (열 트래버스를 기반으로 매우 느립니다). 다음은 몇 가지 반대 상황을 비교합니다.
7. CPU가 데이터를 변경하면 데이터 수정 CPU 상태에 대해 이야기해야합니다. 모든 데이터를 읽으면 여러 CPU의 여러 스레드로 병렬로 읽을 수 있습니다. 데이터 블록에 작업을 작성할 때는 다릅니다. 데이터 블록은 독점, 수정, 무효화 및 기타 상태를 갖게되며 수정 후 데이터가 자연스럽게 실패합니다. 여러 스레드가 여러 CPU에서 동일한 데이터 블록을 수정하면 CPU 간의 버스 데이터 복사 (QPI)가 발생합니다. 물론, 동일한 데이터로 수정하면 선택의 여지가 없지만 6 점의 캐시 라인으로 돌아 오면 문제가 더 번거 롭습니다. 데이터가 동일한 배열에 있고 배열의 요소가 동시에 CPU로 캐싱되는 경우 다중 스레드의 QPI가 매우 빈번합니다. 때로는이 문제가 배열에 조립 된 객체가 조립 된 경우에도 다음과 같이 발생합니다.
class inputinteger {private int value; public inputinteger (int i) {this.value = i;}} inputinteger [] integers = new inputinteger [size]; for (int i = 0; i <size; i ++) {integers [i] = new inputinteger (i);} 현재 정수의 모든 것이 객체라는 것을 알 수 있으며 배열의 객체에 대한 참조만이 있지만 객체의 배열은 이론적으로 독립적이며 연속적으로 저장되지 않습니다. 그러나 Java가 객체 메모리를 할당하면 종종 에덴 지역에 지속적으로 할당됩니다. FOR 루프 내에있는 경우 다른 스레드에 액세스하지 않으면 이러한 객체는 함께 저장됩니다. 그들이 오래된 지역에 GC이더라도, 그것은 합쳐질 가능성이 높습니다. 따라서 Cache 라인을 해결하기 위해 간단한 물체에 의존하여 전체 배열을 수정하는 방법은 4 바이트이기 때문에 신뢰할 수없는 것 같습니다. 64 모드 에서이 크기는 24 바이트 (4Bytes가 채워짐)이고 포인터 압축은 16 바이트입니다. 즉, CPU는 매번 3-4 개의 객체와 일치 할 수 있습니다. CPU 캐시를 만드는 방법이지만 시스템의 QPI에는 영향을 미치지 않습니다. GC 프로세스 메모리 복사 프로세스가 함께 복사 될 가능성이 높기 때문에 객체를 분리하여 완료하지 마십시오. 가장 좋은 방법은 그것을 채우는 것입니다. 약간의 메모리 폐기물이지만, 이것은 가장 신뢰할 수있는 방법이며,이 방법은 물체를 64 바이트로 채우는 것입니다. 포인터 압축이 활성화되지 않으면 24 바이트가 있으며 현재 40 바이트가 있습니다. 물체 내부에 5 개의 오래 만 추가하면됩니다.
클래스 inputinteger {public int value; private long a1, a2, a3, a4, a5;} 하하,이 방법은 매우 소박하지만 잘 작동합니다. 때로는 JVM이 컴파일되면 이러한 매개 변수가 수행되지 않았으므로 직접 죽임을 당합니다. 최적화가 유효하지 않습니다. 메소드 + 방법은 메소드 본문 에서이 5 개의 매개 변수를 간단히 작동시키는 것이지만이 방법은 결코 호출하지 않습니다.
8. CPU 수준에서 때때로 첫 번째로 수행 할 수는 없습니다. 그것은 왕입니다. Atomicintegerfieldupdater의 작동에서 단일 스레드에서 Getanteset (true)을 호출하면 매우 빠르게 실행되며 멀티 코어 CPU에서 속도가 느려지기 시작합니다. 왜 위에 명확하게 말합니까? getAndset이 수정되고 비교되고 먼저 변경되므로 QPI가 매우 높아서 현재로서는 작업을 먼저 얻은 다음 수정하는 것이 좋습니다. 또한 한 번 얻는 좋은 방법입니다. 그것이 얻을 수 없다면, 다른 실이 다른 일을하면서 포기하고 보이게하십시오.
9. 때때로, 일부 CPU의 문제를 바쁘고 바쁘지 않은 문제를 해결하기 위해서는 해결할 알고리즘이 많이있을 것입니다. 예를 들어 NUMA는 솔루션 중 하나입니다. 그러나 특정 시나리오에서 어떤 아키텍처가 더 유용하든 모든 시나리오에 효과적이지 않을 수 있습니다. CPU 상태 관리를 완료하기위한 대기열 잠금 메커니즘이 있지만, 상태가 자주 변경되기 때문에 캐시 라인의 문제도 가지고 있으며, 다양한 응용 프로그램의 코어는 CPU와 협력하기 위해 CPU를보다 효과적으로 사용할 수 있도록 CPU와 협력 할 수있는 일부 알고리즘을 생성 할 것입니다.
일반 변수 루프의 중첩, 휘발성 유형 및 원자* 시리즈와 같은 세부 사항은 완전히 다릅니다. 다차원 배열 루프, 다른 위도로 뒤로 순서대로 반복되며 많은 세부 사항이 있으며 실제 최적화 프로세스에 영감이있는 이유를 이해합니다. 자물쇠의 세부 사항은 너무 얇고 현기증이 나며 시스템의 바닥 수준에는 항상 약간의 가벼운 원자 작업이 있습니다. 누가 그의 코드가 잠금을 요구하지 않는다고 말하든 상관없이, 최고는 CPU가 매 순간 하나의 명령을 실행할 수있는 것만 큼 간단 할 수 있습니다. 멀티 코어 CPU는 또한 읽기 레벨, 쓰기 레벨, 메모리 레벨 등을 포함하여 버스 레벨에서 일부 컨텐츠를 제어 할 수있는 공유 영역이 있습니다. 다른 시나리오에서는 잠금의 세분성이 가능한 한 많이 줄어 듭니다. 시스템의 성능은 자명하며 정상적인 결과입니다.